Bladeren bron

Added GUI to Ryujinx (#695)

* Added GUI to Ryujinx

* Updated to use Glade

Also added scrollbar and default dark theme

* Added support for loading icon from .nro files and cleaned up the code a bit

* Added General Settings Menu (read-only for now) and moved some functionality from MainMenu.cs to ApplicationLibrary.cs

* Added custom GUI theme support and changed the defualt theme to one I just wrote

* Added GTK to process path, fixed a bug and minor edits

* some more edits and a bug fix

* general settings menu is now fully functional. also fixed the bug where ryujinx crashes when it trys to load an invalid gamedir

* big rewrite

* aesthetic changes to General Settings menu

* Added Control Settings

one day done feature :P

* minor changes

* 1st wave of changes

* 2nd wave of changes

* 3rd wave of changes

* Cleanup settings ui

* minor edits

* new about window added, still needs styling

* added spin button for new option and tooltips to settings

* Game icons and names are now shown in the games list

* add nuget package which contains gtk dependencies

* requested changes have been changed

* put CreateGameWindow on a new thread and stopped destroying the main menu when a game loads

* fixed bug that allowed a user to attempt to load multiple games at a time which causes a crash

* Added LastPlayed and TimePlayed columns to the game list

* Did some testing and fixed some bugs

Im not happy with one of the fixes so i will do it properly an upcoming commit

* did some more bug testing and fixed another 2 bugs

* caught an exception when ryujinx tries to load non-homebrew as homebrew

* Large changes

Rewrote ApplicationLibrary.cs (added comments too) so any devs reading it wont get eye cancer, also its probably more efficient now. Added 2 new columns (Developer name and application version) to the game list and wrote the logic for it. Ryujinx now loads NRO's TitleName and TitleID from the NACP file instead of the default NPDM. I also killed a lot of bugs

* Moved Files

moved ApplicationLibrary.cs to Ryujinx.HLE as that is a better place for it. Moved contents of GUI folder to Ui folder and changed the namespaces of the gui files from Ryujinx to Ryujinx.Ui

* Added 'Open Ryujinx Folder' button to the file menu and did some small fixes

* New features

* updated nuget package with missing dlls and changed emmauss' requested changes

* fixed some minor issues

* all requested changes marked as resolved have been changed

* gdkchan's requested changes

* fixed an issue with settings window getting chopped on small res

* fixed 2 problems caused by rebase

* changed the default theme

* applied Thog's patch to fix issue on linux

* fixed issue caused by rebase

* added update check button that runs ryujinx-updater

* reads version info from installer and displays it in about menu

* changes completed

* requested changes changed

* fixed issue with default theme

* fixed a bug and completed requested changes

* added more tooltips and changed some text
Xpl0itR 6 jaren geleden
bovenliggende
commit
edafce57be
40 gewijzigde bestanden met toevoegingen van 5425 en 319 verwijderingen
  1. 1 1
      Ryujinx.HLE/FileSystem/SaveHelper.cs
  2. 2 2
      Ryujinx.HLE/FileSystem/SaveInfo.cs
  3. 2 2
      Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
  4. 43 10
      Ryujinx.HLE/HOS/Horizon.cs
  5. 2 2
      Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
  6. 3 3
      Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs
  7. 1 1
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs
  8. 1 1
      Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
  9. 0 2
      Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
  10. 2 2
      Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs
  11. 2 2
      Ryujinx.HLE/Loaders/Npdm/ACI0.cs
  12. 1 1
      Ryujinx.HLE/Loaders/Npdm/Npdm.cs
  13. 9 1
      Ryujinx.sln
  14. 5 68
      Ryujinx/Config.json
  15. 99 52
      Ryujinx/Configuration.cs
  16. 13 163
      Ryujinx/Program.cs
  17. 7 2
      Ryujinx/RPsupported.dat
  18. 24 1
      Ryujinx/Ryujinx.csproj
  19. 672 0
      Ryujinx/Theme.css
  20. 116 0
      Ryujinx/Ui/AboutWindow.cs
  21. 574 0
      Ryujinx/Ui/AboutWindow.glade
  22. 450 0
      Ryujinx/Ui/ApplicationLibrary.cs
  23. 1 1
      Ryujinx/Ui/GLScreen.cs
  24. 601 0
      Ryujinx/Ui/MainWindow.cs
  25. 347 0
      Ryujinx/Ui/MainWindow.glade
  26. 2 2
      Ryujinx/Ui/NpadKeyboard.cs
  27. 424 0
      Ryujinx/Ui/SwitchSettings.cs
  28. 1989 0
      Ryujinx/Ui/SwitchSettings.glade
  29. BIN
      Ryujinx/Ui/assets/DiscordLogo.png
  30. BIN
      Ryujinx/Ui/assets/GitHubLogo.png
  31. BIN
      Ryujinx/Ui/assets/JoyCon.png
  32. BIN
      Ryujinx/Ui/assets/PatreonLogo.png
  33. BIN
      Ryujinx/Ui/assets/TwitterLogo.png
  34. BIN
      Ryujinx/Ui/assets/ryujinxIcon.png
  35. BIN
      Ryujinx/Ui/assets/ryujinxNCAIcon.png
  36. BIN
      Ryujinx/Ui/assets/ryujinxNROIcon.png
  37. BIN
      Ryujinx/Ui/assets/ryujinxNSOIcon.png
  38. BIN
      Ryujinx/Ui/assets/ryujinxNSPIcon.png
  39. BIN
      Ryujinx/Ui/assets/ryujinxXCIIcon.png
  40. 32 0
      Ryujinx/_schema.json

+ 1 - 1
Ryujinx.HLE/FileSystem/SaveHelper.cs

@@ -10,7 +10,7 @@ namespace Ryujinx.HLE.FileSystem
         public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context)
         {
             string baseSavePath   = NandPath;
-            long   currentTitleId = saveMetaData.TitleId;
+            ulong  currentTitleId = saveMetaData.TitleId;
 
             switch (saveMetaData.SaveSpaceId)
             {

+ 2 - 2
Ryujinx.HLE/FileSystem/SaveInfo.cs

@@ -4,7 +4,7 @@ namespace Ryujinx.HLE.FileSystem
 {
     struct SaveInfo
     {
-        public long    TitleId { get; private set; }
+        public ulong   TitleId { get; private set; }
         public long    SaveId  { get; private set; }
         public UInt128 UserId  { get; private set; }
 
@@ -12,7 +12,7 @@ namespace Ryujinx.HLE.FileSystem
         public SaveSpaceId  SaveSpaceId  { get; private set; }
 
         public SaveInfo(
-            long         titleId,
+            ulong        titleId,
             long         saveId,
             SaveDataType saveDataType,
             UInt128      userId,

+ 2 - 2
Ryujinx.HLE/FileSystem/VirtualFileSystem.cs

@@ -5,7 +5,7 @@ using System.IO;
 
 namespace Ryujinx.HLE.FileSystem
 {
-    class VirtualFileSystem : IDisposable
+    public class VirtualFileSystem : IDisposable
     {
         public const string BasePath   = "RyuFs";
         public const string NandPath   = "nand";
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.FileSystem
 
         public string GetSystemPath() => MakeDirAndGetFullPath(SystemPath);
 
-        public string GetGameSavePath(SaveInfo save, ServiceCtx context)
+        internal string GetGameSavePath(SaveInfo save, ServiceCtx context)
         {
             return MakeDirAndGetFullPath(SaveHelper.GetSavePath(save, context));
         }

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

@@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS
 
         internal KEvent VsyncEvent { get; private set; }
 
-        internal Keyset KeySet { get; private set; }
+        public Keyset KeySet { get; private set; }
 
         private bool _hasStarted;
 
@@ -453,9 +453,7 @@ namespace Ryujinx.HLE.HOS
                 Nacp controlData = new Nacp(controlFile.AsStream());
 
                 TitleName = CurrentTitle = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
-                TitleID = metaData.Aci0.TitleId.ToString("x16");
-
-                CurrentTitle = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
+                TitleID   = metaData.Aci0.TitleId.ToString("x16");
 
                 if (string.IsNullOrWhiteSpace(CurrentTitle))
                 {
@@ -551,18 +549,51 @@ namespace Ryujinx.HLE.HOS
                         if (asetVersion == 0)
                         {
                             ulong iconOffset = reader.ReadUInt64();
-                            ulong iconSize = reader.ReadUInt64();
+                            ulong iconSize   = reader.ReadUInt64();
 
                             ulong nacpOffset = reader.ReadUInt64();
-                            ulong nacpSize = reader.ReadUInt64();
+                            ulong nacpSize   = reader.ReadUInt64();
 
                             ulong romfsOffset = reader.ReadUInt64();
-                            ulong romfsSize = reader.ReadUInt64();
+                            ulong romfsSize   = reader.ReadUInt64();
 
                             if (romfsSize != 0)
                             {
                                 Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
                             }
+
+                            if (nacpSize != 0)
+                            {
+                                input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
+                                using (MemoryStream stream = new MemoryStream(reader.ReadBytes((int)nacpSize)))
+                                {
+                                    ControlData = new Nacp(stream);
+                                }
+
+                                metaData.TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
+
+                                if (string.IsNullOrWhiteSpace(metaData.TitleName))
+                                {
+                                    metaData.TitleName = ControlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                                }
+
+                                metaData.Aci0.TitleId = ControlData.PresenceGroupId;
+
+                                if (metaData.Aci0.TitleId == 0)
+                                {
+                                    metaData.Aci0.TitleId = ControlData.SaveDataOwnerId;
+                                }
+
+                                if (metaData.Aci0.TitleId == 0)
+                                {
+                                    metaData.Aci0.TitleId = ControlData.AddOnContentBaseId - 0x1000;
+                                }
+
+                                if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000")
+                                {
+                                    metaData.Aci0.TitleId = 0000000000000000;
+                                }
+                            }
                         }
                         else
                         {
@@ -578,8 +609,8 @@ namespace Ryujinx.HLE.HOS
 
             ContentManager.LoadEntries();
 
-            TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
-            TitleName = metaData.TitleName;
+            TitleName = CurrentTitle = metaData.TitleName;
+            TitleID   = metaData.Aci0.TitleId.ToString("x16");
 
             ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
         }
@@ -687,7 +718,9 @@ namespace Ryujinx.HLE.HOS
                 // It's only safe to release resources once all threads
                 // have exited.
                 ThreadCounter.Signal();
-                ThreadCounter.Wait();
+                //ThreadCounter.Wait(); // FIXME: Uncomment this
+                // BODY: Right now, guest processes don't exit properly because the logic waits for them to exit.
+                // BODY: However, this doesn't happen when you close the main window so we need to find a way to make them exit gracefully
 
                 Scheduler.Dispose();
 

+ 2 - 2
Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs

@@ -60,8 +60,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         public KProcessCapabilities Capabilities { get; private set; }
 
-        public long TitleId { get; private set; }
-        public long Pid     { get; private set; }
+        public ulong TitleId { get; private set; }
+        public long  Pid     { get; private set; }
 
         private long  _creationTimestamp;
         private ulong _entrypoint;

+ 3 - 3
Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs

@@ -4,8 +4,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
     {
         public string Name { get; private set; }
 
-        public int  Category { get; private set; }
-        public long TitleId  { get; private set; }
+        public int   Category { get; private set; }
+        public ulong TitleId  { get; private set; }
 
         public ulong CodeAddress    { get; private set; }
         public int   CodePagesCount { get; private set; }
@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         public ProcessCreationInfo(
             string name,
             int    category,
-            long   titleId,
+            ulong  titleId,
             ulong  codeAddress,
             int    codePagesCount,
             int    mmuFlags,

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs

@@ -285,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
                             break;
 
-                        case 18: value = process.TitleId; break;
+                        case 18: value = (long)process.TitleId; break;
 
                         case 20: value = (long)process.UserExceptionContextAddress; break;
 

+ 1 - 1
Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs

@@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
         {
             SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64();
 
-            long titleId = context.RequestData.ReadInt64();
+            ulong titleId = context.RequestData.ReadUInt64();
 
             UInt128 userId = context.RequestData.ReadStruct<UInt128>();
 

+ 0 - 2
Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs

@@ -40,8 +40,6 @@ namespace Ryujinx.HLE.HOS.SystemState
 
         internal string ActiveAudioOutput { get; private set; }
 
-        public bool DiscordIntegrationEnabled { get; set; }
-
         public bool DockedMode { get; set; }
 
         public ColorSet ThemeColor { get; set; }

+ 2 - 2
Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs

@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Executables
     {
         public string Name { get; private set; }
 
-        public long TitleId { get; private set; }
+        public ulong TitleId { get; private set; }
 
         public int ProcessCategory { get; private set; }
 
@@ -65,7 +65,7 @@ namespace Ryujinx.HLE.Loaders.Executables
 
             Name = ReadString(reader, 12);
 
-            TitleId = reader.ReadInt64();
+            TitleId = reader.ReadUInt64();
 
             ProcessCategory = reader.ReadInt32();
 

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

@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
     {
         private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
 
-        public long TitleId { get; private set; }
+        public ulong TitleId { get; set; }
 
         public int   FsVersion            { get; private set; }
         public ulong FsPermissionsBitmask { get; private set; }
@@ -28,7 +28,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
 
             stream.Seek(0xc, SeekOrigin.Current);
 
-            TitleId = reader.ReadInt64();
+            TitleId = reader.ReadUInt64();
 
             // Reserved.
             stream.Seek(8, SeekOrigin.Current);

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

@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
         public int    PersonalMmHeapSize  { get; private set; }
         public int    ProcessCategory     { get; private set; }
         public int    MainThreadStackSize { get; private set; }
-        public string TitleName           { get; private set; }
+        public string TitleName           { get;         set; }
         public byte[] ProductCode         { get; private set; }
 
         public Aci0 Aci0 { get; private set; }

+ 9 - 1
Ryujinx.sln

@@ -28,7 +28,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.C
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx.Profiler\Ryujinx.Profiler.csproj", "{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -126,6 +126,14 @@ Global
 		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
 		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.Build.0 = Release|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 5 - 68
Ryujinx/Config.jsonc → Ryujinx/Config.json

@@ -1,75 +1,28 @@
 {
-    "$schema": "./_schema.json",
-
-    // Dump shaders in local directory (e.g. `C:\ShaderDumps`)
     "graphics_shaders_dump_path": "",
-
-    // Enable printing debug logs
     "logging_enable_debug": false,
-
-    // Enable printing stubbed calls logs
     "logging_enable_stub": true,
-
-    // Enable printing information logs
     "logging_enable_info": true,
-
-    // Enable printing warning logs
     "logging_enable_warn": true,
-
-    // Enable printing error logs
     "logging_enable_error": true,
-
-    // Enable printing guest logs
     "logging_enable_guest": true,
-
-    // Enable printing FS access logs. fs_global_access_log_mode must be 2 or 3
     "logging_enable_fs_access_log": false,
-
-    // Filtered log classes, in a JSON array, eg. `[ "Loader", "ServiceFs" ]`
     "logging_filtered_classes": [ ],
-
-    // Enable file logging
     "enable_file_log": true,
-
-    // Change System Language
-    // System Language list: https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b
     "system_language": "AmericanEnglish",
-
-    // Enable or disable Docked Mode
     "docked_mode": false,
-
-    // Enable or disable Discord Rich Presence
     "enable_discord_integration": true,
-
-    // Enable or disable Game Vsync
     "enable_vsync": true,
-
-    // Enable or disable Multi-core scheduling of threads
     "enable_multicore_scheduling": true,
-
-    // Enable integrity checks on Switch content files
     "enable_fs_integrity_checks": true,
-
-    // Sets the "GlobalAccessLogMode". Possible modes are 0-3
-    "fs_global_access_log_mode": 0,
-
-    // Use old ChocolArm64 ARM emulator
     "enable_legacy_jit": false,
-
-    // Enable or disable ignoring missing services, this may cause instability
     "ignore_missing_services": false,
-
-    // The primary controller's type
-    // Supported Values: Handheld, ProController, NpadPair, NpadLeft, NpadRight
     "controller_type": "Handheld",
-
-    // Enable or disable "direct keyboard access (HID) support" (Provides games access to your keyboard as a text entry device).
-    "enable_keyboard": false,
-
-    // Keyboard Controls
-    // https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/Key.cs
+    "gui_columns": [ true, true, true, true, true, true, true, true, true ],
+    "game_dirs": [],
+    "enable_custom_theme": false,
+    "custom_theme_path": "",
     "keyboard_controls": {
-        // Left JoyCon Keyboard Bindings
         "left_joycon": {
             "stick_up": "W",
             "stick_down": "S",
@@ -84,8 +37,6 @@
             "button_l": "E",
             "button_zl": "Q"
         },
-
-        // Right JoyCon Keyboard Bindings
         "right_joycon": {
             "stick_up": "I",
             "stick_down": "K",
@@ -100,27 +51,15 @@
             "button_r": "U",
             "button_zr": "O"
         },
-
         "hotkeys": {
             "toggle_vsync": "Tab"
         }
     },
-
-    // Controller Controls
-    "joystick_controls": {
-        // Whether or not to enable Controller support
+        "joystick_controls": {
         "enabled": true,
-
-        // Controller Device Index
         "index": 0,
-
-        // Controller Analog Stick Deadzone
         "deadzone": 0.05,
-
-        // The value of how pressed down each trigger has to be in order to register a button press
         "trigger_threshold": 0.5,
-
-        // Left JoyCon Controller Bindings
         "left_joycon": {
             "stick": "Axis0",
             "stick_button": "Button13",
@@ -132,8 +71,6 @@
             "button_l": "Button6",
             "button_zl": "Button8"
         },
-
-        // Right JoyCon Controller Bindings
         "right_joycon": {
             "stick": "Axis2",
             "stick_button": "Button14",

+ 99 - 52
Ryujinx/Configuration.cs

@@ -1,4 +1,4 @@
-using ARMeilleure;
+using JsonPrettyPrinterPlus;
 using LibHac.Fs;
 using OpenTK.Input;
 using Ryujinx.Common;
@@ -7,9 +7,12 @@ 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 System;
+using System.Collections.Generic;
 using System.IO;
+using System.Text;
 using System.Threading.Tasks;
 using Utf8Json;
 using Utf8Json.Resolvers;
@@ -26,112 +29,132 @@ namespace Ryujinx
         /// <summary>
         /// Dumps shaders in this local directory
         /// </summary>
-        public string GraphicsShadersDumpPath { get; private set; }
+        public string GraphicsShadersDumpPath { get; set; }
 
         /// <summary>
         /// Enables printing debug log messages
         /// </summary>
-        public bool LoggingEnableDebug { get; private set; }
+        public bool LoggingEnableDebug { get; set; }
 
         /// <summary>
         /// Enables printing stub log messages
         /// </summary>
-        public bool LoggingEnableStub { get; private set; }
+        public bool LoggingEnableStub { get; set; }
 
         /// <summary>
         /// Enables printing info log messages
         /// </summary>
-        public bool LoggingEnableInfo { get; private set; }
+        public bool LoggingEnableInfo { get; set; }
 
         /// <summary>
         /// Enables printing warning log messages
         /// </summary>
-        public bool LoggingEnableWarn { get; private set; }
+        public bool LoggingEnableWarn { get; set; }
 
         /// <summary>
         /// Enables printing error log messages
         /// </summary>
-        public bool LoggingEnableError { get; private set; }
+        public bool LoggingEnableError { get; set; }
 
         /// <summary>
         /// Enables printing guest log messages
         /// </summary>
-        public bool LoggingEnableGuest { get; private set; }
+        public bool LoggingEnableGuest { get; set; }
 
         /// <summary>
         /// Enables printing FS access log messages
         /// </summary>
-        public bool LoggingEnableFsAccessLog { get; private set; }
+        public bool LoggingEnableFsAccessLog { get; set; }
 
         /// <summary>
         /// Controls which log messages are written to the log targets
         /// </summary>
-        public LogClass[] LoggingFilteredClasses { get; private set; }
+        public LogClass[] LoggingFilteredClasses { get; set; }
 
         /// <summary>
         /// Enables or disables logging to a file on disk
         /// </summary>
-        public bool EnableFileLog { get; private set; }
+        public bool EnableFileLog { get; set; }
 
         /// <summary>
         /// Change System Language
         /// </summary>
-        public SystemLanguage SystemLanguage { get; private set; }
+        public SystemLanguage SystemLanguage { get; set; }
 
         /// <summary>
         /// Enables or disables Docked Mode
         /// </summary>
-        public bool DockedMode { get; private set; }
+        public bool DockedMode { get; set; }
 
         /// <summary>
         /// Enables or disables Discord Rich Presence
         /// </summary>
-        public bool EnableDiscordIntegration { get; private set; }
+        public bool EnableDiscordIntegration { get; set; }
 
         /// <summary>
         /// Enables or disables Vertical Sync
         /// </summary>
-        public bool EnableVsync { get; private set; }
+        public bool EnableVsync { get; set; }
 
         /// <summary>
         /// Enables or disables multi-core scheduling of threads
         /// </summary>
-        public bool EnableMulticoreScheduling { get; private set; }
+        public bool EnableMulticoreScheduling { get; set; }
 
         /// <summary>
         /// Enables integrity checks on Game content files
         /// </summary>
-        public bool EnableFsIntegrityChecks { get; private set; }
+        public bool EnableFsIntegrityChecks { get; set; }
 
         /// <summary>
         /// Enables FS access log output to the console. Possible modes are 0-3
         /// </summary>
-        public int FsGlobalAccessLogMode { get; private set; }
+        public int FsGlobalAccessLogMode { get; set; }
 
         /// <summary>
         /// Use old ChocolArm64 ARM emulator
         /// </summary>
-        public bool EnableLegacyJit { get; private set; }
+        public bool EnableLegacyJit { get;  set; }
 
         /// <summary>
         /// Enable or disable ignoring missing services
         /// </summary>
-        public bool IgnoreMissingServices { get; private set; }
+        public bool IgnoreMissingServices { get; set; }
 
         /// <summary>
         ///  The primary controller's type
         /// </summary>
-        public ControllerStatus ControllerType { get; private set; }
+        public ControllerStatus ControllerType { get; set; }
+
+        /// <summary>
+        /// Used to toggle columns in the GUI
+        /// </summary>
+        public List<bool> GuiColumns { get; set; }
+
+        /// <summary>
+        /// A list of directories containing games to be used to load games into the games list
+        /// </summary>
+        public List<string> GameDirs { get; set; }
+
+        /// <summary>
+        /// Enable or disable custom themes in the GUI
+        /// </summary>
+        public bool EnableCustomTheme { get; set; }
+
+        /// <summary>
+        /// Path to custom GUI theme
+        /// </summary>
+        public string CustomThemePath { get; set; }
 
         /// <summary>
         /// Enable or disable keyboard support (Independent from controllers binding)
         /// </summary>
-        public bool EnableKeyboard { get; private set; }
+        public bool EnableKeyboard { get; set; }
 
         /// <summary>
         /// Keyboard control bindings
         /// </summary>
-        public NpadKeyboard KeyboardControls { get; private set; }
+        public NpadKeyboard KeyboardControls { get; set; }
 
         /// <summary>
         /// Controller control bindings
@@ -161,8 +184,8 @@ namespace Ryujinx
         /// <param name="path">The path to the JSON configuration file</param>
         public static async Task LoadAsync(string path)
         {
-            var resolver = CompositeResolver.Create(
-                new[] { new ConfigurationEnumFormatter<Key>() },
+            IJsonFormatterResolver resolver = CompositeResolver.Create(
+                new[] { new ConfigurationEnumFormatter<Key>()  },
                 new[] { StandardResolver.AllowPrivateSnakeCase }
             );
 
@@ -172,18 +195,33 @@ namespace Ryujinx
             }
         }
 
+        /// <summary>
+        /// Save a configuration file to disk
+        /// </summary>
+        /// <param name="path">The path to the JSON configuration file</param>
+        public static void SaveConfig(Configuration config, string path)
+        {
+            IJsonFormatterResolver resolver = CompositeResolver.Create(
+                new[] { new ConfigurationEnumFormatter<Key>()  },
+                new[] { StandardResolver.AllowPrivateSnakeCase }
+            );
+
+            byte[] data = JsonSerializer.Serialize(config, resolver);
+            File.WriteAllText(path, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
+        }
+
         /// <summary>
         /// Configures a <see cref="Switch"/> instance
         /// </summary>
         /// <param name="device">The instance to configure</param>
-        public static void Configure(Switch device)
+        public static void InitialConfigure(Switch device)
         {
             if (Instance == null)
             {
                 throw new InvalidOperationException("Configuration has not been loaded yet.");
             }
 
-            GraphicsConfig.ShadersDumpPath = Instance.GraphicsShadersDumpPath;
+            SwitchSettings.ConfigureSettings(Instance);
 
             Logger.AddTarget(new AsyncLogTargetWrapper(
                 new ConsoleLogTarget(),
@@ -194,65 +232,74 @@ namespace Ryujinx
             if (Instance.EnableFileLog)
             {
                 Logger.AddTarget(new AsyncLogTargetWrapper(
-                    new FileLogTarget(Path.Combine(Program.ApplicationDirectory, "Ryujinx.log")),
+                    new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log")),
                     1000,
                     AsyncLogTargetOverflowAction.Block
                 ));
             }
 
-            Logger.SetEnable(LogLevel.Debug,     Instance.LoggingEnableDebug);
-            Logger.SetEnable(LogLevel.Stub,      Instance.LoggingEnableStub);
-            Logger.SetEnable(LogLevel.Info,      Instance.LoggingEnableInfo);
-            Logger.SetEnable(LogLevel.Warning,   Instance.LoggingEnableWarn);
-            Logger.SetEnable(LogLevel.Error,     Instance.LoggingEnableError);
-            Logger.SetEnable(LogLevel.Guest,     Instance.LoggingEnableGuest);
-            Logger.SetEnable(LogLevel.AccessLog, Instance.LoggingEnableFsAccessLog);
+            Configure(device, Instance);
+        }
+
+        public static void Configure(Switch device, Configuration SwitchConfig)
+        {
+            GraphicsConfig.ShadersDumpPath = SwitchConfig.GraphicsShadersDumpPath;
+
+            Logger.SetEnable(LogLevel.Debug,     SwitchConfig.LoggingEnableDebug      );
+            Logger.SetEnable(LogLevel.Stub,      SwitchConfig.LoggingEnableStub       );
+            Logger.SetEnable(LogLevel.Info,      SwitchConfig.LoggingEnableInfo       );
+            Logger.SetEnable(LogLevel.Warning,   SwitchConfig.LoggingEnableWarn       );
+            Logger.SetEnable(LogLevel.Error,     SwitchConfig.LoggingEnableError      );
+            Logger.SetEnable(LogLevel.Guest,     SwitchConfig.LoggingEnableGuest      );
+            Logger.SetEnable(LogLevel.AccessLog, SwitchConfig.LoggingEnableFsAccessLog);
 
-            if (Instance.LoggingFilteredClasses.Length > 0)
+            if (SwitchConfig.LoggingFilteredClasses.Length > 0)
             {
                 foreach (var logClass in EnumExtensions.GetValues<LogClass>())
                 {
                     Logger.SetEnable(logClass, false);
                 }
 
-                foreach (var logClass in Instance.LoggingFilteredClasses)
+                foreach (var logClass in SwitchConfig.LoggingFilteredClasses)
                 {
                     Logger.SetEnable(logClass, true);
                 }
             }
 
-            device.System.State.DiscordIntegrationEnabled = Instance.EnableDiscordIntegration;
+            MainWindow.DiscordIntegrationEnabled = SwitchConfig.EnableDiscordIntegration;
 
-            device.EnableDeviceVsync = Instance.EnableVsync;
+            device.EnableDeviceVsync = SwitchConfig.EnableVsync;
 
-            device.System.State.DockedMode = Instance.DockedMode;
+            device.System.State.DockedMode = SwitchConfig.DockedMode;
 
-            device.System.State.SetLanguage(Instance.SystemLanguage);
+            device.System.State.SetLanguage(SwitchConfig.SystemLanguage);
 
-            if (Instance.EnableMulticoreScheduling)
+            if (SwitchConfig.EnableMulticoreScheduling)
             {
                 device.System.EnableMultiCoreScheduling();
             }
 
-            device.System.FsIntegrityCheckLevel = Instance.EnableFsIntegrityChecks
+            device.System.FsIntegrityCheckLevel = SwitchConfig.EnableFsIntegrityChecks
                 ? IntegrityCheckLevel.ErrorOnInvalid
                 : IntegrityCheckLevel.None;
 
-            device.System.GlobalAccessLogMode = Instance.FsGlobalAccessLogMode;
+            device.System.GlobalAccessLogMode = SwitchConfig.FsGlobalAccessLogMode;
 
-            device.System.UseLegacyJit = Instance.EnableLegacyJit;
+            device.System.UseLegacyJit = SwitchConfig.EnableLegacyJit;
 
-            ServiceConfiguration.IgnoreMissingServices = Instance.IgnoreMissingServices;
-
-            if (Instance.JoystickControls.Enabled)
+            ServiceConfiguration.IgnoreMissingServices = SwitchConfig.IgnoreMissingServices;
+        }
+       
+        public static void ConfigureHid(Switch device, Configuration SwitchConfig) 
+        {   
+            if (SwitchConfig.JoystickControls.Enabled)
             {
-                if (!Joystick.GetState(Instance.JoystickControls.Index).IsConnected)
+                if (!Joystick.GetState(SwitchConfig.JoystickControls.Index).IsConnected)
                 {
-                    Instance.JoystickControls.SetEnabled(false);
+                    SwitchConfig.JoystickControls.SetEnabled(false);
                 }
             }
-
-            device.Hid.InitializePrimaryController(Instance.ControllerType);
+            device.Hid.InitializePrimaryController(SwitchConfig.ControllerType);
             device.Hid.InitializeKeyboard();
         }
 

+ 13 - 163
Ryujinx/Program.cs

@@ -1,169 +1,41 @@
-using DiscordRPC;
-using Ryujinx.Audio;
+using Gtk;
 using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gal;
-using Ryujinx.Graphics.Gal.OpenGL;
-using Ryujinx.HLE;
 using Ryujinx.Profiler;
+using Ryujinx.UI;
 using System;
 using System.IO;
-using System.Linq;
 
 namespace Ryujinx
 {
     class Program
     {
-        public static DiscordRpcClient DiscordClient;
-
-        public static RichPresence DiscordPresence;
-
-        public static string ApplicationDirectory => AppDomain.CurrentDomain.BaseDirectory;
-
         static void Main(string[] args)
         {
             Console.Title = "Ryujinx Console";
 
-            IGalRenderer renderer = new OglRenderer();
-
-            IAalOutput audioOut = InitializeAudioEngine();
-
-            Switch device = new Switch(renderer, audioOut);
-
-            Configuration.Load(Path.Combine(ApplicationDirectory, "Config.jsonc"));
-            Configuration.Configure(device);
-
-            Profile.Initialize();
+            string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
+            Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
 
             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
             AppDomain.CurrentDomain.ProcessExit        += CurrentDomain_ProcessExit;
 
-            if (device.System.State.DiscordIntegrationEnabled)
-            {
-                DiscordClient   = new DiscordRpcClient("568815339807309834");
-                DiscordPresence = new RichPresence
-                {
-                    Assets = new Assets
-                    {
-                        LargeImageKey  = "ryujinx",
-                        LargeImageText = "Ryujinx is an emulator for the Nintendo Switch"
-                    }
-                };
-
-                DiscordClient.Initialize();
-                DiscordClient.SetPresence(DiscordPresence);
-            }
-
-            if (args.Length == 1)
-            {
-                if (Directory.Exists(args[0]))
-                {
-                    string[] romFsFiles = Directory.GetFiles(args[0], "*.istorage");
-
-                    if (romFsFiles.Length == 0)
-                    {
-                        romFsFiles = Directory.GetFiles(args[0], "*.romfs");
-                    }
-
-                    if (romFsFiles.Length > 0)
-                    {
-                        Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS.");
-                        device.LoadCart(args[0], romFsFiles[0]);
-                    }
-                    else
-                    {
-                        Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS.");
-                        device.LoadCart(args[0]);
-                    }
-                }
-                else if (File.Exists(args[0]))
-                {
-                    switch (Path.GetExtension(args[0]).ToLowerInvariant())
-                    {
-                        case ".xci":
-                            Logger.PrintInfo(LogClass.Application, "Loading as XCI.");
-                            device.LoadXci(args[0]);
-                            break;
-                        case ".nca":
-                            Logger.PrintInfo(LogClass.Application, "Loading as NCA.");
-                            device.LoadNca(args[0]);
-                            break;
-                        case ".nsp":
-                        case ".pfs0":
-                            Logger.PrintInfo(LogClass.Application, "Loading as NSP.");
-                            device.LoadNsp(args[0]);
-                            break;
-                        default:
-                            Logger.PrintInfo(LogClass.Application, "Loading as homebrew.");
-                            device.LoadProgram(args[0]);
-                            break;
-                    }
-                }
-                else
-                {
-                    Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file");
-                }
-            }
-            else
-            {
-                Logger.PrintWarning(LogClass.Application, "Please specify the folder with the NSOs/IStorage or a NSO/NRO.");
-            }
-
-            if (device.System.State.DiscordIntegrationEnabled)
-            {
-                if (File.ReadAllLines(Path.Combine(ApplicationDirectory, "RPsupported.dat")).Contains(device.System.TitleID))
-                {
-                    DiscordPresence.Assets.LargeImageKey = device.System.TitleID;
-                }
-
-                string state = device.System.TitleID;
-
-                if (state == null)
-                {
-                    state = "Ryujinx";
-                }
-                else
-                {
-                    state = state.ToUpper();
-                }
-
-                string details = "Idling";
-
-                if (device.System.TitleName != null)
-                {
-                    details = $"Playing {device.System.TitleName}";
-                }
-
-                DiscordPresence.Details               = details;
-                DiscordPresence.State                 = state;
-                DiscordPresence.Assets.LargeImageText = device.System.TitleName;
-                DiscordPresence.Assets.SmallImageKey  = "ryujinx";
-                DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch";
-                DiscordPresence.Timestamps            = new Timestamps(DateTime.UtcNow);
-
-                DiscordClient.SetPresence(DiscordPresence);
-            }
-
-            using (GlScreen screen = new GlScreen(device, renderer))
-            {
-                screen.MainLoop();
+            Profile.Initialize();
 
-                Profile.FinishProfiling();
+            Application.Init();
 
-                device.Dispose();
-            }
+            Application gtkApplication = new Application("Ryujinx.Ryujinx", GLib.ApplicationFlags.None);
+            MainWindow  mainWindow     = new MainWindow(args, gtkApplication);
 
-            audioOut.Dispose();
-
-            Logger.Shutdown();
+            gtkApplication.Register(GLib.Cancellable.Current);
+            gtkApplication.AddWindow(mainWindow);
+            mainWindow.Show();
 
-            DiscordClient.Dispose();
+            Application.Run();
         }
 
         private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
         {
             Logger.Shutdown();
-
-            DiscordClient.Dispose();
         }
 
         private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
@@ -175,29 +47,7 @@ namespace Ryujinx
             if (e.IsTerminating)
             {
                 Logger.Shutdown();
-
-                DiscordClient.Dispose();
-            }
-        }
-
-        /// <summary>
-        /// Picks an <see cref="IAalOutput"/> audio output renderer supported on this machine
-        /// </summary>
-        /// <returns>An <see cref="IAalOutput"/> supported by this machine</returns>
-        private static IAalOutput InitializeAudioEngine()
-        {
-            if (SoundIoAudioOut.IsSupported)
-            {
-                return new SoundIoAudioOut();
-            }
-            else if (OpenALAudioOut.IsSupported)
-            {
-                return new OpenALAudioOut();
-            }
-            else
-            {
-                return new DummyAudioOut();
             }
         }
     }
-}
+}

+ 7 - 2
Ryujinx/RPsupported.dat

@@ -2,10 +2,10 @@
 01000d700be88000
 01000dc007e90000
 01000e2003fa0000
-01002fc00c6d0000
 0100225000fee000
 010028d0045ce000
 01002b30028f6000
+01002fc00c6d0000
 010034e005c9c000
 01004f8006a78000
 010051f00ac5e000
@@ -15,12 +15,14 @@
 010065500b218000
 010068f00aa78000
 01006a800016e000
+010072800cbe8000
 01007330027ee000
 0100749009844000
 01007a4008486000
 010080b00ad66000
 010094e00b52e000
 01009aa000faa000
+01009b90006dc000
 0100a4200a284000
 0100a5c00d162000
 0100ae000aebc000
@@ -31,8 +33,11 @@
 0100d6b00cd88000
 0100d870045b6000
 0100e0c00adac000
+0100e46006708000
 0100e7200b272000
 0100e9f00b882000
 0100eab00605c000
 0100efd00a4fa000
-0100f6a00a684000
+0100f6a00a684000
+0100f9f00c696000
+051337133769a000

+ 24 - 1
Ryujinx/Ryujinx.csproj

@@ -18,8 +18,28 @@
     <Optimize>false</Optimize>
   </PropertyGroup>
 
+  <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\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\TwitterLogo.png" />
+    <EmbeddedResource Include="Ui\MainWindow.glade" />
+    <EmbeddedResource Include="Ui\SwitchSettings.glade" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="DiscordRichPresence" Version="1.0.108" />
+    <PackageReference Include="GtkSharp" Version="3.22.24.37" />
+    <PackageReference Include="GtkSharp.Dependencies" Version="1.0.1" />
+    <PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
     <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
   </ItemGroup>
 
@@ -33,7 +53,10 @@
   </ItemGroup>
 
   <ItemGroup>
-    <None Update="Config.jsonc">
+    <None Update="Config.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Theme.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Update="RPsupported.dat">

File diff suppressed because it is too large
+ 672 - 0
Ryujinx/Theme.css


+ 116 - 0
Ryujinx/Ui/AboutWindow.cs

@@ -0,0 +1,116 @@
+using Gtk;
+using GUI = Gtk.Builder.ObjectAttribute;
+using System;
+using System.Diagnostics;
+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;
+    }
+
+    public class AboutWindow : Window
+    {
+        public static Info Information { get; private set; }
+
+#pragma warning disable 649
+        [GUI] Window _aboutWin;
+        [GUI] Label  _versionText;
+        [GUI] Image  _ryujinxLogo;
+        [GUI] Image  _patreonLogo;
+        [GUI] Image  _gitHubLogo;
+        [GUI] Image  _discordLogo;
+        [GUI] Image  _twitterLogo;
+#pragma warning restore 649
+
+        public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { }
+
+        private AboutWindow(Builder builder) : base(builder.GetObject("_aboutWin").Handle)
+        {
+            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);
+            _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 );
+            _twitterLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.TwitterLogo.png", 30 , 30 );
+
+            try
+            {
+                IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
+
+                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);
+                }
+
+                _versionText.Text = $"Version {Information.InstallVersion} - {Information.InstallBranch} ({Information.InstallCommit})";
+            }
+            catch
+            {
+                _versionText.Text = "Unknown Version";
+            }
+        }
+
+        public void OpenUrl(string url)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                Process.Start("xdg-open", url);
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                Process.Start("open", url);
+            }
+        }
+
+        //Events
+        private void RyujinxButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://ryujinx.org");
+        }
+
+        private void PatreonButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://www.patreon.com/ryujinx");
+        }
+
+        private void GitHubButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://github.com/Ryujinx/Ryujinx");
+        }
+
+        private void DiscordButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://discordapp.com/invite/N2FmfVc");
+        }
+
+        private void TwitterButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://twitter.com/RyujinxEmu");
+        }
+
+        private void ContributersButton_Pressed(object obj, ButtonPressEventArgs args)
+        {
+            OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
+        }
+
+        private void CloseToggle_Activated(object obj, EventArgs args)
+        {
+            Destroy();
+        }
+    }
+}

+ 574 - 0
Ryujinx/Ui/AboutWindow.glade

@@ -0,0 +1,574 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkDialog" id="_aboutWin">
+    <property name="can_focus">False</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="window_position">center</property>
+    <property name="default_width">800</property>
+    <property name="default_height">350</property>
+    <property name="type_hint">dialog</property>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="bigBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkBox" id="leftBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">10</property>
+                <property name="margin_right">15</property>
+                <property name="margin_top">10</property>
+                <property name="margin_bottom">15</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="valign">start</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkImage" id="_ryujinxLogo">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">10</property>
+                            <property name="margin_right">10</property>
+                            <property name="margin_top">10</property>
+                            <property name="margin_bottom">10</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">center</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Ryujinx</property>
+                                <property name="justify">center</property>
+                                <attributes>
+                                  <attribute name="scale" value="2.7000000000000002"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">(REE-YOU-JI-NX)</property>
+                                <property name="justify">center</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkEventBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <signal name="button-press-event" handler="RyujinxButton_Pressed" swapped="no"/>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="tooltip_text" translatable="yes">Click to open the Ryujinx website in your default browser</property>
+                                    <property name="label" translatable="yes">www.ryujinx.org</property>
+                                    <property name="justify">center</property>
+                                    <attributes>
+                                      <attribute name="underline" value="True"/>
+                                    </attributes>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="padding">5</property>
+                                <property name="position">2</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</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="GtkLabel" id="_versionText">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Version x.x.x (Commit Number)</property>
+                        <property name="justify">center</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="padding">2</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Unlicenced</property>
+                        <property name="justify">center</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="padding">5</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
+or any of its partners, in any way</property>
+                        <property name="justify">center</property>
+                        <attributes>
+                          <attribute name="scale" value="0.80000000000000004"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="padding">5</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_top">25</property>
+                    <child>
+                      <object class="GtkEventBox" id="PatreonButton">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx Patreon page in your default browser</property>
+                        <signal name="button-press-event" handler="PatreonButton_Pressed" swapped="no"/>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage" id="_patreonLogo">
+                                <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">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Patreon</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </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="GtkEventBox" id="GitHubButton">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx GitHub page in your default browser</property>
+                        <signal name="button-press-event" handler="GitHubButton_Pressed" swapped="no"/>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage" id="_gitHubLogo">
+                                <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">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">GitHub</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEventBox" id="DiscordButton">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Click to open an invite to the Ryujinx Discord server in your default browser</property>
+                        <signal name="button-press-event" handler="DiscordButton_Pressed" swapped="no"/>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage" id="_discordLogo">
+                                <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">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Discord</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEventBox" id="TwitterButton">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx Twitter page in your default browser</property>
+                        <signal name="button-press-event" handler="TwitterButton_Pressed" swapped="no"/>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage" id="_twitterLogo">
+                                <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">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Twitter</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparator">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">10</property>
+                <property name="margin_bottom">10</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="rightBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">15</property>
+                <property name="margin_right">10</property>
+                <property name="margin_top">40</property>
+                <property name="margin_bottom">15</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">About</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="margin_left">10</property>
+                    <property name="label" translatable="yes">Ryujinx is an emulator for the Nintendo Switch.
+Please support us on Patreon.
+Get all the latest news on our Twitter or Discord.
+Developers interested in contributing can find out more on our Discord.</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">5</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Created By:</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">5</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">10</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkScrolledWindow">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkViewport">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="baseline_position">top</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="halign">start</property>
+                                    <property name="label" translatable="yes">gdkchan
+LDj3SNuD
+Ac_K
+Thog</property>
+                                    <property name="yalign">0</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="halign">start</property>
+                                    <property name="label" translatable="yes">»jD«
+emmaus
+Thealexbarney
+Andy A (BaronKiko)</property>
+                                    <property name="yalign">0</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                            </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="GtkEventBox" id="ContributersButton">
+                        <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"/>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">end</property>
+                            <property name="margin_right">5</property>
+                            <property name="label" translatable="yes">All Contributors...</property>
+                            <attributes>
+                              <attribute name="underline" value="True"/>
+                            </attributes>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

+ 450 - 0
Ryujinx/Ui/ApplicationLibrary.cs

@@ -0,0 +1,450 @@
+using LibHac;
+using LibHac.Fs;
+using LibHac.Fs.NcaUtils;
+using Ryujinx.Common.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using SystemState = Ryujinx.HLE.HOS.SystemState;
+
+namespace Ryujinx.UI
+{
+    public class ApplicationLibrary
+    {
+        private static Keyset KeySet;
+        private static SystemState.TitleLanguage DesiredTitleLanguage;
+
+        private const double SecondsPerMinute = 60.0;
+        private const double SecondsPerHour   = SecondsPerMinute * 60;
+        private const double SecondsPerDay    = SecondsPerHour   * 24;
+
+        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; }
+
+        public static List<ApplicationData> ApplicationLibraryData { get; private set; }
+
+        public struct ApplicationData
+        {
+            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;
+        }
+
+        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");
+
+            // Builds the applications list with paths to found applications
+            List<string> applications = new List<string>();
+            foreach (string appDir in AppDirs)
+            {
+                if (Directory.Exists(appDir) == false)
+                {
+                    Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
+
+                    continue;
+                }
+
+                DirectoryInfo AppDirInfo = new DirectoryInfo(appDir);
+                foreach (FileInfo App in AppDirInfo.GetFiles())
+                {
+                    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"))
+                    {
+                        applications.Add(App.ToString());
+                    }
+                }
+            }
+
+            // Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
+            ApplicationLibraryData = new List<ApplicationData>();
+            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;
+                byte[] applicationIcon = null;
+
+                using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
+                {
+                    if ((Path.GetExtension(applicationPath) == ".nsp")  ||
+                        (Path.GetExtension(applicationPath) == ".pfs0") ||
+                        (Path.GetExtension(applicationPath) == ".xci"))
+                    {
+                        try
+                        {
+                            IFileSystem controlFs = null;
+
+                            // Store the ControlFS in variable called controlFs
+                            if (Path.GetExtension(applicationPath) == ".xci")
+                            {
+                                Xci xci = new Xci(KeySet, file.AsStorage());
+
+                                controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
+                            }
+                            else
+                            {
+                                controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
+                            }
+
+                            // Creates NACP class from the NACP file
+                            IFile controlNacp = controlFs.OpenFile("/control.nacp", OpenMode.Read);
+                            Nacp  controlData = new Nacp(controlNacp.AsStream());
+
+                            // Get the title name, title ID, developer name and version number from the NACP
+                            version = controlData.DisplayVersion;
+
+                            titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
+
+                            if (string.IsNullOrWhiteSpace(titleName))
+                            {
+                                titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                            }
+
+                            titleId = controlData.PresenceGroupId.ToString("x16");
+
+                            if (string.IsNullOrWhiteSpace(titleId))
+                            {
+                                titleId = controlData.SaveDataOwnerId.ToString("x16");
+                            }
+
+                            if (string.IsNullOrWhiteSpace(titleId))
+                            {
+                                titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
+                            }
+
+                            developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
+
+                            if (string.IsNullOrWhiteSpace(developer))
+                            {
+                                developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
+                            }
+
+                            // Read the icon from the ControlFS and store it as a byte array
+                            try
+                            {
+                                IFile icon = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read);
+                                using (MemoryStream stream = new MemoryStream())
+                                {
+                                    icon.AsStream().CopyTo(stream);
+                                    applicationIcon = stream.ToArray();
+                                }
+                            }
+                            catch (HorizonResultException)
+                            {
+                                IDirectory controlDir = controlFs.OpenDirectory("./", OpenDirectoryMode.All);
+                                foreach (DirectoryEntry entry in controlDir.Read())
+                                {
+                                    if (entry.Name == "control.nacp")
+                                    {
+                                        continue;
+                                    }
+
+                                    IFile icon = controlFs.OpenFile(entry.FullPath, OpenMode.Read);
+                                    using (MemoryStream stream = new MemoryStream())
+                                    {
+                                        icon.AsStream().CopyTo(stream);
+                                        applicationIcon = stream.ToArray();
+                                    }
+
+                                    if (applicationIcon != null)
+                                    {
+                                        break;
+                                    }
+                                }
+
+                                if (applicationIcon == null)
+                                {
+                                    applicationIcon = NspOrXciIcon(applicationPath);
+                                }
+                            }
+                        }
+                        catch (MissingKeyException exception)
+                        {
+                            titleName       = "Unknown";
+                            titleId         = "Unknown";
+                            developer       = "Unknown";
+                            version         = "?";
+                            applicationIcon = NspOrXciIcon(applicationPath);
+
+                            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);
+
+                            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;
+                        }
+                    }
+                    else if (Path.GetExtension(applicationPath) == ".nro")
+                    {
+                        BinaryReader reader = new BinaryReader(file);
+
+                        byte[] Read(long Position, int Size)
+                        {
+                            file.Seek(Position, SeekOrigin.Begin);
+
+                            return reader.ReadBytes(Size);
+                        }
+
+                        file.Seek(24, SeekOrigin.Begin);
+                        int AssetOffset = reader.ReadInt32();
+
+                        if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
+                        {
+                            byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
+
+                            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);
+
+                            // Creates memory stream out of byte array which is the NACP
+                            using (MemoryStream stream = new MemoryStream(Read(AssetOffset + (int)nacpOffset, (int)nacpSize)))
+                            {
+                                // Creates NACP class from the memory stream
+                                Nacp controlData = new Nacp(stream);
+
+                                // Get the title name, title ID, developer name and version number from the NACP
+                                version = controlData.DisplayVersion;
+
+                                titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
+
+                                if (string.IsNullOrWhiteSpace(titleName))
+                                {
+                                    titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
+                                }
+
+                                titleId = controlData.PresenceGroupId.ToString("x16");
+
+                                if (string.IsNullOrWhiteSpace(titleId))
+                                {
+                                    titleId = controlData.SaveDataOwnerId.ToString("x16");
+                                }
+
+                                if (string.IsNullOrWhiteSpace(titleId))
+                                {
+                                    titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
+                                }
+
+                                developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
+
+                                if (string.IsNullOrWhiteSpace(developer))
+                                {
+                                    developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            applicationIcon = RyujinxNroIcon;
+                            titleName       = "Application";
+                            titleId         = "0000000000000000";
+                            developer       = "Unknown";
+                            version         = "?";
+                        }
+                    }
+                    // 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";
+                    }
+                }
+
+                string[] playedData = GetPlayedData(titleId, "00000000000000000000000000000001");
+
+                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,
+                };
+
+                ApplicationLibraryData.Add(data);
+            }
+        }
+
+        private static byte[] GetResourceBytes(string resourceName)
+        {
+            Stream resourceStream    = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
+            byte[] resourceByteArray = new byte[resourceStream.Length];
+
+            resourceStream.Read(resourceByteArray);
+
+            return resourceByteArray;
+        }
+
+        private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
+        {
+            Nca controlNca = null;
+
+            // Add keys to keyset if needed
+            foreach (DirectoryEntry ticketEntry in Pfs.EnumerateEntries("*.tik"))
+            {
+                Ticket ticket = new Ticket(Pfs.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream());
+
+                if (!KeySet.TitleKeys.ContainsKey(ticket.RightsId))
+                {
+                    KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(KeySet));
+                }
+            }
+
+            // Find the Control NCA and store it in variable called controlNca
+            foreach (DirectoryEntry fileEntry in Pfs.EnumerateEntries("*.nca"))
+            {
+                Nca nca = new Nca(KeySet, Pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
+                if (nca.Header.ContentType == ContentType.Control)
+                {
+                    controlNca = nca;
+                }
+            }
+
+            // Return the ControlFS
+            return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+        }
+
+        private static string[] GetPlayedData(string TitleId, string UserId)
+        {
+            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());
+
+                        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";
+                        }
+                    }
+                }
+
+                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"));
+                    }
+                }
+
+                using (FileStream fs = File.OpenRead(Path.Combine(savePath, "LastPlayed.dat")))
+                {
+                    using (StreamReader sr = new StreamReader(fs))
+                    {
+                        playedData[1] = sr.ReadLine();
+                    }
+                }
+
+                return playedData;
+            }
+            catch
+            {
+                return new string[] { "Unknown", "Unknown" };
+            }
+        }
+
+        private static byte[] NspOrXciIcon(string applicationPath)
+        {
+            if (Path.GetExtension(applicationPath) == ".xci")
+            {
+                return RyujinxXciIcon;
+            }
+            else
+            {
+                return RyujinxNspIcon;
+            }
+        }
+    }
+}

+ 1 - 1
Ryujinx/Ui/GLScreen.cs

@@ -10,7 +10,7 @@ using System.Threading;
 
 using Stopwatch = System.Diagnostics.Stopwatch;
 
-namespace Ryujinx
+namespace Ryujinx.UI
 {
     public class GlScreen : GameWindow
     {

+ 601 - 0
Ryujinx/Ui/MainWindow.cs

@@ -0,0 +1,601 @@
+using DiscordRPC;
+using Gtk;
+using GUI = Gtk.Builder.ObjectAttribute;
+using Ryujinx.Audio;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Gal.OpenGL;
+using Ryujinx.Profiler;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.UI
+{
+    public class MainWindow : Window
+    {
+        internal static HLE.Switch _device;
+
+        private static IGalRenderer _renderer;
+
+        private static IAalOutput _audioOut;
+
+        private static Application _gtkApplication;
+
+        private static ListStore _tableStore;
+
+        private static bool _gameLoaded = false;
+
+        private static string _userId = "00000000000000000000000000000001";
+
+        public static bool DiscordIntegrationEnabled { get; set; }
+
+        public static DiscordRpcClient DiscordClient;
+
+        public static RichPresence DiscordPresence;
+
+#pragma warning disable 649
+        [GUI] Window        _mainWin;
+        [GUI] CheckMenuItem _fullScreen;
+        [GUI] MenuItem      _stopEmulation;
+        [GUI] CheckMenuItem _iconToggle;
+        [GUI] CheckMenuItem _titleToggle;
+        [GUI] CheckMenuItem _developerToggle;
+        [GUI] CheckMenuItem _versionToggle;
+        [GUI] CheckMenuItem _timePlayedToggle;
+        [GUI] CheckMenuItem _lastPlayedToggle;
+        [GUI] CheckMenuItem _fileExtToggle;
+        [GUI] CheckMenuItem _fileSizeToggle;
+        [GUI] CheckMenuItem _pathToggle;
+        [GUI] Box           _box;
+        [GUI] TreeView      _gameTable;
+        [GUI] GLArea        _glScreen;
+#pragma warning restore 649
+
+        public MainWindow(string[] args, Application gtkApplication) : this(new Builder("Ryujinx.Ui.MainWindow.glade"), args, gtkApplication) { }
+
+        private MainWindow(Builder builder, string[] args, Application gtkApplication) : base(builder.GetObject("_mainWin").Handle)
+        {
+            _renderer = new OglRenderer();
+
+            _audioOut = InitializeAudioEngine();
+
+            _device = new HLE.Switch(_renderer, _audioOut);
+
+            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)
+            {
+                DiscordClient   = new DiscordRpcClient("568815339807309834");
+                DiscordPresence = new RichPresence
+                {
+                    Assets = new Assets
+                    {
+                        LargeImageKey  = "ryujinx",
+                        LargeImageText = "Ryujinx is an emulator for the Nintendo Switch"
+                    },
+                    Details    = "Main Menu",
+                    State      = "Idling",
+                    Timestamps = new Timestamps(DateTime.UtcNow)
+                };
+
+                DiscordClient.Initialize();
+                DiscordClient.SetPresence(DiscordPresence);
+            }
+
+            builder.Autoconnect(this);
+
+            DeleteEvent += Window_Close;
+
+            _mainWin.Icon            = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.RyujinxIcon.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
+
+                LoadApplication(args[0]);
+            }
+            else
+            {
+                _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();
+            }
+        }
+
+        public static void CreateErrorDialog(string errorMessage)
+        {
+            MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, errorMessage)
+            {
+                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();
+        }
+
+        public static void UpdateGameTable()
+        {
+            _tableStore.Clear();
+            ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
+
+            foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
+            {
+                _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);
+            }
+        }
+
+        public static void ApplyTheme()
+        {
+            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
+            {
+                cssProvider.LoadFromPath(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Theme.css"));
+            }
+
+            StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
+        }
+
+        private void LoadApplication(string path)
+        {
+            if (_gameLoaded)
+            {
+                CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
+            }
+            else
+            {
+                if (Directory.Exists(path))
+                {
+                    string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
+
+                    if (romFsFiles.Length == 0)
+                    {
+                        romFsFiles = Directory.GetFiles(path, "*.romfs");
+                    }
+
+                    if (romFsFiles.Length > 0)
+                    {
+                        Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS.");
+                        _device.LoadCart(path, romFsFiles[0]);
+                    }
+                    else
+                    {
+                        Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS.");
+                        _device.LoadCart(path);
+                    }
+                }
+
+                else if (File.Exists(path))
+                {
+                    switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
+                    {
+                        case ".xci":
+                            Logger.PrintInfo(LogClass.Application, "Loading as XCI.");
+                            _device.LoadXci(path);
+                            break;
+                        case ".nca":
+                            Logger.PrintInfo(LogClass.Application, "Loading as NCA.");
+                            _device.LoadNca(path);
+                            break;
+                        case ".nsp":
+                        case ".pfs0":
+                            Logger.PrintInfo(LogClass.Application, "Loading as NSP.");
+                            _device.LoadNsp(path);
+                            break;
+                        default:
+                            Logger.PrintInfo(LogClass.Application, "Loading as homebrew.");
+                            try
+                            {
+                                _device.LoadProgram(path);
+                            }
+                            catch (ArgumentOutOfRangeException)
+                            {
+                                Logger.PrintError(LogClass.Application, $"The file which you have specified is unsupported by Ryujinx");
+                            }
+                            break;
+                    }
+                }
+                else
+                {
+                    Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file");
+                    End();
+                }
+
+                new Thread(new ThreadStart(CreateGameWindow)).Start();
+
+                _gameLoaded              = true;
+                _stopEmulation.Sensitive = true;
+
+                if (DiscordIntegrationEnabled)
+                {
+                    if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleID))
+                    {
+                        DiscordPresence.Assets.LargeImageKey = _device.System.TitleID;
+                    }
+
+                    string state = _device.System.TitleID;
+
+                    if (state == null)
+                    {
+                        state = "Ryujinx";
+                    }
+                    else
+                    {
+                        state = state.ToUpper();
+                    }
+
+                    string details = "Idling";
+
+                    if (_device.System.TitleName != null)
+                    {
+                        details = $"Playing {_device.System.TitleName}";
+                    }
+
+                    DiscordPresence.Details               = details;
+                    DiscordPresence.State                 = state;
+                    DiscordPresence.Assets.LargeImageText = _device.System.TitleName;
+                    DiscordPresence.Assets.SmallImageKey  = "ryujinx";
+                    DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch";
+                    DiscordPresence.Timestamps            = new Timestamps(DateTime.UtcNow);
+
+                    DiscordClient.SetPresence(DiscordPresence);
+                }
+
+                try
+                {
+                    string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
+
+                    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"));
+                        }
+                    }
+
+                    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"));
+                        }
+                    }
+
+                    using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
+                    {
+                        using (StreamWriter writer = new StreamWriter(stream))
+                        {
+                            writer.WriteLine(DateTime.UtcNow);
+                        }
+                    }
+                }
+                catch (ArgumentNullException)
+                {
+                    Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
+                }
+            }
+        }
+
+        private static void CreateGameWindow()
+        {
+            Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig);
+            
+            using (GlScreen screen = new GlScreen(_device, _renderer))
+            {
+                screen.MainLoop();
+
+                End();
+            }
+        }
+
+        private static void End()
+        {
+            if (_gameLoaded)
+            {
+                try
+                {
+                    string savePath        = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
+                    double currentPlayTime = 0;
+
+                    using (FileStream stream = File.OpenRead(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
+                    {
+                        using (StreamReader reader = new StreamReader(stream))
+                        {
+                            DateTime startTime = DateTime.Parse(reader.ReadLine());
+
+                            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));
+                                }
+                            }
+                        }
+                    }
+                }
+                catch (ArgumentNullException)
+                {
+                    Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
+                }
+            }
+
+            Profile.FinishProfiling();
+            _device.Dispose();
+            _audioOut.Dispose();
+            DiscordClient.Dispose();
+            Logger.Shutdown();
+            Environment.Exit(0);
+        }
+
+        /// <summary>
+        /// Picks an <see cref="IAalOutput"/> audio output renderer supported on this machine
+        /// </summary>
+        /// <returns>An <see cref="IAalOutput"/> supported by this machine</returns>
+        private static IAalOutput InitializeAudioEngine()
+        {
+            if (SoundIoAudioOut.IsSupported)
+            {
+                return new SoundIoAudioOut();
+            }
+            else if (OpenALAudioOut.IsSupported)
+            {
+                return new OpenALAudioOut();
+            }
+            else
+            {
+                return new DummyAudioOut();
+            }
+        }
+
+        //Events
+        private void Row_Activated(object o, RowActivatedArgs args)
+        {
+            _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString()));
+            string path = (string)_tableStore.GetValue(treeIter, 8);
+
+            LoadApplication(path);
+        }
+
+        private void Load_Application_File(object o, EventArgs args)
+        {
+            FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
+
+            fileChooser.Filter = new FileFilter();
+            fileChooser.Filter.AddPattern("*.nsp" );
+            fileChooser.Filter.AddPattern("*.pfs0");
+            fileChooser.Filter.AddPattern("*.xci" );
+            fileChooser.Filter.AddPattern("*.nca" );
+            fileChooser.Filter.AddPattern("*.nro" );
+            fileChooser.Filter.AddPattern("*.nso" );
+
+            if (fileChooser.Run() == (int)ResponseType.Accept)
+            {
+                LoadApplication(fileChooser.Filename);
+            }
+
+            fileChooser.Destroy();
+        }
+
+        private void Load_Application_Folder(object o, EventArgs args)
+        {
+            FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
+
+            if (fileChooser.Run() == (int)ResponseType.Accept)
+            {
+                LoadApplication(fileChooser.Filename);
+            }
+
+            fileChooser.Destroy();
+        }
+
+        private void Open_Ryu_Folder(object o, EventArgs args)
+        {
+            Process.Start(new ProcessStartInfo()
+            {
+                FileName        = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs"),
+                UseShellExecute = true,
+                Verb            = "open"
+            });
+        }
+
+        private void Exit_Pressed(object o, EventArgs args)
+        {
+            End();
+        }
+
+        private void Window_Close(object o, DeleteEventArgs args)
+        {
+            End();
+        }
+
+        private void StopEmulation_Pressed(object o, EventArgs args)
+        {
+            // TODO: Write logic to kill running game
+        }
+
+        private void FullScreen_Toggled(object o, EventArgs args)
+        {
+            if (_fullScreen.Active)
+            {
+                Fullscreen();
+            }
+            else
+            {
+                Unfullscreen();
+            }
+        }
+
+        private void Settings_Pressed(object o, EventArgs args)
+        {
+            SwitchSettings SettingsWin = new SwitchSettings(_device);
+
+            _gtkApplication.Register(GLib.Cancellable.Current);
+            _gtkApplication.AddWindow(SettingsWin);
+
+            SettingsWin.Show();
+        }
+
+        private void Update_Pressed(object o, EventArgs args)
+        {
+            string ryuUpdater = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "RyuUpdater.exe");
+
+            try
+            {
+                Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true });
+            }
+            catch(System.ComponentModel.Win32Exception)
+            {
+                CreateErrorDialog("Update canceled by user or updater was not found");
+            }
+        }
+
+        private void About_Pressed(object o, EventArgs args)
+        {
+            AboutWindow AboutWin = new AboutWindow();
+
+            _gtkApplication.Register(GLib.Cancellable.Current);
+            _gtkApplication.AddWindow(AboutWin);
+
+            AboutWin.Show();
+        }
+
+        private void Icon_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[0] = _iconToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void Title_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[1] = _titleToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void Developer_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[2] = _developerToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void Version_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[3] = _versionToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void TimePlayed_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[4] = _timePlayedToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void LastPlayed_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[5] = _lastPlayedToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void FileExt_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[6] = _fileExtToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void FileSize_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[7] = _fileSizeToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+
+        private void Path_Toggled(object o, EventArgs args)
+        {
+            SwitchSettings.SwitchConfig.GuiColumns[8] = _pathToggle.Active;
+
+            Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+        }
+    }
+}

+ 347 - 0
Ryujinx/Ui/MainWindow.glade

@@ -0,0 +1,347 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkApplicationWindow" id="_mainWin">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Ryujinx</property>
+    <property name="window_position">center</property>
+    <property name="default_width">1280</property>
+    <property name="default_height">750</property>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkBox" id="_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkMenuBar" id="MenuBar">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkMenuItem" id="FileMenu">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">File</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="GtkMenuItem" id="LoadApplicationFile">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Open a file chooser to chose a switch compatible file to load</property>
+                        <property name="label" translatable="yes">Load Application from File</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Load_Application_File" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="LoadApplicationFolder">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Open a file chooser to chose a switch compatible, unpacked application to load</property>
+                        <property name="label" translatable="yes">Load Unpacked Game</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Load_Application_Folder" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="OpenRyuFolder">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Open Ryujinx filesystem folder</property>
+                        <property name="label" translatable="yes">Open Ryujinx Folder</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Open_Ryu_Folder" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="Exit">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Exit Ryujinx</property>
+                        <property name="label" translatable="yes">Exit</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Exit_Pressed" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkMenuItem" id="OptionsMenu">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Options</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="_fullScreen">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Fullscreens the window</property>
+                        <property name="label" translatable="yes">Fullscreen</property>
+                        <property name="use_underline">True</property>
+                        <signal name="toggled" handler="FullScreen_Toggled" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="_stopEmulation">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Stop emualtion of the current game and return to game selection</property>
+                        <property name="label" translatable="yes">Stop Emulation</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <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="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="_iconToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Icon Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Icon Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Icon_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_titleToggle">
+                                <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>
+                                <property name="label" translatable="yes">Enable Title Name/ID Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Title_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_developerToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Developer Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Developer Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Developer_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_versionToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Version Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Version Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Version_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_timePlayedToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Time Played Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Time Played Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="TimePlayed_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_lastPlayedToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Last Played Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Last Played Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="LastPlayed_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_fileExtToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable file extension column in the game list</property>
+                                <property name="label" translatable="yes">Enable File Ext Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="FileExt_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_fileSizeToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable File Size Column in the game list</property>
+                                <property name="label" translatable="yes">Enable File Size Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="FileSize_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkCheckMenuItem" id="_pathToggle">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or Disable Path Column in the game list</property>
+                                <property name="label" translatable="yes">Enable Path Column</property>
+                                <property name="use_underline">True</property>
+                                <signal name="toggled" handler="Path_Toggled" swapped="no"/>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="SettingsMenu">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Open settings window</property>
+                        <property name="label" translatable="yes">Settings</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Settings_Pressed" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkMenuItem" id="ToolsMenu">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Tools</property>
+                <property name="use_underline">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkMenuItem" id="HelpMenu">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Help</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="GtkMenuItem" id="CheckUpdates">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Check for updates to Ryujinx (requires Ryujinx Installer)</property>
+                        <property name="label" translatable="yes">Check for Updates</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="Update_Pressed" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="About">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="tooltip_text" translatable="yes">Open about window</property>
+                        <property name="label" translatable="yes">About</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="About_Pressed" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="_gameTableWindow">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <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="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"/>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <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>
+</interface>

+ 2 - 2
Ryujinx/Ui/NpadKeyboard.cs

@@ -45,12 +45,12 @@ namespace Ryujinx.UI.Input
         /// <summary>
         /// Left JoyCon Keyboard Bindings
         /// </summary>
-        public NpadKeyboardLeft LeftJoycon { get; private set; }
+        public NpadKeyboardLeft LeftJoycon { get; set; }
 
         /// <summary>
         /// Right JoyCon Keyboard Bindings
         /// </summary>
-        public NpadKeyboardRight RightJoycon { get; private set; }
+        public NpadKeyboardRight RightJoycon { get; set; }
 
         /// <summary>
         /// Hotkey Keyboard Bindings

+ 424 - 0
Ryujinx/Ui/SwitchSettings.cs

@@ -0,0 +1,424 @@
+using Gtk;
+using GUI = Gtk.Builder.ObjectAttribute;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Input;
+using Ryujinx.UI.Input;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Ryujinx.UI
+{
+    public class SwitchSettings : Window
+    {
+        internal static Configuration SwitchConfig { get; set; }
+
+        internal HLE.Switch Device { get; set; }
+
+        private static ListStore _gameDirsBoxStore;
+
+        private static bool _listeningForKeypress;
+
+#pragma warning disable 649
+        [GUI] Window       _settingsWin;
+        [GUI] CheckButton  _errorLogToggle;
+        [GUI] CheckButton  _warningLogToggle;
+        [GUI] CheckButton  _infoLogToggle;
+        [GUI] CheckButton  _stubLogToggle;
+        [GUI] CheckButton  _debugLogToggle;
+        [GUI] CheckButton  _fileLogToggle;
+        [GUI] CheckButton  _guestLogToggle;
+        [GUI] CheckButton  _fsAccessLogToggle;
+        [GUI] Adjustment   _fsLogSpinAdjustment;
+        [GUI] CheckButton  _dockedModeToggle;
+        [GUI] CheckButton  _discordToggle;
+        [GUI] CheckButton  _vSyncToggle;
+        [GUI] CheckButton  _multiSchedToggle;
+        [GUI] CheckButton  _fsicToggle;
+        [GUI] CheckButton  _legacyJitToggle;
+        [GUI] CheckButton  _ignoreToggle;
+        [GUI] CheckButton  _directKeyboardAccess;
+        [GUI] ComboBoxText _systemLanguageSelect;
+        [GUI] CheckButton  _custThemeToggle;
+        [GUI] Entry        _custThemePath;
+        [GUI] ToggleButton _browseThemePath;
+        [GUI] Label        _custThemePathLabel;
+        [GUI] TreeView     _gameDirsBox;
+        [GUI] Entry        _addGameDirBox;
+        [GUI] ToggleButton _addDir;
+        [GUI] ToggleButton _browseDir;
+        [GUI] ToggleButton _removeDir;
+        [GUI] Entry        _logPath;
+        [GUI] Entry        _graphicsShadersDumpPath;
+        [GUI] Image        _controllerImage;
+
+        [GUI] ComboBoxText _controller1Type;
+        [GUI] ToggleButton _lStickUp1;
+        [GUI] ToggleButton _lStickDown1;
+        [GUI] ToggleButton _lStickLeft1;
+        [GUI] ToggleButton _lStickRight1;
+        [GUI] ToggleButton _lStickButton1;
+        [GUI] ToggleButton _dpadUp1;
+        [GUI] ToggleButton _dpadDown1;
+        [GUI] ToggleButton _dpadLeft1;
+        [GUI] ToggleButton _dpadRight1;
+        [GUI] ToggleButton _minus1;
+        [GUI] ToggleButton _l1;
+        [GUI] ToggleButton _zL1;
+        [GUI] ToggleButton _rStickUp1;
+        [GUI] ToggleButton _rStickDown1;
+        [GUI] ToggleButton _rStickLeft1;
+        [GUI] ToggleButton _rStickRight1;
+        [GUI] ToggleButton _rStickButton1;
+        [GUI] ToggleButton _a1;
+        [GUI] ToggleButton _b1;
+        [GUI] ToggleButton _x1;
+        [GUI] ToggleButton _y1;
+        [GUI] ToggleButton _plus1;
+        [GUI] ToggleButton _r1;
+        [GUI] ToggleButton _zR1;
+#pragma warning restore 649
+
+        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);
+
+            //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);
+
+            //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.EnableLegacyJit)           { _legacyJitToggle.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());
+
+            _lStickUp1.Label     = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString();
+            _lStickDown1.Label   = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString();
+            _lStickLeft1.Label   = SwitchConfig.KeyboardControls.LeftJoycon.StickLeft.ToString();
+            _lStickRight1.Label  = SwitchConfig.KeyboardControls.LeftJoycon.StickRight.ToString();
+            _lStickButton1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickButton.ToString();
+            _dpadUp1.Label       = SwitchConfig.KeyboardControls.LeftJoycon.DPadUp.ToString();
+            _dpadDown1.Label     = SwitchConfig.KeyboardControls.LeftJoycon.DPadDown.ToString();
+            _dpadLeft1.Label     = SwitchConfig.KeyboardControls.LeftJoycon.DPadLeft.ToString();
+            _dpadRight1.Label    = SwitchConfig.KeyboardControls.LeftJoycon.DPadRight.ToString();
+            _minus1.Label        = SwitchConfig.KeyboardControls.LeftJoycon.ButtonMinus.ToString();
+            _l1.Label            = SwitchConfig.KeyboardControls.LeftJoycon.ButtonL.ToString();
+            _zL1.Label           = SwitchConfig.KeyboardControls.LeftJoycon.ButtonZl.ToString();
+            _rStickUp1.Label     = SwitchConfig.KeyboardControls.RightJoycon.StickUp.ToString();
+            _rStickDown1.Label   = SwitchConfig.KeyboardControls.RightJoycon.StickDown.ToString();
+            _rStickLeft1.Label   = SwitchConfig.KeyboardControls.RightJoycon.StickLeft.ToString();
+            _rStickRight1.Label  = SwitchConfig.KeyboardControls.RightJoycon.StickRight.ToString();
+            _rStickButton1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickButton.ToString();
+            _a1.Label            = SwitchConfig.KeyboardControls.RightJoycon.ButtonA.ToString();
+            _b1.Label            = SwitchConfig.KeyboardControls.RightJoycon.ButtonB.ToString();
+            _x1.Label            = SwitchConfig.KeyboardControls.RightJoycon.ButtonX.ToString();
+            _y1.Label            = SwitchConfig.KeyboardControls.RightJoycon.ButtonY.ToString();
+            _plus1.Label         = SwitchConfig.KeyboardControls.RightJoycon.ButtonPlus.ToString();
+            _r1.Label            = SwitchConfig.KeyboardControls.RightJoycon.ButtonR.ToString();
+            _zR1.Label           = SwitchConfig.KeyboardControls.RightJoycon.ButtonZr.ToString();
+
+            _custThemePath.Buffer.Text           = SwitchConfig.CustomThemePath;
+            _graphicsShadersDumpPath.Buffer.Text = SwitchConfig.GraphicsShadersDumpPath;
+            _fsLogSpinAdjustment.Value           = SwitchConfig.FsGlobalAccessLogMode;
+
+            _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0);
+            _gameDirsBoxStore  = new ListStore(typeof(string));
+            _gameDirsBox.Model = _gameDirsBoxStore;
+            foreach (string gameDir in SwitchConfig.GameDirs)
+            {
+                _gameDirsBoxStore.AppendValues(gameDir);
+            }
+
+            if (_custThemeToggle.Active == false)
+            {
+                _custThemePath.Sensitive      = false;
+                _custThemePathLabel.Sensitive = false;
+                _browseThemePath.Sensitive    = false;
+            }
+
+            _logPath.Buffer.Text = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log");
+
+            _listeningForKeypress = false;
+        }
+
+        //Events
+        private void Button_Pressed(object obj, EventArgs args, ToggleButton Button)
+        {
+            if (_listeningForKeypress == false)
+            {
+                KeyPressEvent += On_KeyPress;
+
+                _listeningForKeypress = true;
+
+                void On_KeyPress(object Obj, KeyPressEventArgs KeyPressed)
+                {
+                    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;
+                    }
+                    else if (GdkToOpenTKInput.ContainsKey(key))
+                    {
+                        Button.Label = GdkToOpenTKInput[key];
+                    }
+                    else
+                    {
+                        Button.Label = "Space";
+                    }
+
+                    Button.SetStateFlags(0, true);
+
+                    KeyPressEvent -= On_KeyPress;
+
+                    _listeningForKeypress = false;
+                }
+            }
+            else
+            {
+                Button.SetStateFlags(0, true);
+            }
+        }
+
+        private void AddDir_Pressed(object obj, EventArgs args)
+        {
+            if (Directory.Exists(_addGameDirBox.Buffer.Text))
+            {
+                _gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text);
+            }
+
+            _addDir.SetStateFlags(0, true);
+        }
+
+        private void BrowseDir_Pressed(object obj, EventArgs args)
+        {
+            FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept);
+
+            if (fileChooser.Run() == (int)ResponseType.Accept)
+            {
+                _gameDirsBoxStore.AppendValues(fileChooser.Filename);
+            }
+
+            fileChooser.Destroy();
+
+            _browseDir.SetStateFlags(0, true);
+        }
+
+        private void RemoveDir_Pressed(object obj, EventArgs args)
+        {
+            TreeSelection selection = _gameDirsBox.Selection;
+
+            selection.GetSelected(out TreeIter treeIter);
+            _gameDirsBoxStore.Remove(ref treeIter);
+
+            _removeDir.SetStateFlags(0, true);
+        }
+
+        private void CustThemeToggle_Activated(object obj, EventArgs args)
+        {
+            _custThemePath.Sensitive      = _custThemeToggle.Active;
+            _custThemePathLabel.Sensitive = _custThemeToggle.Active;
+            _browseThemePath.Sensitive    = _custThemeToggle.Active;
+        }
+
+        private void BrowseThemeDir_Pressed(object obj, EventArgs args)
+        {
+            FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
+
+            fileChooser.Filter = new FileFilter();
+            fileChooser.Filter.AddPattern("*.css");
+
+            if (fileChooser.Run() == (int)ResponseType.Accept)
+            {
+                _custThemePath.Buffer.Text = fileChooser.Filename;
+            }
+
+            fileChooser.Destroy();
+
+            _browseThemePath.SetStateFlags(0, true);
+        }
+
+        private void SaveToggle_Activated(object obj, EventArgs args)
+        {
+            List<string> gameDirs = new List<string>();
+
+            _gameDirsBoxStore.GetIterFirst(out TreeIter treeIter);
+            for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++)
+            {
+                _gameDirsBoxStore.GetValue(treeIter, i);
+
+                gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0));
+
+                _gameDirsBoxStore.IterNext(ref treeIter);
+            }
+
+            SwitchConfig.LoggingEnableError        = _errorLogToggle.Active;
+            SwitchConfig.LoggingEnableWarn         = _warningLogToggle.Active;
+            SwitchConfig.LoggingEnableInfo         = _infoLogToggle.Active;
+            SwitchConfig.LoggingEnableStub         = _stubLogToggle.Active;
+            SwitchConfig.LoggingEnableDebug        = _debugLogToggle.Active;
+            SwitchConfig.LoggingEnableGuest        = _guestLogToggle.Active;
+            SwitchConfig.LoggingEnableFsAccessLog  = _fsAccessLogToggle.Active;
+            SwitchConfig.EnableFileLog             = _fileLogToggle.Active;
+            SwitchConfig.DockedMode                = _dockedModeToggle.Active;
+            SwitchConfig.EnableDiscordIntegration  = _discordToggle.Active;
+            SwitchConfig.EnableVsync               = _vSyncToggle.Active;
+            SwitchConfig.EnableMulticoreScheduling = _multiSchedToggle.Active;
+            SwitchConfig.EnableFsIntegrityChecks   = _fsicToggle.Active;
+            SwitchConfig.EnableLegacyJit           = _legacyJitToggle.Active;
+            SwitchConfig.IgnoreMissingServices     = _ignoreToggle.Active;
+            SwitchConfig.EnableKeyboard            = _directKeyboardAccess.Active;
+            SwitchConfig.EnableCustomTheme         = _custThemeToggle.Active;
+
+            SwitchConfig.KeyboardControls.LeftJoycon = new NpadKeyboardLeft()
+            {
+                StickUp     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickUp1.Label),
+                StickDown   = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickDown1.Label),
+                StickLeft   = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickLeft1.Label),
+                StickRight  = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickRight1.Label),
+                StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickButton1.Label),
+                DPadUp      = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadUp1.Label),
+                DPadDown    = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadDown1.Label),
+                DPadLeft    = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadLeft1.Label),
+                DPadRight   = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadRight1.Label),
+                ButtonMinus = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _minus1.Label),
+                ButtonL     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _l1.Label),
+                ButtonZl    = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zL1.Label),
+            };
+
+            SwitchConfig.KeyboardControls.RightJoycon = new NpadKeyboardRight()
+            {
+                StickUp     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickUp1.Label),
+                StickDown   = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickDown1.Label),
+                StickLeft   = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickLeft1.Label),
+                StickRight  = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickRight1.Label),
+                StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickButton1.Label),
+                ButtonA     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _a1.Label),
+                ButtonB     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _b1.Label),
+                ButtonX     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _x1.Label),
+                ButtonY     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _y1.Label),
+                ButtonPlus  = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _plus1.Label),
+                ButtonR     = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _r1.Label),
+                ButtonZr    = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zR1.Label),
+            };
+
+            SwitchConfig.SystemLanguage          = (SystemLanguage)Enum.Parse(typeof(SystemLanguage), _systemLanguageSelect.ActiveId);
+            SwitchConfig.ControllerType          = (ControllerStatus)Enum.Parse(typeof(ControllerStatus), _controller1Type.ActiveId);
+            SwitchConfig.CustomThemePath         = _custThemePath.Buffer.Text;
+            SwitchConfig.GraphicsShadersDumpPath = _graphicsShadersDumpPath.Buffer.Text;
+            SwitchConfig.GameDirs                = gameDirs;
+            SwitchConfig.FsGlobalAccessLogMode   = (int)_fsLogSpinAdjustment.Value;
+
+            Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
+            Configuration.Configure(Device, SwitchConfig);
+
+            MainWindow.ApplyTheme();
+            MainWindow.UpdateGameTable();
+
+            Destroy();
+        }
+
+        private void CloseToggle_Activated(object obj, EventArgs args)
+        {
+            Destroy();
+        }
+
+        public readonly Dictionary<string, string> GdkToOpenTKInput = new Dictionary<string, string>()
+        {
+            { "Key_0",       "Number0"        },
+            { "Key_1",       "Number1"        },
+            { "Key_2",       "Number2"        },
+            { "Key_3",       "Number3"        },
+            { "Key_4",       "Number4"        },
+            { "Key_5",       "Number5"        },
+            { "Key_6",       "Number6"        },
+            { "Key_7",       "Number7"        },
+            { "Key_8",       "Number8"        },
+            { "Key_9",       "Number9"        },
+            { "equal",       "Plus"           },
+            { "uparrow",     "Up"             },
+            { "downarrow",   "Down"           },
+            { "leftarrow",   "Left"           },
+            { "rightarrow",  "Right"          },
+            { "Control_L",   "ControlLeft"    },
+            { "Control_R",   "ControlRight"   },
+            { "Shift_L",     "ShiftLeft"      },
+            { "Shift_R",     "ShiftRight"     },
+            { "Alt_L",       "AltLeft"        },
+            { "Alt_R",       "AltRight"       },
+            { "Page_Up",     "PageUp"         },
+            { "Page_Down",   "PageDown"       },
+            { "KP_Enter",    "KeypadEnter"    },
+            { "KP_Up",       "Up"             },
+            { "KP_Down",     "Down"           },
+            { "KP_Left",     "Left"           },
+            { "KP_Right",    "Right"          },
+            { "KP_Divide",   "KeypadDivide"   },
+            { "KP_Multiply", "KeypadMultiply" },
+            { "KP_Subtract", "KeypadSubtract" },
+            { "KP_Add",      "KeypadAdd"      },
+            { "KP_Decimal",  "KeypadDecimal"  },
+            { "KP_0",        "Keypad0"        },
+            { "KP_1",        "Keypad1"        },
+            { "KP_2",        "Keypad2"        },
+            { "KP_3",        "Keypad3"        },
+            { "KP_4",        "Keypad4"        },
+            { "KP_5",        "Keypad5"        },
+            { "KP_6",        "Keypad6"        },
+            { "KP_7",        "Keypad7"        },
+            { "KP_8",        "Keypad8"        },
+            { "KP_9",        "Keypad9"        },
+        };
+    }
+}

+ 1989 - 0
Ryujinx/Ui/SwitchSettings.glade

@@ -0,0 +1,1989 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkAdjustment" id="_fsLogSpinAdjustment">
+    <property name="upper">3</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkDialog" id="_settingsWin">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Ryujinx - Settings</property>
+    <property name="modal">True</property>
+    <property name="window_position">center</property>
+    <property name="default_width">910</property>
+    <property name="default_height">790</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <placeholder/>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="margin_right">5</property>
+            <property name="margin_top">3</property>
+            <property name="margin_bottom">3</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkToggleButton" id="SaveToggle">
+                <property name="label" translatable="yes">Save</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="toggled" handler="SaveToggle_Activated" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToggleButton" id="CloseToggle">
+                <property name="label" translatable="yes">Close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="toggled" handler="CloseToggle_Activated" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="padding">5</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkViewport">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkNotebook">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child>
+                      <object class="GtkBox" id="TabGeneral">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="margin_left">5</property>
+                        <property name="margin_right">10</property>
+                        <property name="margin_top">5</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkBox" id="CatGeneral">
+                            <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="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="margin_bottom">5</property>
+                                <property name="label" translatable="yes">General</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="box1">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkBox">
+                                    <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">Change System Language</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="yes">System Language:</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="_systemLanguageSelect">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="tooltip_text" translatable="yes">Change System Language</property>
+                                        <items>
+                                          <item id="AmericanEnglish" translatable="yes">American English</item>
+                                          <item id="BritishEnglish" translatable="yes">British English</item>
+                                          <item id="CanadianFrench" translatable="yes">Canadian French</item>
+                                          <item id="Chinese" translatable="yes">Chinese</item>
+                                          <item id="Dutch" translatable="yes">Dutch</item>
+                                          <item id="French" translatable="yes">French</item>
+                                          <item id="German" translatable="yes">German</item>
+                                          <item id="Italian" translatable="yes">Italian</item>
+                                          <item id="Japanese" translatable="yes">Japanese</item>
+                                          <item id="Korean" translatable="yes">Korean</item>
+                                          <item id="LatinAmericanSpanish" translatable="yes">Latin American Spanish</item>
+                                          <item id="Portuguese" translatable="yes">Portuguese</item>
+                                          <item id="Russian" translatable="yes">Russian</item>
+                                          <item id="SimplifiedChinese" translatable="yes">Simplified Chinese</item>
+                                          <item id="Spanish" translatable="yes">Spanish</item>
+                                          <item id="Taiwanese" translatable="yes">Taiwanese</item>
+                                          <item id="TraditionalChinese" translatable="yes">Traditional Chinese</item>
+                                        </items>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</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="GtkCheckButton" id="_discordToggle">
+                                    <property name="label" translatable="yes">Enable Discord Integration</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables or disables Discord Rich Presense</property>
+                                    <property name="halign">start</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSeparator">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">5</property>
+                            <property name="margin_right">5</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="CatGameDir">
+                            <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="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="margin_bottom">5</property>
+                                <property name="label" translatable="yes">Game Directories</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">10</property>
+                                <property name="margin_right">10</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkScrolledWindow">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="margin_bottom">10</property>
+                                    <property name="shadow_type">in</property>
+                                    <child>
+                                      <object class="GtkTreeView" id="_gameDirsBox">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="headers_visible">False</property>
+                                        <property name="headers_clickable">False</property>
+                                        <child internal-child="selection">
+                                          <object class="GtkTreeSelection"/>
+                                        </child>
+                                      </object>
+                                    </child>
+                                    <style>
+                                      <class name="GameDir"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <child>
+                                      <object class="GtkEntry" id="_addGameDirBox">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="tooltip_text" translatable="yes">Enter a game directroy to add to the list</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_addDir">
+                                        <property name="label" translatable="yes">Add</property>
+                                        <property name="width_request">80</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="tooltip_text" translatable="yes"> Add a game directory to the list</property>
+                                        <property name="margin_left">5</property>
+                                        <signal name="toggled" handler="AddDir_Pressed" swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_browseDir">
+                                        <property name="label" translatable="yes">Browse...</property>
+                                        <property name="width_request">80</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="tooltip_text" translatable="yes">Browse for a game directory</property>
+                                        <property name="margin_left">5</property>
+                                        <signal name="toggled" handler="BrowseDir_Pressed" swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_removeDir">
+                                        <property name="label" translatable="yes">Remove</property>
+                                        <property name="width_request">80</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="tooltip_text" translatable="yes">Remove selected game directory</property>
+                                        <property name="margin_left">5</property>
+                                        <signal name="toggled" handler="RemoveDir_Pressed" swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">3</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSeparator">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">5</property>
+                            <property name="margin_right">5</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">5</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="CatThemes">
+                            <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="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="margin_bottom">5</property>
+                                <property name="label" translatable="yes">Themes</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">10</property>
+                                <property name="margin_right">10</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkCheckButton" id="_custThemeToggle">
+                                    <property name="label" translatable="yes">Use Custom Theme</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enable or disable custom themes in the GUI</property>
+                                    <property name="halign">start</property>
+                                    <property name="draw_indicator">True</property>
+                                    <signal name="toggled" handler="CustThemeToggle_Activated" swapped="no"/>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <child>
+                                      <object class="GtkLabel" id="_custThemePathLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="tooltip_text" translatable="yes">Path to custom GUI theme</property>
+                                        <property name="label" translatable="yes">Custom Theme Path:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkEntry" id="_custThemePath">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="tooltip_text" translatable="yes">Path to custom GUI theme</property>
+                                        <property name="valign">center</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="_browseThemePath">
+                                        <property name="label" translatable="yes">Browse...</property>
+                                        <property name="width_request">80</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="tooltip_text" translatable="yes">Browse for a custom GUI theme</property>
+                                        <property name="margin_left">5</property>
+                                        <signal name="toggled" handler="BrowseThemeDir_Pressed" swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</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="padding">10</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>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">6</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="tab">
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">General</property>
+                      </object>
+                      <packing>
+                        <property name="tab_fill">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="TabController">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_top">5</property>
+                            <property name="margin_bottom">10</property>
+                            <child>
+                              <object class="GtkCheckButton" id="_dockedModeToggle">
+                                <property name="label" translatable="yes">Enable Docked Mode</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or disable Docked Mode</property>
+                                <property name="draw_indicator">True</property>
+                              </object>
+                              <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="GtkCheckButton" id="_directKeyboardAccess">
+                                <property name="label" translatable="yes">Direct Keyboard Access</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="tooltip_text" translatable="yes">Enable or disable "direct keyboard access (HID) support" (Provides games access to your keyboard as a text entry device)</property>
+                                <property name="draw_indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</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="padding">5</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkNotebook">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</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>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="orientation">vertical</property>
+                                    <child>
+                                      <object class="GtkBox">
+                                        <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>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">10</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkGrid">
+                                        <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>
+                                      </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="_controllerImage">
+                                    <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>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child type="tab">
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Input</property>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                        <property name="tab_fill">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="TabSystem">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="margin_left">5</property>
+                        <property name="margin_right">10</property>
+                        <property name="margin_top">5</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <object class="GtkBox" id="CatCore">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">start</property>
+                            <property name="margin_left">5</property>
+                            <property name="margin_right">5</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <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_bottom">5</property>
+                                <property name="label" translatable="yes">Core</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">10</property>
+                                <property name="margin_right">10</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkCheckButton" id="_vSyncToggle">
+                                    <property name="label" translatable="yes">Enable VSync</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables or disables Vertical Sync</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_multiSchedToggle">
+                                    <property name="label" translatable="yes">Enable Multicore Scheduling</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables or disables multi-core scheduling of threads</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_legacyJitToggle">
+                                    <property name="label" translatable="yes">Use old ChocolArm64 ARM emulator</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Uses old ChocolArm64 ARM emulator rather then the new ARMeilleure</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_fsicToggle">
+                                    <property name="label" translatable="yes">Enable FS Integrity Checks</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables integrity checks on Game content files</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">3</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <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">Graphics Shaders Dump Path</property>
+                                        <property name="label" translatable="yes">Graphics Shaders Dump Path:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkEntry" id="_graphicsShadersDumpPath">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="tooltip_text" translatable="yes">Graphics Shaders Dump Path</property>
+                                        <property name="valign">center</property>
+                                        <property name="caps_lock_warning">False</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">4</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSeparator">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">5</property>
+                            <property name="margin_right">5</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="CatLog">
+                            <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="orientation">vertical</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="margin_bottom">5</property>
+                                <property name="label" translatable="yes">Logging</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">10</property>
+                                <property name="margin_right">10</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkCheckButton" id="_fileLogToggle">
+                                    <property name="label" translatable="yes">Enable Logging to File</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables or disables logging to a file on disk</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="margin_left">5</property>
+                                    <property name="margin_bottom">10</property>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="tooltip_text" translatable="yes">Location of the log file</property>
+                                        <property name="label" translatable="yes">Log File Location:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkEntry" id="_logPath">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="tooltip_text" translatable="yes">Location of the log file</property>
+                                        <property name="valign">center</property>
+                                        <property name="editable">False</property>
+                                        <property name="caps_lock_warning">False</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_debugLogToggle">
+                                    <property name="label" translatable="yes">Enable Debug Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing debug log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_stubLogToggle">
+                                    <property name="label" translatable="yes">Enable Stub Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing stub log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">3</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_infoLogToggle">
+                                    <property name="label" translatable="yes">Enable Info Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing info log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">4</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_warningLogToggle">
+                                    <property name="label" translatable="yes">Enable Warning Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing warning log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">5</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_errorLogToggle">
+                                    <property name="label" translatable="yes">Enable Error Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing error log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">6</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_guestLogToggle">
+                                    <property name="label" translatable="yes">Enable Guest Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing guest log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">7</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_fsAccessLogToggle">
+                                    <property name="label" translatable="yes">Enable Fs Access Logs</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enables printing fs access log messages</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">8</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <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">Enables FS access log output to the console. Possible modes are 0-3</property>
+                                        <property name="label" translatable="yes">Fs Global Access Log Mode:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="tooltip_text" translatable="yes">Enables FS access log output to the console. Possible modes are 0-3</property>
+                                        <property name="adjustment">_fsLogSpinAdjustment</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">9</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSeparator">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="margin_left">5</property>
+                            <property name="margin_right">5</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="padding">5</property>
+                            <property name="position">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="CatHacks">
+                            <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="orientation">vertical</property>
+                            <child>
+                              <object class="GtkBox">
+                                <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="halign">start</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="label" translatable="yes">Hacks</property>
+                                    <attributes>
+                                      <attribute name="weight" value="bold"/>
+                                    </attributes>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="label" translatable="yes"> - These may cause instability</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">10</property>
+                                <property name="margin_right">10</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkCheckButton" id="_ignoreToggle">
+                                    <property name="label" translatable="yes">Ignore Missing Services</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="tooltip_text" translatable="yes">Enable or disable ignoring missing services</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_top">5</property>
+                                    <property name="margin_bottom">5</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                              </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="padding">5</property>
+                            <property name="position">4</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child type="tab">
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">System</property>
+                      </object>
+                      <packing>
+                        <property name="position">2</property>
+                        <property name="tab_fill">False</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

BIN
Ryujinx/Ui/assets/DiscordLogo.png


BIN
Ryujinx/Ui/assets/GitHubLogo.png


BIN
Ryujinx/Ui/assets/JoyCon.png


BIN
Ryujinx/Ui/assets/PatreonLogo.png


BIN
Ryujinx/Ui/assets/TwitterLogo.png


BIN
Ryujinx/Ui/assets/ryujinxIcon.png


BIN
Ryujinx/Ui/assets/ryujinxNCAIcon.png


BIN
Ryujinx/Ui/assets/ryujinxNROIcon.png


BIN
Ryujinx/Ui/assets/ryujinxNSOIcon.png


BIN
Ryujinx/Ui/assets/ryujinxNSPIcon.png


BIN
Ryujinx/Ui/assets/ryujinxXCIIcon.png


+ 32 - 0
Ryujinx/_schema.json

@@ -494,6 +494,38 @@
         false
       ]
     },
+    "game_dirs": {
+      "$id": "#/properties/game_dirs",
+      "type": "string list",
+      "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",
+      "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 ]
+    },
+    "enable_custom_theme": {
+      "$id": "#/properties/enable_custom_theme",
+      "type": "boolean",
+      "title": "Enable custom themes in the GUI",
+      "description": "Enable or disable custom themes in the GUI",
+      "default": false,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "custom_theme_path": {
+      "$id": "#/properties/custom_theme_path",
+      "type": "string",
+      "title": "Path to custom GUI theme",
+      "description": "Path to custom GUI theme",
+      "default": ""
+    },
     "controller_type": {
       "$id": "#/properties/controller_type",
       "type": "string",

Some files were not shown because too many files changed in this diff