Ver código fonte

Add features to GUI (#757)

* controller image changes depending on the selected controller type

the new controller image assets are temporary until i get new ones

* Game list scans subdirs for games

* Key file existence check

* Only shows Program NCAs in Application list

* Change shown GUI columns without restarting

* Sort by column if you click on the column header

Columns are sorted as text so there are inaccuracies on some columns

* Fix sort on Time Played, Last Played and File Size columns

* Add ability to designate favourite games #1

TODO:
- Make fav games persistent
- Fix invisible check marks due to theme

* Add ability to designate favourite games #2

Also removed default theme

* Added a Windows specific build condition and a Linux bug fix

* bugfix

* Load metadata from JSONs

* Temp bug fix for MacOS

* lil clean up

* requested changes

* Misc fixes

* edited schema and config

* Show the TitleID of games on the title bar

* gui column config option have names

* Async loading of game list

* bugfix and cleanup

* thog's requested changes

* requested changes and cleanup

still need to fix the gtk seizure

* Fix issue where an ExeFS as a NSP didn't show up in the application list

* Minor fixes

* catch glib unhandled exceptions

* Make sure to do UI manipulation in the main thread

* Print path of invalid files

* Ac_k's requested changes

* Return of the dark theme

* move AboutInfo struct to another file

* sort usings

* changes

- gdkchan's requested changes that have been marked resolved
- made some structs internal as they aren't used outside of the GUI
- renamed Ryujinx.UI to Ryujinx.Ui to fit naming convention and folder structure
- fixed bug where controller type dropdown box is stretched
Xpl0itR 6 anos atrás
pai
commit
da4e0856c9
46 arquivos alterados com 1792 adições e 2083 exclusões
  1. 10 12
      Ryujinx.HLE/HOS/Horizon.cs
  2. 1 1
      Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs
  3. 1 1
      Ryujinx.HLE/Loaders/Npdm/ACI0.cs
  4. 1 1
      Ryujinx.HLE/Loaders/Npdm/ACID.cs
  5. 1 1
      Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs
  6. 1 1
      Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs
  7. 1 1
      Ryujinx.HLE/Loaders/Npdm/Npdm.cs
  8. 1 1
      Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs
  9. 6 0
      Ryujinx.sln.DotSettings
  10. 22 5
      Ryujinx/Config.json
  11. 4 4
      Ryujinx/Configuration.cs
  12. 22 6
      Ryujinx/Program.cs
  13. 6 0
      Ryujinx/RPsupported.dat
  14. 36 12
      Ryujinx/Ryujinx.csproj
  15. 0 672
      Ryujinx/Theme.css
  16. 9 0
      Ryujinx/Ui/AboutInfo.cs
  17. 22 26
      Ryujinx/Ui/AboutWindow.cs
  18. 5 5
      Ryujinx/Ui/AboutWindow.glade
  19. 11 0
      Ryujinx/Ui/ApplicationAddedEventArgs.cs
  20. 17 0
      Ryujinx/Ui/ApplicationData.cs
  21. 252 240
      Ryujinx/Ui/ApplicationLibrary.cs
  22. 9 0
      Ryujinx/Ui/ApplicationMetadata.cs
  23. 7 4
      Ryujinx/Ui/GLScreen.cs
  24. 23 0
      Ryujinx/Ui/GtkDialog.cs
  25. 16 0
      Ryujinx/Ui/GuiColumns.cs
  26. 476 207
      Ryujinx/Ui/MainWindow.cs
  27. 96 26
      Ryujinx/Ui/MainWindow.glade
  28. 1 2
      Ryujinx/Ui/NpadController.cs
  29. 1 1
      Ryujinx/Ui/NpadKeyboard.cs
  30. 100 75
      Ryujinx/Ui/SwitchSettings.cs
  31. 620 776
      Ryujinx/Ui/SwitchSettings.glade
  32. BIN
      Ryujinx/Ui/assets/BlueCon.png
  33. BIN
      Ryujinx/Ui/assets/DiscordLogo.png
  34. BIN
      Ryujinx/Ui/assets/GitHubLogo.png
  35. 0 0
      Ryujinx/Ui/assets/Icon.png
  36. BIN
      Ryujinx/Ui/assets/JoyCon.png
  37. 0 0
      Ryujinx/Ui/assets/NCAIcon.png
  38. 0 0
      Ryujinx/Ui/assets/NROIcon.png
  39. 0 0
      Ryujinx/Ui/assets/NSOIcon.png
  40. 0 0
      Ryujinx/Ui/assets/NSPIcon.png
  41. BIN
      Ryujinx/Ui/assets/PatreonLogo.png
  42. BIN
      Ryujinx/Ui/assets/ProCon.png
  43. BIN
      Ryujinx/Ui/assets/RedCon.png
  44. BIN
      Ryujinx/Ui/assets/TwitterLogo.png
  45. 0 0
      Ryujinx/Ui/assets/XCIIcon.png
  46. 14 3
      Ryujinx/_schema.json

+ 10 - 12
Ryujinx.HLE/HOS/Horizon.cs

@@ -105,11 +105,9 @@ namespace Ryujinx.HLE.HOS
 
         public Nacp ControlData { get; set; }
 
-        public string CurrentTitle { get; private set; }
-
         public string TitleName { get; private set; }
 
-        public string TitleID { get; private set; }
+        public string TitleId { get; private set; }
 
         public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
 
@@ -366,7 +364,7 @@ namespace Ryujinx.HLE.HOS
             {
                 ControlData = new Nacp(controlFile.AsStream());
 
-                TitleName = CurrentTitle = ControlData.Descriptions[(int) State.DesiredTitleLanguage].Title;
+                TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
             }
         }
 
@@ -500,12 +498,12 @@ namespace Ryujinx.HLE.HOS
 
                 Nacp controlData = new Nacp(controlFile.AsStream());
 
-                TitleName = CurrentTitle = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
-                TitleID   = metaData.Aci0.TitleId.ToString("x16");
+                TitleName = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
+                TitleId   = metaData.Aci0.TitleId.ToString("x16");
 
-                if (string.IsNullOrWhiteSpace(CurrentTitle))
+                if (string.IsNullOrWhiteSpace(TitleName))
                 {
-                    TitleName = CurrentTitle = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                    TitleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
                 }
 
                 return controlData;
@@ -517,7 +515,7 @@ namespace Ryujinx.HLE.HOS
             }
             else
             {
-                TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
+                TitleId = metaData.Aci0.TitleId.ToString("x16");
             }
         }
 
@@ -557,7 +555,7 @@ namespace Ryujinx.HLE.HOS
                 }
             }
 
-            TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
+            TitleId = metaData.Aci0.TitleId.ToString("x16");
 
             LoadNso("rtld");
             LoadNso("main");
@@ -659,8 +657,8 @@ namespace Ryujinx.HLE.HOS
 
             ContentManager.LoadEntries();
 
-            TitleName = CurrentTitle = metaData.TitleName;
-            TitleID   = metaData.Aci0.TitleId.ToString("x16");
+            TitleName = metaData.TitleName;
+            TitleId   = metaData.Aci0.TitleId.ToString("x16");
 
             ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
         }

+ 1 - 1
Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs

@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
 
             return new ApplicationLaunchProperty
             {
-                TitleId             = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleID), 0),
+                TitleId             = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleId), 0),
                 Version             = 0x00,
                 BaseGameStorageId   = (byte)StorageId.NandSystem,
                 UpdateGameStorageId = (byte)StorageId.None

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/ACI0.cs

@@ -3,7 +3,7 @@ using System.IO;
 
 namespace Ryujinx.HLE.Loaders.Npdm
 {
-    class Aci0
+    public class Aci0
     {
         private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
 

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/ACID.cs

@@ -3,7 +3,7 @@ using System.IO;
 
 namespace Ryujinx.HLE.Loaders.Npdm
 {
-    class Acid
+    public class Acid
     {
         private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
 

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs

@@ -2,7 +2,7 @@
 
 namespace Ryujinx.HLE.Loaders.Npdm
 {
-    class FsAccessControl
+    public class FsAccessControl
     {
         public int   Version            { get; private set; }
         public ulong PermissionsBitmask { get; private set; }

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs

@@ -2,7 +2,7 @@
 
 namespace Ryujinx.HLE.Loaders.Npdm
 {
-    class KernelAccessControl
+    public class KernelAccessControl
     {
         public int[] Capabilities { get; private set; }
 

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/Npdm.cs

@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
     // https://github.com/SciresM/hactool/blob/master/npdm.c
     // https://github.com/SciresM/hactool/blob/master/npdm.h
     // http://switchbrew.org/index.php?title=NPDM
-    class Npdm
+    public class Npdm
     {
         private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
 

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs

@@ -5,7 +5,7 @@ using System.Text;
 
 namespace Ryujinx.HLE.Loaders.Npdm
 {
-    class ServiceAccessControl
+    public class ServiceAccessControl
     {
         public IReadOnlyDictionary<string, bool> Services { get; private set; }
 

+ 6 - 0
Ryujinx.sln.DotSettings

@@ -4,9 +4,15 @@
 	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
 	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=mins/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=nacp/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Npad/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=patreon/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Probs/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Ryujinx/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Sint/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Snorm/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Srgb/@EntryIndexedValue">True</s:Boolean>

+ 22 - 5
Ryujinx/Config.json

@@ -7,7 +7,9 @@
     "logging_enable_error": true,
     "logging_enable_guest": true,
     "logging_enable_fs_access_log": false,
-    "logging_filtered_classes": [ ],
+    "logging_filtered_classes": [
+        
+    ],
     "enable_file_log": true,
     "system_language": "AmericanEnglish",
     "docked_mode": false,
@@ -15,12 +17,27 @@
     "enable_vsync": true,
     "enable_multicore_scheduling": true,
     "enable_fs_integrity_checks": true,
+    "fs_global_access_log_mode": 0,
     "ignore_missing_services": false,
     "controller_type": "Handheld",
-    "gui_columns": [ true, true, true, true, true, true, true, true, true ],
-    "game_dirs": [],
+    "gui_columns": {
+        "fav_column": true,
+        "icon_column": true,
+        "app_column": true,
+        "dev_column": true,
+        "version_column": true,
+        "time_played_column": true,
+        "last_played_column": true,
+        "file_ext_column": true,
+        "file_size_column": true,
+        "path_column": true
+    },
+    "game_dirs": [
+        
+    ],
     "enable_custom_theme": false,
     "custom_theme_path": "",
+    "enable_keyboard": false,
     "keyboard_controls": {
         "left_joycon": {
             "stick_up": "W",
@@ -54,7 +71,7 @@
             "toggle_vsync": "Tab"
         }
     },
-        "joystick_controls": {
+    "joystick_controls": {
         "enabled": true,
         "index": 0,
         "deadzone": 0.05,
@@ -82,4 +99,4 @@
             "button_zr": "Axis5"
         }
     }
-}
+}

+ 4 - 4
Ryujinx/Configuration.cs

@@ -7,8 +7,8 @@ using Ryujinx.HLE;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.HOS.Services;
 using Ryujinx.HLE.Input;
-using Ryujinx.UI;
-using Ryujinx.UI.Input;
+using Ryujinx.Ui;
+using Ryujinx.Ui.Input;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -124,7 +124,7 @@ namespace Ryujinx
         /// <summary>
         /// Used to toggle columns in the GUI
         /// </summary>
-        public List<bool> GuiColumns { get; set; }
+        public GuiColumns GuiColumns { get; set; }
 
         /// <summary>
         /// A list of directories containing games to be used to load games into the games list
@@ -154,7 +154,7 @@ namespace Ryujinx
         /// <summary>
         /// Controller control bindings
         /// </summary>
-        public UI.Input.NpadController JoystickControls { get; private set; }
+        public Ui.Input.NpadController JoystickControls { get; private set; }
 
         /// <summary>
         /// Loads a configuration file from disk

+ 22 - 6
Ryujinx/Program.cs

@@ -1,7 +1,7 @@
 using Gtk;
 using Ryujinx.Common.Logging;
 using Ryujinx.Profiler;
-using Ryujinx.UI;
+using Ryujinx.Ui;
 using System;
 using System.IO;
 
@@ -18,16 +18,20 @@ namespace Ryujinx
 
             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
             AppDomain.CurrentDomain.ProcessExit        += CurrentDomain_ProcessExit;
+            GLib.ExceptionManager.UnhandledException   += Glib_UnhandledException;
 
             Profile.Initialize();
 
             Application.Init();
 
-            Application gtkApplication = new Application("Ryujinx.Ryujinx", GLib.ApplicationFlags.None);
-            MainWindow  mainWindow     = new MainWindow(args, gtkApplication);
+            string appDataPath     = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs", "system", "prod.keys");
+            string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch", "prod.keys");
+            if (!File.Exists(appDataPath) && !File.Exists(userProfilePath))
+            {
+                GtkDialog.CreateErrorDialog($"Key file was not found. Please refer to `KEYS.md` for more info");
+            }
 
-            gtkApplication.Register(GLib.Cancellable.Current);
-            gtkApplication.AddWindow(mainWindow);
+            MainWindow mainWindow = new MainWindow();
             mainWindow.Show();
 
             if (args.Length == 1)
@@ -45,7 +49,7 @@ namespace Ryujinx
 
         private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
         {
-            var exception = e.ExceptionObject as Exception;
+            Exception exception = e.ExceptionObject as Exception;
 
             Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}");
 
@@ -54,5 +58,17 @@ namespace Ryujinx
                 Logger.Shutdown();
             }
         }
+
+        private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e)
+        {
+            Exception exception = e.ExceptionObject as Exception;
+
+            Logger.PrintError(LogClass.Application, $"Unhandled exception caught: {exception}");
+
+            if (e.IsTerminating)
+            {
+                Logger.Shutdown();
+            }
+        }
     }
 }

+ 6 - 0
Ryujinx/RPsupported.dat

@@ -9,6 +9,7 @@
 010034e005c9c000
 01004f8006a78000
 010051f00ac5e000
+010056e00853a000
 0100574009f9e000
 0100628004bce000
 0100633007d48000
@@ -16,15 +17,20 @@
 010068f00aa78000
 01006a800016e000
 010072800cbe8000
+01007300020fa000
 01007330027ee000
 0100749009844000
 01007a4008486000
+01007ef00011e000
 010080b00ad66000
+01008db008c2c000
 010094e00b52e000
 01009aa000faa000
 01009b90006dc000
+01009cc00c97c000
 0100a4200a284000
 0100a5c00d162000
+0100abf008968000
 0100ae000aebc000
 0100b3f000be2000
 0100bc2004ff4000

+ 36 - 12
Ryujinx/Ryujinx.csproj

@@ -18,23 +18,50 @@
     <Optimize>false</Optimize>
   </PropertyGroup>
 
-  <!-- Due to GtkSharp. -->
+  <!-- Due to .net core 3.0 embedded resource loading -->
   <PropertyGroup>
     <EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
+    <DefineConstants>MACOS_BUILD</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="Ui\AboutWindow.glade" />
+    <None Remove="Ui\assets\BlueCon.png" />
+    <None Remove="Ui\assets\ProCon.png" />
+    <None Remove="Ui\assets\RedCon.png" />
+    <None Remove="Ui\assets\NCAIcon.png" />
+    <None Remove="Ui\assets\NROIcon.png" />
+    <None Remove="Ui\assets\NSOIcon.png" />
+    <None Remove="Ui\assets\NSPIcon.png" />
+    <None Remove="Ui\assets\XCIIcon.png" />
+    <None Remove="Ui\assets\DiscordLogo.png" />
+    <None Remove="Ui\assets\GitHubLogo.png" />
+    <None Remove="Ui\assets\JoyCon.png" />
+    <None Remove="Ui\assets\PatreonLogo.png" />
+    <None Remove="Ui\assets\Icon.png" />
+    <None Remove="Ui\assets\TwitterLogo.png" />
+    <None Remove="Ui\MainWindow.glade" />
+    <None Remove="Ui\SwitchSettings.glade" />
+  </ItemGroup>
+
   <ItemGroup>
     <EmbeddedResource Include="Ui\AboutWindow.glade" />
-    <EmbeddedResource Include="Ui\assets\ryujinxNCAIcon.png" />
-    <EmbeddedResource Include="Ui\assets\ryujinxNROIcon.png" />
-    <EmbeddedResource Include="Ui\assets\ryujinxNSOIcon.png" />
-    <EmbeddedResource Include="Ui\assets\ryujinxNSPIcon.png" />
-    <EmbeddedResource Include="Ui\assets\ryujinxXCIIcon.png" />
+    <EmbeddedResource Include="Ui\assets\BlueCon.png" />
+    <EmbeddedResource Include="Ui\assets\ProCon.png" />
+    <EmbeddedResource Include="Ui\assets\RedCon.png" />
+    <EmbeddedResource Include="Ui\assets\NCAIcon.png" />
+    <EmbeddedResource Include="Ui\assets\NROIcon.png" />
+    <EmbeddedResource Include="Ui\assets\NSOIcon.png" />
+    <EmbeddedResource Include="Ui\assets\NSPIcon.png" />
+    <EmbeddedResource Include="Ui\assets\XCIIcon.png" />
     <EmbeddedResource Include="Ui\assets\DiscordLogo.png" />
     <EmbeddedResource Include="Ui\assets\GitHubLogo.png" />
     <EmbeddedResource Include="Ui\assets\JoyCon.png" />
     <EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
-    <EmbeddedResource Include="Ui\assets\ryujinxIcon.png" />
+    <EmbeddedResource Include="Ui\assets\Icon.png" />
     <EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
     <EmbeddedResource Include="Ui\MainWindow.glade" />
     <EmbeddedResource Include="Ui\SwitchSettings.glade" />
@@ -42,8 +69,8 @@
 
   <ItemGroup>
     <PackageReference Include="DiscordRichPresence" Version="1.0.121" />
-    <PackageReference Include="GtkSharp" Version="3.22.24.37" />
-    <PackageReference Include="GtkSharp.Dependencies" Version="1.0.1" />
+    <PackageReference Include="GtkSharp" Version="3.22.25.24" />
+    <PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
     <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
   </ItemGroup>
@@ -61,9 +88,6 @@
     <None Update="Config.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Update="Theme.css">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
     <None Update="RPsupported.dat">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 672
Ryujinx/Theme.css


+ 9 - 0
Ryujinx/Ui/AboutInfo.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Ui
+{
+    internal struct AboutInfo
+    {
+        public string InstallVersion;
+        public string InstallCommit;
+        public string InstallBranch;
+    }
+}

+ 22 - 26
Ryujinx/Ui/AboutWindow.cs

@@ -1,27 +1,22 @@
 using Gtk;
-using GUI = Gtk.Builder.ObjectAttribute;
 using System;
 using System.Diagnostics;
+using System.IO;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using Utf8Json;
 using Utf8Json.Resolvers;
-using System.IO;
 
-namespace Ryujinx.UI
-{
-    public struct Info
-    {
-        public string InstallVersion;
-        public string InstallCommit;
-        public string InstallBranch;
-    }
+using GUI = Gtk.Builder.ObjectAttribute;
 
+namespace Ryujinx.Ui
+{
     public class AboutWindow : Window
     {
-        public static Info Information { get; private set; }
+        private static AboutInfo AboutInformation { get; set; }
 
-#pragma warning disable 649
+#pragma warning disable CS0649
+#pragma warning disable IDE0044
         [GUI] Window _aboutWin;
         [GUI] Label  _versionText;
         [GUI] Image  _ryujinxLogo;
@@ -29,7 +24,8 @@ namespace Ryujinx.UI
         [GUI] Image  _gitHubLogo;
         [GUI] Image  _discordLogo;
         [GUI] Image  _twitterLogo;
-#pragma warning restore 649
+#pragma warning restore CS0649
+#pragma warning restore IDE0044
 
         public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { }
 
@@ -37,8 +33,8 @@ namespace Ryujinx.UI
         {
             builder.Autoconnect(this);
 
-            _aboutWin.Icon      = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
-            _ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png", 100, 100);
+            _aboutWin.Icon      = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+            _ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"       , 100, 100);
             _patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 );
             _gitHubLogo.Pixbuf  = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 );
             _discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 );
@@ -50,10 +46,10 @@ namespace Ryujinx.UI
 
                 using (Stream stream = File.OpenRead(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "Installer", "Config", "Config.json")))
                 {
-                    Information = JsonSerializer.Deserialize<Info>(stream, resolver);
+                    AboutInformation = JsonSerializer.Deserialize<AboutInfo>(stream, resolver);
                 }
 
-                _versionText.Text = $"Version {Information.InstallVersion} - {Information.InstallBranch} ({Information.InstallCommit})";
+                _versionText.Text = $"Version {AboutInformation.InstallVersion} - {AboutInformation.InstallBranch} ({AboutInformation.InstallCommit})";
             }
             catch
             {
@@ -61,7 +57,7 @@ namespace Ryujinx.UI
             }
         }
 
-        public void OpenUrl(string url)
+        private static void OpenUrl(string url)
         {
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
@@ -78,39 +74,39 @@ namespace Ryujinx.UI
         }
 
         //Events
-        private void RyujinxButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://ryujinx.org");
         }
 
-        private void PatreonButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://www.patreon.com/ryujinx");
         }
 
-        private void GitHubButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://github.com/Ryujinx/Ryujinx");
         }
 
-        private void DiscordButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://discordapp.com/invite/N2FmfVc");
         }
 
-        private void TwitterButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://twitter.com/RyujinxEmu");
         }
 
-        private void ContributersButton_Pressed(object obj, ButtonPressEventArgs args)
+        private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
         {
             OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
         }
 
-        private void CloseToggle_Activated(object obj, EventArgs args)
+        private void CloseToggle_Activated(object sender, EventArgs args)
         {
-            Destroy();
+            Dispose();
         }
     }
 }

+ 5 - 5
Ryujinx/Ui/AboutWindow.glade

@@ -154,10 +154,10 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel">
+                      <object class="GtkLabel" id="license">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Unlicenced</property>
+                        <property name="label" translatable="yes">MIT License</property>
                         <property name="justify">center</property>
                       </object>
                       <packing>
@@ -168,7 +168,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel">
+                      <object class="GtkLabel" id="disclaimer">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
@@ -523,11 +523,11 @@ Andy A (BaronKiko)</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEventBox" id="ContributersButton">
+                      <object class="GtkEventBox" id="ContributorsButton">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="valign">start</property>
-                        <signal name="button-press-event" handler="ContributersButton_Pressed" swapped="no"/>
+                        <signal name="button-press-event" handler="ContributorsButton_Pressed" swapped="no"/>
                         <child>
                           <object class="GtkLabel">
                             <property name="visible">True</property>

+ 11 - 0
Ryujinx/Ui/ApplicationAddedEventArgs.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.Ui
+{
+    public class ApplicationAddedEventArgs : EventArgs
+    {
+        public ApplicationData AppData       { get; set; }
+        public int             NumAppsFound  { get; set; }
+        public int             NumAppsLoaded { get; set; }
+    }
+}

+ 17 - 0
Ryujinx/Ui/ApplicationData.cs

@@ -0,0 +1,17 @@
+namespace Ryujinx.Ui
+{
+    public struct ApplicationData
+    {
+        public bool   Favorite      { get; set; }
+        public byte[] Icon          { get; set; }
+        public string TitleName     { get; set; }
+        public string TitleId       { get; set; }
+        public string Developer     { get; set; }
+        public string Version       { get; set; }
+        public string TimePlayed    { get; set; }
+        public string LastPlayed    { get; set; }
+        public string FileExtension { get; set; }
+        public string FileSize      { get; set; }
+        public string Path          { get; set; }
+    }
+}

+ 252 - 240
Ryujinx/Ui/ApplicationLibrary.cs

@@ -1,66 +1,50 @@
-using LibHac;
+using JsonPrettyPrinterPlus;
+using LibHac;
 using LibHac.Fs;
 using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.Spl;
 using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.Loaders.Npdm;
 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using Utf8Json;
+using Utf8Json.Resolvers;
 
-using SystemState = Ryujinx.HLE.HOS.SystemState;
+using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage;
 
-namespace Ryujinx.UI
+namespace Ryujinx.Ui
 {
     public class ApplicationLibrary
     {
-        private static Keyset KeySet;
-        private static SystemState.TitleLanguage DesiredTitleLanguage;
+        public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
 
-        private const double SecondsPerMinute = 60.0;
-        private const double SecondsPerHour   = SecondsPerMinute * 60;
-        private const double SecondsPerDay    = SecondsPerHour   * 24;
+        private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
+        private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
+        private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png");
+        private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
+        private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
 
-        public static byte[] RyujinxNspIcon { get; private set; }
-        public static byte[] RyujinxXciIcon { get; private set; }
-        public static byte[] RyujinxNcaIcon { get; private set; }
-        public static byte[] RyujinxNroIcon { get; private set; }
-        public static byte[] RyujinxNsoIcon { get; private set; }
+        private static Keyset              _keySet;
+        private static TitleLanguage       _desiredTitleLanguage;
+        private static ApplicationMetadata _appMetadata;
 
-        public static List<ApplicationData> ApplicationLibraryData { get; private set; }
-
-        public struct ApplicationData
+        public static void LoadApplications(List<string> appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage)
         {
-            public byte[] Icon;
-            public string TitleName;
-            public string TitleId;
-            public string Developer;
-            public string Version;
-            public string TimePlayed;
-            public string LastPlayed;
-            public string FileExt;
-            public string FileSize;
-            public string Path;
-        }
+            int numApplicationsFound  = 0;
+            int numApplicationsLoaded = 0;
 
-        public static void Init(List<string> AppDirs, Keyset keySet, SystemState.TitleLanguage desiredTitleLanguage)
-        {
-            KeySet               = keySet;
-            DesiredTitleLanguage = desiredTitleLanguage;
-
-            // Loads the default application Icons
-            RyujinxNspIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSPIcon.png");
-            RyujinxXciIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxXCIIcon.png");
-            RyujinxNcaIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNCAIcon.png");
-            RyujinxNroIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNROIcon.png");
-            RyujinxNsoIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSOIcon.png");
+            _keySet               = keySet;
+            _desiredTitleLanguage = desiredTitleLanguage;
 
             // Builds the applications list with paths to found applications
             List<string> applications = new List<string>();
-            foreach (string appDir in AppDirs)
+            foreach (string appDir in appDirs)
             {
                 if (Directory.Exists(appDir) == false)
                 {
@@ -69,30 +53,80 @@ namespace Ryujinx.UI
                     continue;
                 }
 
-                DirectoryInfo AppDirInfo = new DirectoryInfo(appDir);
-                foreach (FileInfo App in AppDirInfo.GetFiles())
+                foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
                 {
-                    if ((Path.GetExtension(App.ToString()) == ".xci")  ||
-                        (Path.GetExtension(App.ToString()) == ".nca")  ||
-                        (Path.GetExtension(App.ToString()) == ".nsp")  ||
-                        (Path.GetExtension(App.ToString()) == ".pfs0") ||
-                        (Path.GetExtension(App.ToString()) == ".nro")  ||
-                        (Path.GetExtension(App.ToString()) == ".nso"))
+                    if ((Path.GetExtension(app) == ".xci") ||
+                        (Path.GetExtension(app) == ".nro") ||
+                        (Path.GetExtension(app) == ".nso") ||
+                        (Path.GetFileName(app)  == "hbl.nsp"))
                     {
-                        applications.Add(App.ToString());
+                        applications.Add(app);
+                        numApplicationsFound++;
+                    }
+                    else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0"))
+                    {
+                        try
+                        {
+                            bool hasMainNca = false;
+
+                            PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
+                            foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
+                            {
+                                nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
+
+                                Nca nca       = new Nca(_keySet, ncaFile.AsStorage());
+                                int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+                                if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+                                {
+                                    hasMainNca = true;
+                                }
+                            }
+
+                            if (!hasMainNca)
+                            {
+                                continue;
+                            }
+                        }
+                        catch (InvalidDataException)
+                        {
+                            Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
+                        }
+
+                        applications.Add(app);
+                        numApplicationsFound++;
+                    }
+                    else if (Path.GetExtension(app) == ".nca")
+                    {
+                        try
+                        {
+                            Nca nca       = new Nca(_keySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
+                            int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+                            if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+                            {
+                                continue;
+                            }
+                        }
+                        catch (InvalidDataException)
+                        {
+                            Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
+                        }
+
+                        applications.Add(app);
+                        numApplicationsFound++;
                     }
                 }
             }
 
-            // Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
-            ApplicationLibraryData = new List<ApplicationData>();
+            // Loops through applications list, creating a struct and then firing an event containing the struct for each application
             foreach (string applicationPath in applications)
             {
-                double filesize        = new FileInfo(applicationPath).Length * 0.000000000931;
-                string titleName       = null;
-                string titleId         = null;
-                string developer       = null;
-                string version         = null;
+                double fileSize        = new FileInfo(applicationPath).Length * 0.000000000931;
+                string titleName       = "Unknown";
+                string titleId         = "0000000000000000";
+                string developer       = "Unknown";
+                string version         = "0";
                 byte[] applicationIcon = null;
 
                 using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
@@ -103,150 +137,156 @@ namespace Ryujinx.UI
                     {
                         try
                         {
-                            IFileSystem controlFs = null;
-
-                            // Store the ControlFS in variable called controlFs
+                            PartitionFileSystem pfs;
+                             
                             if (Path.GetExtension(applicationPath) == ".xci")
                             {
-                                Xci xci = new Xci(KeySet, file.AsStorage());
+                                Xci xci = new Xci(_keySet, file.AsStorage());
 
-                                controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
+                                pfs = xci.OpenPartition(XciPartitionType.Secure);
                             }
                             else
                             {
-                                controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
+                                pfs = new PartitionFileSystem(file.AsStorage());
                             }
 
-                            // Creates NACP class from the NACP file
-                            controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
+                            // Store the ControlFS in variable called controlFs
+                            IFileSystem controlFs = GetControlFs(pfs);
 
-                            Nacp controlData = new Nacp(controlNacpFile.AsStream());
+                            // If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP
+                            if (controlFs == null)
+                            {
+                                applicationIcon = _nspIcon;
 
-                            // Get the title name, title ID, developer name and version number from the NACP
-                            version = controlData.DisplayVersion;
+                                Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read);
 
-                            titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
+                                if (result != ResultFs.PathNotFound)
+                                {
+                                    Npdm npdm = new Npdm(npdmFile.AsStream());
 
-                            if (string.IsNullOrWhiteSpace(titleName))
-                            {
-                                titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                                    titleName = npdm.TitleName;
+                                    titleId   = npdm.Aci0.TitleId.ToString("x16");
+                                }
                             }
+                            else
+                            {
+                                // Creates NACP class from the NACP file
+                                controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
 
-                            titleId = controlData.PresenceGroupId.ToString("x16");
+                                Nacp controlData = new Nacp(controlNacpFile.AsStream());
 
-                            if (string.IsNullOrWhiteSpace(titleId))
-                            {
-                                titleId = controlData.SaveDataOwnerId.ToString("x16");
-                            }
+                                // Get the title name, title ID, developer name and version number from the NACP
+                                version = controlData.DisplayVersion;
 
-                            if (string.IsNullOrWhiteSpace(titleId))
-                            {
-                                titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
-                            }
+                                titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
 
-                            developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
+                                if (string.IsNullOrWhiteSpace(titleName))
+                                {
+                                    titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                                }
 
-                            if (string.IsNullOrWhiteSpace(developer))
-                            {
-                                developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
-                            }
+                                titleId = controlData.PresenceGroupId.ToString("x16");
 
-                            // Read the icon from the ControlFS and store it as a byte array
-                            try
-                            {
-                                controlFs.OpenFile(out IFile icon, $"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
+                                if (string.IsNullOrWhiteSpace(titleId))
+                                {
+                                    titleId = controlData.SaveDataOwnerId.ToString("x16");
+                                }
 
-                                using (MemoryStream stream = new MemoryStream())
+                                if (string.IsNullOrWhiteSpace(titleId))
                                 {
-                                    icon.AsStream().CopyTo(stream);
-                                    applicationIcon = stream.ToArray();
+                                    titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
                                 }
-                            }
-                            catch (HorizonResultException)
-                            {
-                                foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
+
+                                developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
+
+                                if (string.IsNullOrWhiteSpace(developer))
                                 {
-                                    if (entry.Name == "control.nacp")
-                                    {
-                                        continue;
-                                    }
+                                    developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
+                                }
 
-                                    controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
+                                // Read the icon from the ControlFS and store it as a byte array
+                                try
+                                {
+                                    controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
 
                                     using (MemoryStream stream = new MemoryStream())
                                     {
                                         icon.AsStream().CopyTo(stream);
                                         applicationIcon = stream.ToArray();
                                     }
-
-                                    if (applicationIcon != null)
+                                }
+                                catch (HorizonResultException)
+                                {
+                                    foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
                                     {
-                                        break;
+                                        if (entry.Name == "control.nacp")
+                                        {
+                                            continue;
+                                        }
+
+                                        controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
+
+                                        using (MemoryStream stream = new MemoryStream())
+                                        {
+                                            icon.AsStream().CopyTo(stream);
+                                            applicationIcon = stream.ToArray();
+                                        }
+
+                                        if (applicationIcon != null)
+                                        {
+                                            break;
+                                        }
                                     }
-                                }
 
-                                if (applicationIcon == null)
-                                {
-                                    applicationIcon = NspOrXciIcon(applicationPath);
+                                    if (applicationIcon == null)
+                                    {
+                                        applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
+                                    }
                                 }
                             }
                         }
                         catch (MissingKeyException exception)
                         {
-                            titleName       = "Unknown";
-                            titleId         = "Unknown";
-                            developer       = "Unknown";
-                            version         = "?";
-                            applicationIcon = NspOrXciIcon(applicationPath);
+                            applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
 
                             Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
                         }
                         catch (InvalidDataException)
                         {
-                            titleName       = "Unknown";
-                            titleId         = "Unknown";
-                            developer       = "Unknown";
-                            version         = "?";
-                            applicationIcon = NspOrXciIcon(applicationPath);
+                            applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
 
-                            Logger.PrintWarning(LogClass.Application, $"The file is not an NCA file or the header key is incorrect. Errored File: {applicationPath}");
-                        }
-                        catch (Exception exception)
-                        {
-                            Logger.PrintWarning(LogClass.Application, $"This warning usualy means that you have a DLC in one of you game directories\n{exception}");
-
-                            continue;
+                            Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
                         }
                     }
                     else if (Path.GetExtension(applicationPath) == ".nro")
                     {
                         BinaryReader reader = new BinaryReader(file);
 
-                        byte[] Read(long Position, int Size)
+                        byte[] Read(long position, int size)
                         {
-                            file.Seek(Position, SeekOrigin.Begin);
+                            file.Seek(position, SeekOrigin.Begin);
 
-                            return reader.ReadBytes(Size);
+                            return reader.ReadBytes(size);
                         }
 
                         file.Seek(24, SeekOrigin.Begin);
-                        int AssetOffset = reader.ReadInt32();
+                        int assetOffset = reader.ReadInt32();
 
-                        if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
+                        if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
                         {
-                            byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
+                            byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
 
-                            long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
-                            long iconSize   = BitConverter.ToInt64(IconSectionInfo, 8);
+                            long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
+                            long iconSize   = BitConverter.ToInt64(iconSectionInfo, 8);
 
                             ulong nacpOffset = reader.ReadUInt64();
                             ulong nacpSize   = reader.ReadUInt64();
 
                             // Reads and stores game icon as byte array
-                            applicationIcon = Read(AssetOffset + iconOffset, (int)iconSize);
+                            applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
 
                             // Creates memory stream out of byte array which is the NACP
-                            using (MemoryStream stream = new MemoryStream(Read(AssetOffset + (int)nacpOffset, (int)nacpSize)))
+                            using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize)))
                             {
                                 // Creates NACP class from the memory stream
                                 Nacp controlData = new Nacp(stream);
@@ -254,7 +294,7 @@ namespace Ryujinx.UI
                                 // Get the title name, title ID, developer name and version number from the NACP
                                 version = controlData.DisplayVersion;
 
-                                titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
+                                titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
 
                                 if (string.IsNullOrWhiteSpace(titleName))
                                 {
@@ -273,7 +313,7 @@ namespace Ryujinx.UI
                                     titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
                                 }
 
-                                developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
+                                developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
 
                                 if (string.IsNullOrWhiteSpace(developer))
                                 {
@@ -283,59 +323,50 @@ namespace Ryujinx.UI
                         }
                         else
                         {
-                            applicationIcon = RyujinxNroIcon;
-                            titleName       = "Application";
-                            titleId         = "0000000000000000";
-                            developer       = "Unknown";
-                            version         = "?";
+                            applicationIcon = _nroIcon;
                         }
                     }
                     // If its an NCA or NSO we just set defaults
                     else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
                     {
-                        if (Path.GetExtension(applicationPath) == ".nca")
-                        {
-                            applicationIcon = RyujinxNcaIcon;
-                        }
-                        else if (Path.GetExtension(applicationPath) == ".nso")
-                        {
-                            applicationIcon = RyujinxNsoIcon;
-                        }
-
-                        string fileName = Path.GetFileName(applicationPath);
-                        string fileExt  = Path.GetExtension(applicationPath);
-
-                        StringBuilder titlename = new StringBuilder();
-                        titlename.Append(fileName);
-                        titlename.Remove(fileName.Length - fileExt.Length, fileExt.Length);
-
-                        titleName = titlename.ToString();
-                        titleId   = "0000000000000000";
-                        version   = "?";
-                        developer = "Unknown";
+                        applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon;
+                        titleName       = Path.GetFileNameWithoutExtension(applicationPath);
                     }
                 }
 
-                string[] playedData = GetPlayedData(titleId, "00000000000000000000000000000001");
+                (bool favorite, string timePlayed, string lastPlayed) = GetMetadata(titleId);
 
                 ApplicationData data = new ApplicationData()
                 {
-                    Icon       = applicationIcon,
-                    TitleName  = titleName,
-                    TitleId    = titleId,
-                    Developer  = developer,
-                    Version    = version,
-                    TimePlayed = playedData[0],
-                    LastPlayed = playedData[1],
-                    FileExt    = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
-                    FileSize   = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
-                    Path       = applicationPath,
+                    Favorite      = favorite,
+                    Icon          = applicationIcon,
+                    TitleName     = titleName,
+                    TitleId       = titleId,
+                    Developer     = developer,
+                    Version       = version,
+                    TimePlayed    = timePlayed,
+                    LastPlayed    = lastPlayed,
+                    FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
+                    FileSize      = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
+                    Path          = applicationPath,
                 };
 
-                ApplicationLibraryData.Add(data);
+                numApplicationsLoaded++;
+
+                OnApplicationAdded(new ApplicationAddedEventArgs()
+                { 
+                    AppData       = data,
+                    NumAppsFound  = numApplicationsFound,
+                    NumAppsLoaded = numApplicationsLoaded
+                });
             }
         }
 
+        protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
+        {
+            ApplicationAdded?.Invoke(null, e);
+        }
+
         private static byte[] GetResourceBytes(string resourceName)
         {
             Stream resourceStream    = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
@@ -346,29 +377,29 @@ namespace Ryujinx.UI
             return resourceByteArray;
         }
 
-        private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
+        private static IFileSystem GetControlFs(PartitionFileSystem pfs)
         {
             Nca controlNca = null;
 
-            // Add keys to keyset if needed
-            foreach (DirectoryEntryEx ticketEntry in Pfs.EnumerateEntries("/", "*.tik"))
+            // Add keys to key set if needed
+            foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
             {
-                Result result = Pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
+                Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
 
                 if (result.IsSuccess())
                 {
                     Ticket ticket = new Ticket(ticketFile.AsStream());
 
-                    KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
+                    _keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_keySet)));
                 }
             }
 
             // Find the Control NCA and store it in variable called controlNca
-            foreach (DirectoryEntryEx fileEntry in Pfs.EnumerateEntries("/", "*.nca"))
+            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
             {
-                Pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
+                pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
 
-                Nca nca = new Nca(KeySet, ncaFile.AsStorage());
+                Nca nca = new Nca(_keySet, ncaFile.AsStorage());
 
                 if (nca.Header.ContentType == NcaContentType.Control)
                 {
@@ -377,84 +408,65 @@ namespace Ryujinx.UI
             }
 
             // Return the ControlFS
-            return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+            return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
         }
 
-        private static string[] GetPlayedData(string TitleId, string UserId)
+        private static (bool favorite, string timePlayed, string lastPlayed) GetMetadata(string titleId)
         {
-            try
-            {
-                string[] playedData = new string[2];
-                string savePath     = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", UserId, TitleId);
-
-                if (File.Exists(Path.Combine(savePath, "TimePlayed.dat")) == false)
-                {
-                    Directory.CreateDirectory(savePath);
-                    using (FileStream file = File.OpenWrite(Path.Combine(savePath, "TimePlayed.dat")))
-                    {
-                        file.Write(Encoding.ASCII.GetBytes("0"));
-                    }
-                }
-                using (FileStream fs = File.OpenRead(Path.Combine(savePath, "TimePlayed.dat")))
-                {
-                    using (StreamReader sr = new StreamReader(fs))
-                    {
-                        float timePlayed = float.Parse(sr.ReadLine());
+            string metadataFolder = Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui");
+            string metadataFile   = Path.Combine(metadataFolder, "metadata.json");
 
-                        if (timePlayed < SecondsPerMinute)
-                        {
-                            playedData[0] = $"{timePlayed}s";
-                        }
-                        else if (timePlayed < SecondsPerHour)
-                        {
-                            playedData[0] = $"{Math.Round(timePlayed / SecondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
-                        }
-                        else if (timePlayed < SecondsPerDay)
-                        {
-                            playedData[0] = $"{Math.Round(timePlayed / SecondsPerHour  , 2, MidpointRounding.AwayFromZero)} hrs";
-                        }
-                        else
-                        {
-                            playedData[0] = $"{Math.Round(timePlayed / SecondsPerDay   , 2, MidpointRounding.AwayFromZero)} days";
-                        }
-                    }
-                }
+            IJsonFormatterResolver resolver = CompositeResolver.Create(StandardResolver.AllowPrivateSnakeCase);
 
-                if (File.Exists(Path.Combine(savePath, "LastPlayed.dat")) == false)
-                {
-                    Directory.CreateDirectory(savePath);
-                    using (FileStream file = File.OpenWrite(Path.Combine(savePath, "LastPlayed.dat")))
-                    {
-                        file.Write(Encoding.ASCII.GetBytes("Never"));
-                    }
-                }
+            if (!File.Exists(metadataFile))
+            {
+                Directory.CreateDirectory(metadataFolder);
 
-                using (FileStream fs = File.OpenRead(Path.Combine(savePath, "LastPlayed.dat")))
+                _appMetadata = new ApplicationMetadata
                 {
-                    using (StreamReader sr = new StreamReader(fs))
-                    {
-                        playedData[1] = sr.ReadLine();
-                    }
-                }
+                    Favorite   = false,
+                    TimePlayed = 0,
+                    LastPlayed = "Never"
+                };
 
-                return playedData;
+                byte[] saveData = JsonSerializer.Serialize(_appMetadata, resolver);
+                File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
             }
-            catch
+
+            using (Stream stream = File.OpenRead(metadataFile))
             {
-                return new string[] { "Unknown", "Unknown" };
+                _appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
             }
+
+            return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed);
         }
 
-        private static byte[] NspOrXciIcon(string applicationPath)
+        private static string ConvertSecondsToReadableString(double seconds)
         {
-            if (Path.GetExtension(applicationPath) == ".xci")
+            const int secondsPerMinute = 60;
+            const int secondsPerHour   = secondsPerMinute * 60;
+            const int secondsPerDay    = secondsPerHour   * 24;
+
+            string readableString;
+
+            if (seconds < secondsPerMinute)
+            {
+                readableString = $"{seconds}s";
+            }
+            else if (seconds < secondsPerHour)
+            {
+                readableString = $"{Math.Round(seconds / secondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
+            }
+            else if (seconds < secondsPerDay)
             {
-                return RyujinxXciIcon;
+                readableString = $"{Math.Round(seconds / secondsPerHour, 2, MidpointRounding.AwayFromZero)} hrs";
             }
             else
             {
-                return RyujinxNspIcon;
+                readableString = $"{Math.Round(seconds / secondsPerDay, 2, MidpointRounding.AwayFromZero)} days";
             }
+
+            return readableString;
         }
     }
 }

+ 9 - 0
Ryujinx/Ui/ApplicationMetadata.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Ui
+{
+    internal struct ApplicationMetadata
+    {
+        public bool   Favorite   { get; set; }
+        public double TimePlayed { get; set; }
+        public string LastPlayed { get; set; }
+    }
+}

+ 7 - 4
Ryujinx/Ui/GLScreen.cs

@@ -10,7 +10,7 @@ using System.Threading;
 
 using Stopwatch = System.Diagnostics.Stopwatch;
 
-namespace Ryujinx.UI
+namespace Ryujinx.Ui
 {
     public class GlScreen : GameWindow
     {
@@ -297,10 +297,13 @@ namespace Ryujinx.UI
             double hostFps = _device.Statistics.GetSystemFrameRate();
             double gameFps = _device.Statistics.GetGameFrameRate();
 
-            string titleSection = string.IsNullOrWhiteSpace(_device.System.CurrentTitle) ? string.Empty
-                : " | " + _device.System.CurrentTitle;
+            string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
+                : " | " + _device.System.TitleName;
 
-            _newTitle = $"Ryujinx{titleSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
+            string titleIDSection = string.IsNullOrWhiteSpace(_device.System.TitleId) ? string.Empty
+                : " | " + _device.System.TitleId.ToUpper();
+
+            _newTitle = $"Ryujinx{titleNameSection}{titleIDSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
                 $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
 
             _titleEvent = true;

+ 23 - 0
Ryujinx/Ui/GtkDialog.cs

@@ -0,0 +1,23 @@
+using Gtk;
+using System.Reflection;
+
+namespace Ryujinx.Ui
+{
+    internal class GtkDialog
+    {
+        internal static void CreateErrorDialog(string errorMessage)
+        {
+            MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, null)
+            {
+                Title          = "Ryujinx - Error",
+                Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
+                Text           = "Ryujinx has encountered an error",
+                SecondaryText  = errorMessage,
+                WindowPosition = WindowPosition.Center
+            };
+            errorDialog.SetSizeRequest(100, 20);
+            errorDialog.Run();
+            errorDialog.Dispose();
+        }
+    }
+}

+ 16 - 0
Ryujinx/Ui/GuiColumns.cs

@@ -0,0 +1,16 @@
+namespace Ryujinx.Ui
+{
+    public struct GuiColumns
+    {
+        public bool FavColumn;
+        public bool IconColumn;
+        public bool AppColumn;
+        public bool DevColumn;
+        public bool VersionColumn;
+        public bool TimePlayedColumn;
+        public bool LastPlayedColumn;
+        public bool FileExtColumn;
+        public bool FileSizeColumn;
+        public bool PathColumn;
+    }
+}

+ 476 - 207
Ryujinx/Ui/MainWindow.cs

@@ -1,10 +1,11 @@
 using DiscordRPC;
 using Gtk;
-using GUI = Gtk.Builder.ObjectAttribute;
+using JsonPrettyPrinterPlus;
 using Ryujinx.Audio;
 using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gal;
 using Ryujinx.Graphics.Gal.OpenGL;
+using Ryujinx.Graphics.Gal;
+using Ryujinx.HLE.FileSystem;
 using Ryujinx.Profiler;
 using System;
 using System.Diagnostics;
@@ -12,25 +13,42 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using System.Threading.Tasks;
 using System.Threading;
+using Utf8Json;
+using Utf8Json.Resolvers;
 
-namespace Ryujinx.UI
+using GUI = Gtk.Builder.ObjectAttribute;
+
+namespace Ryujinx.Ui
 {
     public class MainWindow : Window
     {
-        internal static HLE.Switch _device;
+        private static HLE.Switch _device;
 
         private static IGalRenderer _renderer;
 
         private static IAalOutput _audioOut;
 
-        private static Application _gtkApplication;
+        private static GlScreen _screen;
 
         private static ListStore _tableStore;
 
-        private static bool _gameLoaded = false;
+        private static bool _updatingGameTable;
+        private static bool _gameLoaded;
+        private static bool _ending;
+
+        private static TreeViewColumn _favColumn;
+        private static TreeViewColumn _appColumn;
+        private static TreeViewColumn _devColumn;
+        private static TreeViewColumn _versionColumn;
+        private static TreeViewColumn _timePlayedColumn;
+        private static TreeViewColumn _lastPlayedColumn;
+        private static TreeViewColumn _fileExtColumn;
+        private static TreeViewColumn _fileSizeColumn;
+        private static TreeViewColumn _pathColumn;
 
-        private static string _userId = "00000000000000000000000000000001";
+        private static TreeView _treeView;
 
         public static bool DiscordIntegrationEnabled { get; set; }
 
@@ -38,12 +56,14 @@ namespace Ryujinx.UI
 
         public static RichPresence DiscordPresence;
 
-#pragma warning disable 649
+#pragma warning disable CS0649
+#pragma warning disable IDE0044
         [GUI] Window        _mainWin;
         [GUI] CheckMenuItem _fullScreen;
         [GUI] MenuItem      _stopEmulation;
+        [GUI] CheckMenuItem _favToggle;
         [GUI] CheckMenuItem _iconToggle;
-        [GUI] CheckMenuItem _titleToggle;
+        [GUI] CheckMenuItem _appToggle;
         [GUI] CheckMenuItem _developerToggle;
         [GUI] CheckMenuItem _versionToggle;
         [GUI] CheckMenuItem _timePlayedToggle;
@@ -51,28 +71,33 @@ namespace Ryujinx.UI
         [GUI] CheckMenuItem _fileExtToggle;
         [GUI] CheckMenuItem _fileSizeToggle;
         [GUI] CheckMenuItem _pathToggle;
-        [GUI] Box           _box;
         [GUI] TreeView      _gameTable;
-        [GUI] GLArea        _glScreen;
-#pragma warning restore 649
+        [GUI] Label         _progressLabel;
+        [GUI] LevelBar      _progressBar;
+#pragma warning restore CS0649
+#pragma warning restore IDE0044
 
-        public MainWindow(string[] args, Application gtkApplication) : this(new Builder("Ryujinx.Ui.MainWindow.glade"), args, gtkApplication) { }
+        public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
 
-        private MainWindow(Builder builder, string[] args, Application gtkApplication) : base(builder.GetObject("_mainWin").Handle)
+        private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
         {
+            builder.Autoconnect(this);
+
+            DeleteEvent += Window_Close;
+
+            ApplicationLibrary.ApplicationAdded += Application_Added;
+
             _renderer = new OglRenderer();
 
             _audioOut = InitializeAudioEngine();
 
             _device = new HLE.Switch(_renderer, _audioOut);
 
+            _treeView = _gameTable;
+
             Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
             Configuration.InitialConfigure(_device);
 
-            ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
-
-            _gtkApplication = gtkApplication;
-
             ApplyTheme();
 
             if (DiscordIntegrationEnabled)
@@ -94,117 +119,130 @@ namespace Ryujinx.UI
                 DiscordClient.SetPresence(DiscordPresence);
             }
 
-            builder.Autoconnect(this);
-
-            DeleteEvent += Window_Close;
-
-            _mainWin.Icon            = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
+            _mainWin.Icon            = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
             _stopEmulation.Sensitive = false;
 
-            if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _iconToggle.Active       = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _titleToggle.Active      = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _developerToggle.Active  = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _versionToggle.Active    = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _timePlayedToggle.Active = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _lastPlayedToggle.Active = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _fileExtToggle.Active    = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _fileSizeToggle.Active   = true; }
-            if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _pathToggle.Active       = true; }
-
-            if (args.Length == 1)
-            {
-                // Temporary code section start, remove this section when game is rendered to the GLArea in the GUI
-                _box.Remove(_glScreen);
-
-                if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon",        new CellRendererPixbuf(), "pixbuf", 0); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(),   "text",   1); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer",   new CellRendererText(),   "text",   2); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version",     new CellRendererText(),   "text",   3); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(),   "text",   4); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(),   "text",   5); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext",    new CellRendererText(),   "text",   6); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size",   new CellRendererText(),   "text",   7); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path",        new CellRendererText(),   "text",   8); }
-
-                _tableStore      = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
-                _gameTable.Model = _tableStore;
-
-                UpdateGameTable();
-                // Temporary code section end
-            }
-            else
+            if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn)        { _favToggle.Active        = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn)       { _iconToggle.Active       = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn)        { _appToggle.Active        = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn)        { _developerToggle.Active  = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn)    { _versionToggle.Active    = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn)    { _fileExtToggle.Active    = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn)   { _fileSizeToggle.Active   = true; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn)       { _pathToggle.Active       = true; }
+
+            _gameTable.Model = _tableStore = new ListStore(
+                typeof(bool), 
+                typeof(Gdk.Pixbuf), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string), 
+                typeof(string));
+            
+            _tableStore.SetSortFunc(5, TimePlayedSort);
+            _tableStore.SetSortFunc(6, LastPlayedSort);
+            _tableStore.SetSortFunc(8, FileSizeSort);
+            _tableStore.SetSortColumnId(0, SortType.Descending);
+
+            UpdateColumns();
+#pragma warning disable CS4014
+            UpdateGameTable();
+#pragma warning restore CS4014
+        }
+
+        internal static void ApplyTheme()
+        {
+            if (!SwitchSettings.SwitchConfig.EnableCustomTheme)
             {
-                _box.Remove(_glScreen);
+                return;
+            }
 
-                if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon",        new CellRendererPixbuf(), "pixbuf", 0); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(),   "text",   1); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer",   new CellRendererText(),   "text",   2); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version",     new CellRendererText(),   "text",   3); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(),   "text",   4); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(),   "text",   5); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext",    new CellRendererText(),   "text",   6); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size",   new CellRendererText(),   "text",   7); }
-                if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path",        new CellRendererText(),   "text",   8); }
+            if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
+            {
+                CssProvider cssProvider = new CssProvider();
 
-                _tableStore      = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
-                _gameTable.Model = _tableStore;
+                cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
 
-                UpdateGameTable();
+                StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
             }
-        }
-
-        public static void CreateErrorDialog(string errorMessage)
-        {
-            MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, errorMessage)
+            else
             {
-                Title          = "Ryujinx - Error",
-                Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png"),
-                WindowPosition = WindowPosition.Center
-            };
-            errorDialog.SetSizeRequest(100, 20);
-            errorDialog.Run();
-            errorDialog.Destroy();
+                Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\".");
+            }
         }
 
-        public static void UpdateGameTable()
+        private void UpdateColumns()
         {
-            _tableStore.Clear();
-            ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
+            foreach (TreeViewColumn column in _gameTable.Columns)
+            {
+                _gameTable.RemoveColumn(column);
+            }
 
-            foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
+            CellRendererToggle favToggle = new CellRendererToggle();
+            favToggle.Toggled += FavToggle_Toggled;
+
+            if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn)        { _gameTable.AppendColumn("Fav",         favToggle,                "active", 0); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn)       { _gameTable.AppendColumn("Icon",        new CellRendererPixbuf(), "pixbuf", 1); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn)        { _gameTable.AppendColumn("Application", new CellRendererText(),   "text",   2); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn)        { _gameTable.AppendColumn("Developer",   new CellRendererText(),   "text",   3); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn)    { _gameTable.AppendColumn("Version",     new CellRendererText(),   "text",   4); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(),   "text",   5); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(),   "text",   6); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn)    { _gameTable.AppendColumn("File Ext",    new CellRendererText(),   "text",   7); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn)   { _gameTable.AppendColumn("File Size",   new CellRendererText(),   "text",   8); }
+            if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn)       { _gameTable.AppendColumn("Path",        new CellRendererText(),   "text",   9); }
+
+            foreach (TreeViewColumn column in _gameTable.Columns)
             {
-                _tableStore.AppendValues(new Gdk.Pixbuf(AppData.Icon, 75, 75), $"{AppData.TitleName}\n{AppData.TitleId.ToUpper()}", AppData.Developer, AppData.Version, AppData.TimePlayed, AppData.LastPlayed, AppData.FileExt, AppData.FileSize, AppData.Path);
+                if (column.Title == "Fav")              { _favColumn        = column; }
+                else if (column.Title == "Application") { _appColumn        = column; }
+                else if (column.Title == "Developer")   { _devColumn        = column; }
+                else if (column.Title == "Version")     { _versionColumn    = column; }
+                else if (column.Title == "Time Played") { _timePlayedColumn = column; }
+                else if (column.Title == "Last Played") { _lastPlayedColumn = column; }
+                else if (column.Title == "File Ext")    { _fileExtColumn    = column; }
+                else if (column.Title == "File Size")   { _fileSizeColumn   = column; }
+                else if (column.Title == "Path")        { _pathColumn       = column; }
             }
+
+            if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn)        { _favColumn.SortColumnId        = 0; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn)       { _appColumn.SortColumnId        = 2; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn)        { _devColumn.SortColumnId        = 3; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn)        { _versionColumn.SortColumnId    = 4; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedColumn.SortColumnId = 5; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedColumn.SortColumnId = 6; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn)    { _fileExtColumn.SortColumnId    = 7; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn)   { _fileSizeColumn.SortColumnId   = 8; }
+            if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn)       { _pathColumn.SortColumnId       = 9; }
         }
 
-        public static void ApplyTheme()
+        internal static async Task UpdateGameTable()
         {
-            CssProvider cssProvider = new CssProvider();
-
-            if (SwitchSettings.SwitchConfig.EnableCustomTheme)
-            {
-                if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
-                {
-                    cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
-                }
-                else
-                {
-                    Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\"");
-                }
-            }
-            else
+            if (_updatingGameTable)
             {
-                cssProvider.LoadFromPath(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Theme.css"));
+                return;
             }
 
-            StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
+            _updatingGameTable = true;
+
+            _tableStore.Clear();
+
+            await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage));
+
+            _updatingGameTable = false;
         }
 
         internal void LoadApplication(string path)
         {
             if (_gameLoaded)
             {
-                CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
+                GtkDialog.CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
             }
             else
             {
@@ -266,19 +304,23 @@ namespace Ryujinx.UI
                     End();
                 }
 
-                new Thread(new ThreadStart(CreateGameWindow)).Start();
+#if MACOS_BUILD
+                CreateGameWindow();
+#else
+                new Thread(CreateGameWindow).Start();
+#endif
 
                 _gameLoaded              = true;
                 _stopEmulation.Sensitive = true;
 
                 if (DiscordIntegrationEnabled)
                 {
-                    if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleID))
+                    if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleId))
                     {
-                        DiscordPresence.Assets.LargeImageKey = _device.System.TitleID;
+                        DiscordPresence.Assets.LargeImageKey = _device.System.TitleId;
                     }
 
-                    string state = _device.System.TitleID;
+                    string state = _device.System.TitleId;
 
                     if (state == null)
                     {
@@ -306,40 +348,37 @@ namespace Ryujinx.UI
                     DiscordClient.SetPresence(DiscordPresence);
                 }
 
-                try
-                {
-                    string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
+                string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
+                string metadataFile   = System.IO.Path.Combine(metadataFolder, "metadata.json");
 
-                    if (File.Exists(System.IO.Path.Combine(savePath, "TimePlayed.dat")) == false)
-                    {
-                        Directory.CreateDirectory(savePath);
-                        using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
-                        {
-                            stream.Write(Encoding.ASCII.GetBytes("0"));
-                        }
-                    }
+                IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
 
-                    if (File.Exists(System.IO.Path.Combine(savePath, "LastPlayed.dat")) == false)
-                    {
-                        Directory.CreateDirectory(savePath);
-                        using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
-                        {
-                            stream.Write(Encoding.ASCII.GetBytes("Never"));
-                        }
-                    }
+                ApplicationMetadata appMetadata;
+
+                if (!File.Exists(metadataFile))
+                {
+                    Directory.CreateDirectory(metadataFolder);
 
-                    using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
+                    appMetadata = new ApplicationMetadata
                     {
-                        using (StreamWriter writer = new StreamWriter(stream))
-                        {
-                            writer.WriteLine(DateTime.UtcNow);
-                        }
-                    }
+                        Favorite   = false,
+                        TimePlayed = 0,
+                        LastPlayed = "Never"
+                    };
+
+                    byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
+                    File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
                 }
-                catch (ArgumentNullException)
+
+                using (Stream stream = File.OpenRead(metadataFile))
                 {
-                    Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
+                    appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
                 }
+
+                appMetadata.LastPlayed = DateTime.UtcNow.ToString();
+
+                byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
+                File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
             }
         }
 
@@ -347,9 +386,9 @@ namespace Ryujinx.UI
         {
             Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig);
             
-            using (GlScreen screen = new GlScreen(_device, _renderer))
+            using (_screen = new GlScreen(_device, _renderer))
             {
-                screen.MainLoop();
+                _screen.MainLoop();
 
                 End();
             }
@@ -357,41 +396,49 @@ namespace Ryujinx.UI
 
         private static void End()
         {
+            if (_ending)
+            {
+                return;
+            }
+
+            _ending = true;
+
             if (_gameLoaded)
             {
-                try
+                string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
+                string metadataFile   = System.IO.Path.Combine(metadataFolder, "metadata.json");
+
+                IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
+
+                ApplicationMetadata appMetadata;
+
+                if (!File.Exists(metadataFile))
                 {
-                    string savePath        = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
-                    double currentPlayTime = 0;
+                    Directory.CreateDirectory(metadataFolder);
 
-                    using (FileStream stream = File.OpenRead(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
+                    appMetadata = new ApplicationMetadata
                     {
-                        using (StreamReader reader = new StreamReader(stream))
-                        {
-                            DateTime startTime = DateTime.Parse(reader.ReadLine());
+                        Favorite   = false,
+                        TimePlayed = 0,
+                        LastPlayed = "Never"
+                    };
 
-                            using (FileStream lastPlayedStream = File.OpenRead(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
-                            {
-                                using (StreamReader lastPlayedReader = new StreamReader(lastPlayedStream))
-                                {
-                                    currentPlayTime = double.Parse(lastPlayedReader.ReadLine());
-                                }
-                            }
-
-                            using (FileStream timePlayedStream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
-                            {
-                                using (StreamWriter timePlayedWriter = new StreamWriter(timePlayedStream))
-                                {
-                                    timePlayedWriter.WriteLine(currentPlayTime + Math.Round(DateTime.UtcNow.Subtract(startTime).TotalSeconds, MidpointRounding.AwayFromZero));
-                                }
-                            }
-                        }
-                    }
+                    byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
+                    File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
                 }
-                catch (ArgumentNullException)
+
+                using (Stream stream = File.OpenRead(metadataFile))
                 {
-                    Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
+                    appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
                 }
+
+                DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
+                double   sessionTimePlayed  = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
+
+                appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
+
+                byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
+                File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
             }
 
             Profile.FinishProfiling();
@@ -423,15 +470,69 @@ namespace Ryujinx.UI
         }
 
         //Events
-        private void Row_Activated(object o, RowActivatedArgs args)
+        private void Application_Added(object sender, ApplicationAddedEventArgs e)
+        {
+            Application.Invoke(delegate
+            {
+                _tableStore.AppendValues(
+                    e.AppData.Favorite,
+                    new Gdk.Pixbuf(e.AppData.Icon, 75, 75),
+                    $"{e.AppData.TitleName}\n{e.AppData.TitleId.ToUpper()}",
+                    e.AppData.Developer,
+                    e.AppData.Version,
+                    e.AppData.TimePlayed,
+                    e.AppData.LastPlayed,
+                    e.AppData.FileExtension,
+                    e.AppData.FileSize,
+                    e.AppData.Path);
+
+                _progressLabel.Text = $"{e.NumAppsLoaded}/{e.NumAppsFound} Games Loaded";
+                _progressBar.Value  = (float)e.NumAppsLoaded / e.NumAppsFound;
+            });
+        }
+
+        private void FavToggle_Toggled(object sender, ToggledArgs args)
+        {
+            _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path));
+
+            string titleId      = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
+            string metadataPath = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui", "metadata.json");
+
+            IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
+
+            ApplicationMetadata appMetadata;
+            
+            using (Stream stream = File.OpenRead(metadataPath))
+            {
+                appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
+            }
+
+            if ((bool)_tableStore.GetValue(treeIter, 0))
+            {
+                _tableStore.SetValue(treeIter, 0, false);
+
+                appMetadata.Favorite = false;
+            }
+            else
+            {
+                _tableStore.SetValue(treeIter, 0, true);
+
+                appMetadata.Favorite = true;
+            }
+
+            byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
+            File.WriteAllText(metadataPath, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
+        }
+
+        private void Row_Activated(object sender, RowActivatedArgs args)
         {
             _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString()));
-            string path = (string)_tableStore.GetValue(treeIter, 8);
+            string path = (string)_tableStore.GetValue(treeIter, 9);
 
             LoadApplication(path);
         }
 
-        private void Load_Application_File(object o, EventArgs args)
+        private void Load_Application_File(object sender, EventArgs args)
         {
             FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
 
@@ -448,10 +549,10 @@ namespace Ryujinx.UI
                 LoadApplication(fileChooser.Filename);
             }
 
-            fileChooser.Destroy();
+            fileChooser.Dispose();
         }
 
-        private void Load_Application_Folder(object o, EventArgs args)
+        private void Load_Application_Folder(object sender, EventArgs args)
         {
             FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
 
@@ -460,35 +561,39 @@ namespace Ryujinx.UI
                 LoadApplication(fileChooser.Filename);
             }
 
-            fileChooser.Destroy();
+            fileChooser.Dispose();
         }
 
-        private void Open_Ryu_Folder(object o, EventArgs args)
+        private void Open_Ryu_Folder(object sender, EventArgs args)
         {
             Process.Start(new ProcessStartInfo()
             {
-                FileName        = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs"),
+                FileName        = new VirtualFileSystem().GetBasePath(),
                 UseShellExecute = true,
                 Verb            = "open"
             });
         }
 
-        private void Exit_Pressed(object o, EventArgs args)
+        private void Exit_Pressed(object sender, EventArgs args)
         {
+            _screen?.Exit();
             End();
         }
 
-        private void Window_Close(object o, DeleteEventArgs args)
+        private void Window_Close(object sender, DeleteEventArgs args)
         {
+            _screen?.Exit();
             End();
         }
 
-        private void StopEmulation_Pressed(object o, EventArgs args)
+        private void StopEmulation_Pressed(object sender, EventArgs args)
         {
             // TODO: Write logic to kill running game
+
+            _gameLoaded = false;
         }
 
-        private void FullScreen_Toggled(object o, EventArgs args)
+        private void FullScreen_Toggled(object sender, EventArgs args)
         {
             if (_fullScreen.Active)
             {
@@ -500,19 +605,15 @@ namespace Ryujinx.UI
             }
         }
 
-        private void Settings_Pressed(object o, EventArgs args)
+        private void Settings_Pressed(object sender, EventArgs args)
         {
-            SwitchSettings SettingsWin = new SwitchSettings(_device);
-
-            _gtkApplication.Register(GLib.Cancellable.Current);
-            _gtkApplication.AddWindow(SettingsWin);
-
-            SettingsWin.Show();
+            SwitchSettings settingsWin = new SwitchSettings(_device);
+            settingsWin.Show();
         }
 
-        private void Update_Pressed(object o, EventArgs args)
+        private void Update_Pressed(object sender, EventArgs args)
         {
-            string ryuUpdater = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "RyuUpdater.exe");
+            string ryuUpdater = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "RyuUpdater.exe");
 
             try
             {
@@ -520,81 +621,249 @@ namespace Ryujinx.UI
             }
             catch(System.ComponentModel.Win32Exception)
             {
-                CreateErrorDialog("Update canceled by user or updater was not found");
+                GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
             }
         }
 
-        private void About_Pressed(object o, EventArgs args)
+        private void About_Pressed(object sender, EventArgs args)
         {
-            AboutWindow AboutWin = new AboutWindow();
+            AboutWindow aboutWin = new AboutWindow();
+            aboutWin.Show();
+        }
+
+        private void Fav_Toggled(object sender, EventArgs args)
+        {
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
 
-            _gtkApplication.Register(GLib.Cancellable.Current);
-            _gtkApplication.AddWindow(AboutWin);
+            updatedColumns.FavColumn               = _favToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
-            AboutWin.Show();
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void Icon_Toggled(object o, EventArgs args)
+        private void Icon_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[0] = _iconToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.IconColumn              = _iconToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void Title_Toggled(object o, EventArgs args)
+        private void Title_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[1] = _titleToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.AppColumn               = _appToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void Developer_Toggled(object o, EventArgs args)
+        private void Developer_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[2] = _developerToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.DevColumn               = _developerToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void Version_Toggled(object o, EventArgs args)
+        private void Version_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[3] = _versionToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.VersionColumn           = _versionToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void TimePlayed_Toggled(object o, EventArgs args)
+        private void TimePlayed_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[4] = _timePlayedToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.TimePlayedColumn        = _timePlayedToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void LastPlayed_Toggled(object o, EventArgs args)
+        private void LastPlayed_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[5] = _lastPlayedToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.LastPlayedColumn        = _lastPlayedToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void FileExt_Toggled(object o, EventArgs args)
+        private void FileExt_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[6] = _fileExtToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.FileExtColumn           = _fileExtToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void FileSize_Toggled(object o, EventArgs args)
+        private void FileSize_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[7] = _fileSizeToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.FileSizeColumn          = _fileSizeToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
         }
 
-        private void Path_Toggled(object o, EventArgs args)
+        private void Path_Toggled(object sender, EventArgs args)
         {
-            SwitchSettings.SwitchConfig.GuiColumns[8] = _pathToggle.Active;
+            GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
+
+            updatedColumns.PathColumn              = _pathToggle.Active;
+            SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
 
             Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+
+            UpdateColumns();
+        }
+
+        private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
+        {
+#pragma warning disable CS4014
+            UpdateGameTable();
+#pragma warning restore CS4014
+        }
+
+        private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 5).ToString();
+            string bValue = model.GetValue(b, 5).ToString();
+
+            if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins")
+            {
+                aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString();
+            }
+            else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs")
+            {
+                aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString();
+            }
+            else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days")
+            {
+                aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString();
+            }
+            else
+            {
+                aValue = aValue.Substring(0, aValue.Length - 1);
+            }
+
+            if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins")
+            {
+                bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString();
+            }
+            else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs")
+            {
+                bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString();
+            }
+            else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days")
+            {
+                bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString();
+            }
+            else
+            {
+                bValue = bValue.Substring(0, bValue.Length - 1);
+            }
+
+            if (float.Parse(aValue) > float.Parse(bValue))
+            {
+                return -1;
+            }
+            else if (float.Parse(bValue) > float.Parse(aValue))
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 6).ToString();
+            string bValue = model.GetValue(b, 6).ToString();
+
+            if (aValue == "Never")
+            {
+                aValue = DateTime.UnixEpoch.ToString();
+            }
+
+            if (bValue == "Never")
+            {
+                bValue = DateTime.UnixEpoch.ToString();
+            }
+
+            return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
+        }
+
+        private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 8).ToString();
+            string bValue = model.GetValue(b, 8).ToString();
+
+            if (aValue.Substring(aValue.Length - 2) == "GB")
+            {
+                aValue = (float.Parse(aValue[0..^2]) * 1024).ToString();
+            }
+            else
+            {
+                aValue = aValue[0..^2];
+            }
+
+            if (bValue.Substring(bValue.Length - 2) == "GB")
+            {
+                bValue = (float.Parse(bValue[0..^2]) * 1024).ToString();
+            }
+            else
+            {
+                bValue = bValue[0..^2];
+            }
+
+            if (float.Parse(aValue) > float.Parse(bValue))
+            {
+                return -1;
+            }
+            else if (float.Parse(bValue) > float.Parse(aValue))
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
         }
     }
 }

+ 96 - 26
Ryujinx/Ui/MainWindow.glade

@@ -126,13 +126,23 @@
                       <object class="GtkMenuItem" id="GUIColumns">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Select which GUI columns to enable (restart Ryujinx for these changes to take effect)</property>
+                        <property name="tooltip_text" translatable="yes">Select which GUI columns to enable</property>
                         <property name="label" translatable="yes">Enable GUI Columns</property>
                         <property name="use_underline">True</property>
                         <child type="submenu">
                           <object class="GtkMenu">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_favToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Favorite Games Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Favorite Games Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Fav_Toggled" swapped="no"/>
+                              </object>
+                            </child>
                             <child>
                               <object class="GtkCheckMenuItem" id="_iconToggle">
                                 <property name="visible">True</property>
@@ -144,7 +154,7 @@
                               </object>
                             </child>
                             <child>
-                              <object class="GtkCheckMenuItem" id="_titleToggle">
+                              <object class="GtkCheckMenuItem" id="_appToggle">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
                                 <property name="tooltip_text" translatable="yes">Enable or Disable Title Name/ID Column in the game list</property>
@@ -303,22 +313,96 @@
           </packing>
         </child>
         <child>
-          <object class="GtkScrolledWindow" id="_gameTableWindow">
+          <object class="GtkBox" id="MainBox">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="shadow_type">in</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
             <child>
-              <object class="GtkTreeView" id="_gameTable">
+              <object class="GtkScrolledWindow" id="_gameTableWindow">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="headers_clickable">False</property>
-                <property name="reorderable">True</property>
-                <property name="hover_selection">True</property>
-                <signal name="row-activated" handler="Row_Activated" swapped="no"/>
-                <child internal-child="selection">
-                  <object class="GtkTreeSelection"/>
+                <property name="shadow_type">in</property>
+                <child>
+                  <object class="GtkTreeView" id="_gameTable">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="reorderable">True</property>
+                    <property name="hover_selection">True</property>
+                    <signal name="row-activated" handler="Row_Activated" swapped="no"/>
+                    <child internal-child="selection">
+                      <object class="GtkTreeSelection"/>
+                    </child>
+                  </object>
                 </child>
               </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="FooterBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkEventBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">5</property>
+                    <signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="name">RefreshList</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="stock">gtk-refresh</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="_progressLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">5</property>
+                    <property name="margin_right">5</property>
+                    <property name="margin_top">2</property>
+                    <property name="margin_bottom">2</property>
+                    <property name="label" translatable="yes">0/0 Games Loaded</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLevelBar" id="_progressBar">
+                    <property name="width_request">200</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="margin_left">5</property>
+                    <property name="margin_right">5</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
             </child>
           </object>
           <packing>
@@ -327,20 +411,6 @@
             <property name="position">1</property>
           </packing>
         </child>
-        <child>
-          <object class="GtkGLArea" id="_glScreen">
-            <property name="width_request">1280</property>
-            <property name="height_request">720</property>
-            <property name="visible">True</property>
-            <property name="app_paintable">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
       </object>
     </child>
   </object>

+ 1 - 2
Ryujinx/Ui/NpadController.cs

@@ -3,7 +3,7 @@ using OpenTK.Input;
 using Ryujinx.HLE.Input;
 using System;
 
-namespace Ryujinx.UI.Input
+namespace Ryujinx.Ui.Input
 {
     public enum ControllerInputId
     {
@@ -64,7 +64,6 @@ namespace Ryujinx.UI.Input
     public struct NpadControllerRight
     {
         public ControllerInputId Stick;
-        public ControllerInputId StickY;
         public ControllerInputId StickButton;
         public ControllerInputId ButtonA;
         public ControllerInputId ButtonB;

+ 1 - 1
Ryujinx/Ui/NpadKeyboard.cs

@@ -1,7 +1,7 @@
 using OpenTK.Input;
 using Ryujinx.HLE.Input;
 
-namespace Ryujinx.UI.Input
+namespace Ryujinx.Ui.Input
 {
     public struct NpadKeyboardLeft
     {

+ 100 - 75
Ryujinx/Ui/SwitchSettings.cs

@@ -1,27 +1,29 @@
 using Gtk;
-using GUI = Gtk.Builder.ObjectAttribute;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Input;
-using Ryujinx.UI.Input;
+using Ryujinx.Ui.Input;
 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
 
-namespace Ryujinx.UI
+using GUI = Gtk.Builder.ObjectAttribute;
+
+namespace Ryujinx.Ui
 {
     public class SwitchSettings : Window
     {
         internal static Configuration SwitchConfig { get; set; }
 
-        internal HLE.Switch Device { get; set; }
+        private readonly HLE.Switch _device;
 
         private static ListStore _gameDirsBoxStore;
 
         private static bool _listeningForKeypress;
 
-#pragma warning disable 649
+#pragma warning disable CS0649
+#pragma warning disable IDE0044
         [GUI] Window       _settingsWin;
         [GUI] CheckButton  _errorLogToggle;
         [GUI] CheckButton  _warningLogToggle;
@@ -51,7 +53,7 @@ namespace Ryujinx.UI
         [GUI] ToggleButton _removeDir;
         [GUI] Entry        _logPath;
         [GUI] Entry        _graphicsShadersDumpPath;
-        [GUI] Image        _controllerImage;
+        [GUI] Image        _controller1Image;
 
         [GUI] ComboBoxText _controller1Type;
         [GUI] ToggleButton _lStickUp1;
@@ -78,67 +80,70 @@ namespace Ryujinx.UI
         [GUI] ToggleButton _plus1;
         [GUI] ToggleButton _r1;
         [GUI] ToggleButton _zR1;
-#pragma warning restore 649
+#pragma warning restore CS0649
+#pragma warning restore IDE0044
 
-        public static void ConfigureSettings(Configuration Instance) { SwitchConfig = Instance; }
+        public static void ConfigureSettings(Configuration instance) { SwitchConfig = instance; }
 
         public SwitchSettings(HLE.Switch device) : this(new Builder("Ryujinx.Ui.SwitchSettings.glade"), device) { }
 
         private SwitchSettings(Builder builder, HLE.Switch device) : base(builder.GetObject("_settingsWin").Handle)
         {
-            Device = device;
-
             builder.Autoconnect(this);
 
-            _settingsWin.Icon       = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
-            _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
+            _device = device;
+
+            _settingsWin.Icon        = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+            _controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
 
             //Bind Events
-            _lStickUp1.Clicked     += (o, args) => Button_Pressed(o, args, _lStickUp1);
-            _lStickDown1.Clicked   += (o, args) => Button_Pressed(o, args, _lStickDown1);
-            _lStickLeft1.Clicked   += (o, args) => Button_Pressed(o, args, _lStickLeft1);
-            _lStickRight1.Clicked  += (o, args) => Button_Pressed(o, args, _lStickRight1);
-            _lStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _lStickButton1);
-            _dpadUp1.Clicked       += (o, args) => Button_Pressed(o, args, _dpadUp1);
-            _dpadDown1.Clicked     += (o, args) => Button_Pressed(o, args, _dpadDown1);
-            _dpadLeft1.Clicked     += (o, args) => Button_Pressed(o, args, _dpadLeft1);
-            _dpadRight1.Clicked    += (o, args) => Button_Pressed(o, args, _dpadRight1);
-            _minus1.Clicked        += (o, args) => Button_Pressed(o, args, _minus1);
-            _l1.Clicked            += (o, args) => Button_Pressed(o, args, _l1);
-            _zL1.Clicked           += (o, args) => Button_Pressed(o, args, _zL1);
-            _rStickUp1.Clicked     += (o, args) => Button_Pressed(o, args, _rStickUp1);
-            _rStickDown1.Clicked   += (o, args) => Button_Pressed(o, args, _rStickDown1);
-            _rStickLeft1.Clicked   += (o, args) => Button_Pressed(o, args, _rStickLeft1);
-            _rStickRight1.Clicked  += (o, args) => Button_Pressed(o, args, _rStickRight1);
-            _rStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _rStickButton1);
-            _a1.Clicked            += (o, args) => Button_Pressed(o, args, _a1);
-            _b1.Clicked            += (o, args) => Button_Pressed(o, args, _b1);
-            _x1.Clicked            += (o, args) => Button_Pressed(o, args, _x1);
-            _y1.Clicked            += (o, args) => Button_Pressed(o, args, _y1);
-            _plus1.Clicked         += (o, args) => Button_Pressed(o, args, _plus1);
-            _r1.Clicked            += (o, args) => Button_Pressed(o, args, _r1);
-            _zR1.Clicked           += (o, args) => Button_Pressed(o, args, _zR1);
+            _lStickUp1.Clicked       += (sender, args) => Button_Pressed(sender, args, _lStickUp1);
+            _lStickDown1.Clicked     += (sender, args) => Button_Pressed(sender, args, _lStickDown1);
+            _lStickLeft1.Clicked     += (sender, args) => Button_Pressed(sender, args, _lStickLeft1);
+            _lStickRight1.Clicked    += (sender, args) => Button_Pressed(sender, args, _lStickRight1);
+            _lStickButton1.Clicked   += (sender, args) => Button_Pressed(sender, args, _lStickButton1);
+            _dpadUp1.Clicked         += (sender, args) => Button_Pressed(sender, args, _dpadUp1);
+            _dpadDown1.Clicked       += (sender, args) => Button_Pressed(sender, args, _dpadDown1);
+            _dpadLeft1.Clicked       += (sender, args) => Button_Pressed(sender, args, _dpadLeft1);
+            _dpadRight1.Clicked      += (sender, args) => Button_Pressed(sender, args, _dpadRight1);
+            _minus1.Clicked          += (sender, args) => Button_Pressed(sender, args, _minus1);
+            _l1.Clicked              += (sender, args) => Button_Pressed(sender, args, _l1);
+            _zL1.Clicked             += (sender, args) => Button_Pressed(sender, args, _zL1);
+            _rStickUp1.Clicked       += (sender, args) => Button_Pressed(sender, args, _rStickUp1);
+            _rStickDown1.Clicked     += (sender, args) => Button_Pressed(sender, args, _rStickDown1);
+            _rStickLeft1.Clicked     += (sender, args) => Button_Pressed(sender, args, _rStickLeft1);
+            _rStickRight1.Clicked    += (sender, args) => Button_Pressed(sender, args, _rStickRight1);
+            _rStickButton1.Clicked   += (sender, args) => Button_Pressed(sender, args, _rStickButton1);
+            _a1.Clicked              += (sender, args) => Button_Pressed(sender, args, _a1);
+            _b1.Clicked              += (sender, args) => Button_Pressed(sender, args, _b1);
+            _x1.Clicked              += (sender, args) => Button_Pressed(sender, args, _x1);
+            _y1.Clicked              += (sender, args) => Button_Pressed(sender, args, _y1);
+            _plus1.Clicked           += (sender, args) => Button_Pressed(sender, args, _plus1);
+            _r1.Clicked              += (sender, args) => Button_Pressed(sender, args, _r1);
+            _zR1.Clicked             += (sender, args) => Button_Pressed(sender, args, _zR1);
+            _controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image);
 
             //Setup Currents
-            if (SwitchConfig.EnableFileLog)             { _fileLogToggle.Click();        }
-            if (SwitchConfig.LoggingEnableError)        { _errorLogToggle.Click();       }
-            if (SwitchConfig.LoggingEnableWarn)         { _warningLogToggle.Click();     }
-            if (SwitchConfig.LoggingEnableInfo)         { _infoLogToggle.Click();        }
-            if (SwitchConfig.LoggingEnableStub)         { _stubLogToggle.Click();        }
-            if (SwitchConfig.LoggingEnableDebug)        { _debugLogToggle.Click();       }
-            if (SwitchConfig.LoggingEnableGuest)        { _guestLogToggle.Click();       }
-            if (SwitchConfig.LoggingEnableFsAccessLog)  { _fsAccessLogToggle.Click();    }
-            if (SwitchConfig.DockedMode)                { _dockedModeToggle.Click();     }
-            if (SwitchConfig.EnableDiscordIntegration)  { _discordToggle.Click();        }
-            if (SwitchConfig.EnableVsync)               { _vSyncToggle.Click();          }
-            if (SwitchConfig.EnableMulticoreScheduling) { _multiSchedToggle.Click();     }
-            if (SwitchConfig.EnableFsIntegrityChecks)   { _fsicToggle.Click();           }
-            if (SwitchConfig.IgnoreMissingServices)     { _ignoreToggle.Click();         }
-            if (SwitchConfig.EnableKeyboard)            { _directKeyboardAccess.Click(); }
-            if (SwitchConfig.EnableCustomTheme)         { _custThemeToggle.Click();      }
+            if (SwitchConfig.EnableFileLog)             _fileLogToggle.Click();
+            if (SwitchConfig.LoggingEnableError)        _errorLogToggle.Click();
+            if (SwitchConfig.LoggingEnableWarn)         _warningLogToggle.Click();
+            if (SwitchConfig.LoggingEnableInfo)         _infoLogToggle.Click();
+            if (SwitchConfig.LoggingEnableStub)         _stubLogToggle.Click();
+            if (SwitchConfig.LoggingEnableDebug)        _debugLogToggle.Click();
+            if (SwitchConfig.LoggingEnableGuest)        _guestLogToggle.Click();
+            if (SwitchConfig.LoggingEnableFsAccessLog)  _fsAccessLogToggle.Click();
+            if (SwitchConfig.DockedMode)                _dockedModeToggle.Click();
+            if (SwitchConfig.EnableDiscordIntegration)  _discordToggle.Click();
+            if (SwitchConfig.EnableVsync)               _vSyncToggle.Click();
+            if (SwitchConfig.EnableMulticoreScheduling) _multiSchedToggle.Click();
+            if (SwitchConfig.EnableFsIntegrityChecks)   _fsicToggle.Click();
+            if (SwitchConfig.IgnoreMissingServices)     _ignoreToggle.Click();
+            if (SwitchConfig.EnableKeyboard)            _directKeyboardAccess.Click();
+            if (SwitchConfig.EnableCustomTheme)         _custThemeToggle.Click();
 
             _systemLanguageSelect.SetActiveId(SwitchConfig.SystemLanguage.ToString());
             _controller1Type     .SetActiveId(SwitchConfig.ControllerType.ToString());
+            Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image);
 
             _lStickUp1.Label     = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString();
             _lStickDown1.Label   = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString();
@@ -190,7 +195,7 @@ namespace Ryujinx.UI
         }
 
         //Events
-        private void Button_Pressed(object obj, EventArgs args, ToggleButton Button)
+        private void Button_Pressed(object sender, EventArgs args, ToggleButton button)
         {
             if (_listeningForKeypress == false)
             {
@@ -198,25 +203,25 @@ namespace Ryujinx.UI
 
                 _listeningForKeypress = true;
 
-                void On_KeyPress(object Obj, KeyPressEventArgs KeyPressed)
+                void On_KeyPress(object o, KeyPressEventArgs keyPressed)
                 {
-                    string key    = KeyPressed.Event.Key.ToString();
+                    string key    = keyPressed.Event.Key.ToString();
                     string capKey = key.First().ToString().ToUpper() + key.Substring(1);
 
                     if (Enum.IsDefined(typeof(OpenTK.Input.Key), capKey))
                     {
-                        Button.Label = capKey;
+                        button.Label = capKey;
                     }
-                    else if (GdkToOpenTKInput.ContainsKey(key))
+                    else if (GdkToOpenTkInput.ContainsKey(key))
                     {
-                        Button.Label = GdkToOpenTKInput[key];
+                        button.Label = GdkToOpenTkInput[key];
                     }
                     else
                     {
-                        Button.Label = "Space";
+                        button.Label = "Space";
                     }
 
-                    Button.SetStateFlags(0, true);
+                    button.SetStateFlags(0, true);
 
                     KeyPressEvent -= On_KeyPress;
 
@@ -225,11 +230,30 @@ namespace Ryujinx.UI
             }
             else
             {
-                Button.SetStateFlags(0, true);
+                button.SetStateFlags(0, true);
             }
         }
 
-        private void AddDir_Pressed(object obj, EventArgs args)
+        private void Controller_Changed(object sender, EventArgs args, string controllerType, Image controllerImage)
+        {
+            switch (controllerType)
+            {
+                case "ProController":
+                    controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.png", 500, 500);
+                    break;
+                case "NpadLeft":
+                    controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.BlueCon.png", 500, 500);
+                    break;
+                case "NpadRight":
+                    controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.RedCon.png", 500, 500);
+                    break;
+                default:
+                    controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
+                    break;
+            }
+        }
+
+        private void AddDir_Pressed(object sender, EventArgs args)
         {
             if (Directory.Exists(_addGameDirBox.Buffer.Text))
             {
@@ -239,7 +263,7 @@ namespace Ryujinx.UI
             _addDir.SetStateFlags(0, true);
         }
 
-        private void BrowseDir_Pressed(object obj, EventArgs args)
+        private void BrowseDir_Pressed(object sender, EventArgs args)
         {
             FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept);
 
@@ -248,12 +272,12 @@ namespace Ryujinx.UI
                 _gameDirsBoxStore.AppendValues(fileChooser.Filename);
             }
 
-            fileChooser.Destroy();
+            fileChooser.Dispose();
 
             _browseDir.SetStateFlags(0, true);
         }
 
-        private void RemoveDir_Pressed(object obj, EventArgs args)
+        private void RemoveDir_Pressed(object sender, EventArgs args)
         {
             TreeSelection selection = _gameDirsBox.Selection;
 
@@ -263,14 +287,14 @@ namespace Ryujinx.UI
             _removeDir.SetStateFlags(0, true);
         }
 
-        private void CustThemeToggle_Activated(object obj, EventArgs args)
+        private void CustThemeToggle_Activated(object sender, EventArgs args)
         {
             _custThemePath.Sensitive      = _custThemeToggle.Active;
             _custThemePathLabel.Sensitive = _custThemeToggle.Active;
             _browseThemePath.Sensitive    = _custThemeToggle.Active;
         }
 
-        private void BrowseThemeDir_Pressed(object obj, EventArgs args)
+        private void BrowseThemeDir_Pressed(object sender, EventArgs args)
         {
             FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
 
@@ -282,12 +306,12 @@ namespace Ryujinx.UI
                 _custThemePath.Buffer.Text = fileChooser.Filename;
             }
 
-            fileChooser.Destroy();
+            fileChooser.Dispose();
 
             _browseThemePath.SetStateFlags(0, true);
         }
 
-        private void SaveToggle_Activated(object obj, EventArgs args)
+        private void SaveToggle_Activated(object sender, EventArgs args)
         {
             List<string> gameDirs = new List<string>();
 
@@ -358,20 +382,21 @@ namespace Ryujinx.UI
             SwitchConfig.FsGlobalAccessLogMode   = (int)_fsLogSpinAdjustment.Value;
 
             Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
-            Configuration.Configure(Device, SwitchConfig);
+            Configuration.Configure(_device, SwitchConfig);
 
             MainWindow.ApplyTheme();
+#pragma warning disable CS4014
             MainWindow.UpdateGameTable();
-
-            Destroy();
+#pragma warning restore CS4014
+            Dispose();
         }
 
-        private void CloseToggle_Activated(object obj, EventArgs args)
+        private void CloseToggle_Activated(object sender, EventArgs args)
         {
-            Destroy();
+            Dispose();
         }
 
-        public readonly Dictionary<string, string> GdkToOpenTKInput = new Dictionary<string, string>()
+        public readonly Dictionary<string, string> GdkToOpenTkInput = new Dictionary<string, string>()
         {
             { "Key_0",       "Number0"        },
             { "Key_1",       "Number1"        },

+ 620 - 776
Ryujinx/Ui/SwitchSettings.glade

@@ -139,6 +139,7 @@
                                         <property name="visible">True</property>
                                         <property name="can_focus">False</property>
                                         <property name="tooltip_text" translatable="yes">Change System Language</property>
+                                        <property name="margin_left">5</property>
                                         <items>
                                           <item id="AmericanEnglish" translatable="yes">American English</item>
                                           <item id="BritishEnglish" translatable="yes">British English</item>
@@ -174,7 +175,7 @@
                                 </child>
                                 <child>
                                   <object class="GtkCheckButton" id="_discordToggle">
-                                    <property name="label" translatable="yes">Enable Discord Integration</property>
+                                    <property name="label" translatable="yes">Enable Discord Rich Presence</property>
                                     <property name="visible">True</property>
                                     <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
@@ -512,7 +513,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkBox" id="TabController">
+                      <object class="GtkBox" id="TabInput">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="orientation">vertical</property>
@@ -521,7 +522,7 @@
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
                             <property name="margin_top">5</property>
-                            <property name="margin_bottom">10</property>
+                            <property name="margin_bottom">5</property>
                             <child>
                               <object class="GtkCheckButton" id="_dockedModeToggle">
                                 <property name="label" translatable="yes">Enable Docked Mode</property>
@@ -563,636 +564,66 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkNotebook">
+                          <object class="GtkSeparator">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="Controller1Box">
                             <property name="visible">True</property>
-                            <property name="can_focus">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">10</property>
+                            <property name="margin_right">10</property>
+                            <property name="margin_bottom">5</property>
                             <child>
                               <object class="GtkBox">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
-                                <property name="margin_left">5</property>
-                                <property name="margin_right">5</property>
-                                <property name="margin_top">5</property>
-                                <property name="margin_bottom">5</property>
+                                <property name="orientation">vertical</property>
                                 <child>
                                   <object class="GtkBox">
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
-                                    <property name="orientation">vertical</property>
                                     <child>
-                                      <object class="GtkBox">
+                                      <object class="GtkLabel">
                                         <property name="visible">True</property>
                                         <property name="can_focus">False</property>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="tooltip_text" translatable="yes">The primary controller's type</property>
-                                            <property name="halign">center</property>
-                                            <property name="margin_left">10</property>
-                                            <property name="margin_right">10</property>
-                                            <property name="margin_top">5</property>
-                                            <property name="margin_bottom">5</property>
-                                            <property name="label" translatable="yes">Controller Type:</property>
-                                          </object>
-                                          <packing>
-                                            <property name="expand">False</property>
-                                            <property name="fill">True</property>
-                                            <property name="position">0</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkComboBoxText" id="_controller1Type">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="tooltip_text" translatable="yes">The primary controller's type</property>
-                                            <property name="margin_left">5</property>
-                                            <property name="active">0</property>
-                                            <items>
-                                              <item id="Handheld" translatable="yes">Handheld</item>
-                                              <item id="ProController" translatable="yes">Pro Controller</item>
-                                              <item id="NpadPair" translatable="yes">Paired Joycons</item>
-                                              <item id="NpadLeft" translatable="yes">Left Joycon</item>
-                                              <item id="NpadRight" translatable="yes">Right Joycon</item>
-                                            </items>
-                                          </object>
-                                          <packing>
-                                            <property name="expand">True</property>
-                                            <property name="fill">True</property>
-                                            <property name="position">1</property>
-                                          </packing>
-                                        </child>
+                                        <property name="tooltip_text" translatable="yes">The primary controller's type</property>
+                                        <property name="halign">center</property>
+                                        <property name="margin_top">5</property>
+                                        <property name="margin_bottom">5</property>
+                                        <property name="label" translatable="yes">Controller Type:</property>
                                       </object>
                                       <packing>
-                                        <property name="expand">True</property>
+                                        <property name="expand">False</property>
                                         <property name="fill">True</property>
-                                        <property name="padding">10</property>
                                         <property name="position">0</property>
                                       </packing>
                                     </child>
                                     <child>
-                                      <object class="GtkGrid">
+                                      <object class="GtkComboBoxText" id="_controller1Type">
                                         <property name="visible">True</property>
                                         <property name="can_focus">False</property>
-                                        <property name="row_spacing">2</property>
-                                        <property name="column_spacing">5</property>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">LStick Up</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">0</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">LStick Down</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">1</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">LStick Left</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">2</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">LStick Right</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">3</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">LStick Button</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">4</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">Dpad Up</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">5</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">Dpad Down</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">6</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">Dpad Left</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">7</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">Dpad Right</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">8</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">-</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">9</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">L</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">10</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">ZL</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">0</property>
-                                            <property name="top_attach">11</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">ZR</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">11</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">R</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">10</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">+</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">9</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">Y</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">8</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">X</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">7</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">B</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">6</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">A</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">5</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">RStick Button</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">4</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">RStick Right</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">3</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">RStick Left</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">2</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">RStick Down</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">1</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkLabel">
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">False</property>
-                                            <property name="label" translatable="yes">RStick Up</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">2</property>
-                                            <property name="top_attach">0</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_lStickUp1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">0</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_lStickDown1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">1</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_lStickLeft1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">2</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_lStickRight1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">3</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_lStickButton1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">4</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_dpadUp1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">5</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_dpadDown1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">6</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_dpadLeft1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">7</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_dpadRight1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">8</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_minus1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">9</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_l1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">10</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_zL1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">1</property>
-                                            <property name="top_attach">11</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_rStickUp1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">0</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_rStickDown1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">1</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_rStickLeft1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">2</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_rStickRight1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">3</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_rStickButton1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">4</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_a1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">5</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_b1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">6</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_x1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">7</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_y1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">8</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_plus1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">9</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_r1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">10</property>
-                                          </packing>
-                                        </child>
-                                        <child>
-                                          <object class="GtkToggleButton" id="_zR1">
-                                            <property name="label" translatable="yes"> </property>
-                                            <property name="visible">True</property>
-                                            <property name="can_focus">True</property>
-                                            <property name="receives_default">True</property>
-                                          </object>
-                                          <packing>
-                                            <property name="left_attach">3</property>
-                                            <property name="top_attach">11</property>
-                                          </packing>
-                                        </child>
+                                        <property name="tooltip_text" translatable="yes">The primary controller's type</property>
+                                        <property name="margin_left">5</property>
+                                        <property name="active">0</property>
+                                        <items>
+                                          <item id="Handheld" translatable="yes">Handheld</item>
+                                          <item id="ProController" translatable="yes">Pro Controller</item>
+                                          <item id="NpadPair" translatable="yes">Paired Joycons</item>
+                                          <item id="NpadLeft" translatable="yes">Left Joycon</item>
+                                          <item id="NpadRight" translatable="yes">Right Joycon</item>
+                                        </items>
                                       </object>
                                       <packing>
-                                        <property name="expand">False</property>
+                                        <property name="expand">True</property>
                                         <property name="fill">True</property>
-                                        <property name="padding">10</property>
                                         <property name="position">1</property>
                                       </packing>
                                     </child>
@@ -1200,179 +631,592 @@
                                   <packing>
                                     <property name="expand">False</property>
                                     <property name="fill">True</property>
+                                    <property name="padding">10</property>
                                     <property name="position">0</property>
                                   </packing>
                                 </child>
                                 <child>
-                                  <object class="GtkImage" id="_controllerImage">
+                                  <object class="GtkGrid">
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
-                                    <property name="margin_left">5</property>
-                                  </object>
-                                  <packing>
-                                    <property name="expand">True</property>
-                                    <property name="fill">True</property>
-                                    <property name="position">1</property>
-                                  </packing>
-                                </child>
-                              </object>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller1">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 1</property>
-                              </object>
-                              <packing>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">1</property>
-                                <property name="tab_expand">True</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller2">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 2</property>
-                              </object>
-                              <packing>
-                                <property name="position">1</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">2</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 3</property>
-                              </object>
-                              <packing>
-                                <property name="position">2</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">3</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller4">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 4</property>
-                              </object>
-                              <packing>
-                                <property name="position">3</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">4</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller5">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 5</property>
-                              </object>
-                              <packing>
-                                <property name="position">4</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">5</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller6">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 6</property>
-                              </object>
-                              <packing>
-                                <property name="position">5</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">6</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller7">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 7</property>
-                              </object>
-                              <packing>
-                                <property name="position">6</property>
-                                <property name="tab_fill">False</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Multiple controllers are not yet supported</property>
-                              </object>
-                              <packing>
-                                <property name="position">7</property>
-                              </packing>
-                            </child>
-                            <child type="tab">
-                              <object class="GtkLabel" id="Controller8">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Controller 8</property>
-                              </object>
-                              <packing>
-                                <property name="position">7</property>
-                                <property name="tab_fill">False</property>
+                                    <property name="row_spacing">2</property>
+                                    <property name="column_spacing">5</property>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">LStick Up</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">LStick Down</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">LStick Left</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">LStick Right</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">3</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">LStick Button</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">4</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Dpad Up</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">5</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Dpad Down</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">6</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Dpad Left</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">7</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Dpad Right</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">8</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">-</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">9</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">L</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">10</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">ZL</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">11</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">ZR</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">11</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">R</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">10</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">+</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">9</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Y</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">8</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">X</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">7</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">B</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">6</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">A</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">5</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">RStick Button</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">4</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">RStick Right</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">3</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">RStick Left</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">RStick Down</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">RStick Up</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">2</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_lStickUp1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_lStickDown1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_lStickLeft1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_lStickRight1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">3</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_lStickButton1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">4</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_dpadUp1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">5</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_dpadDown1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">6</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_dpadLeft1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">7</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_dpadRight1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">8</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_minus1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">9</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_l1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">10</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_zL1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">11</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_rStickUp1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_rStickDown1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_rStickLeft1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_rStickRight1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">3</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_rStickButton1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">4</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_a1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">5</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_b1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">6</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_x1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">7</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_y1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">8</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_plus1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">9</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_r1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">10</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_zR1">
+                                        <property name="label" translatable="yes"> </property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">3</property>
+                                        <property name="top_attach">11</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">10</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkImage" id="_controller1Image">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
                               </packing>
                             </child>
                           </object>

BIN
Ryujinx/Ui/assets/BlueCon.png


BIN
Ryujinx/Ui/assets/DiscordLogo.png


BIN
Ryujinx/Ui/assets/GitHubLogo.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxIcon.png → Ryujinx/Ui/assets/Icon.png


BIN
Ryujinx/Ui/assets/JoyCon.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxNCAIcon.png → Ryujinx/Ui/assets/NCAIcon.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxNROIcon.png → Ryujinx/Ui/assets/NROIcon.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxNSOIcon.png → Ryujinx/Ui/assets/NSOIcon.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxNSPIcon.png → Ryujinx/Ui/assets/NSPIcon.png


BIN
Ryujinx/Ui/assets/PatreonLogo.png


BIN
Ryujinx/Ui/assets/ProCon.png


BIN
Ryujinx/Ui/assets/RedCon.png


BIN
Ryujinx/Ui/assets/TwitterLogo.png


+ 0 - 0
Ryujinx/Ui/assets/ryujinxXCIIcon.png → Ryujinx/Ui/assets/XCIIcon.png


+ 14 - 3
Ryujinx/_schema.json

@@ -484,17 +484,28 @@
     },
     "game_dirs": {
       "$id": "#/properties/game_dirs",
-      "type": "string list",
+      "type": "array",
       "title": "List of Game Directories",
       "description": "A list of directories containing games to be used to load games into the games list",
       "default": []
     },
     "gui_columns": {
       "$id": "#/properties/gui_columns",
-      "type": "bool list",
+      "type": "array",
       "title": "Used to toggle columns in the GUI",
       "description": "Used to toggle columns in the GUI",
-      "default": [ true, true, true, true, true, true, true, true, true ]
+      "default": {
+        "fav_column": true,
+        "icon_column": true,
+        "app_column": true,
+        "dev_column": true,
+        "version_column": true,
+        "time_played_column": true,
+        "last_played_column": true,
+        "file_ext_column": true,
+        "file_size_column": true,
+        "path_column": true
+      }
     },
     "enable_custom_theme": {
       "$id": "#/properties/enable_custom_theme",

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff