From d306115750df9df170cfef4d49c6b0b7af498962 Mon Sep 17 00:00:00 2001
From: jduncanator <1518948+jduncanator@users.noreply.github.com>
Date: Mon, 11 Feb 2019 23:00:32 +1100
Subject: [PATCH] Logger and Configuration Refactoring (#573)

* Logging: Refactor log targets into Ryujinx.Common

* Logger: Implement JSON Log Target

* Logger: Optimize Console/File logging targets

Implement a simple ObjectPool to pool up StringBuilders to avoid causing excessive GCing of gen1/2 items when large amounts of log entries are being generated.

We can also pre-determine the async overflow action at initialization time, allowing for an easy optimization in the message enqueue function, avoiding a number of comparisons.

* Logger: Implement LogFormatters

* Config: Refactor configuration file and loading

* Config: Rename to .jsonc to avoid highlighting issues in VSC and GitHub

* Resolve style nits

* Config: Resolve incorrect default key binding

* Config: Also update key binding default in schema

* Tidy up namespace imports

* Config: Update CONFIG.md to reflect new Config file
---
 CONFIG.md                                     | 210 ++---
 README.md                                     |   2 +-
 .../BinaryReaderExtensions.cs}                |  10 +-
 Ryujinx.Common/Extensions/EnumExtensions.cs   |  12 +
 .../Logging/Formatters/DefaultLogFormatter.cs |  53 ++
 .../Logging/Formatters/ILogFormatter.cs       |   7 +
 Ryujinx.Common/Logging/LogClass.cs            |   1 +
 Ryujinx.Common/Logging/Logger.cs              |  28 +-
 .../Logging/Targets/AsyncLogTargetWrapper.cs  |  76 ++
 .../Logging/Targets/ConsoleLogTarget.cs       |  48 +
 .../Logging/Targets/FileLogTarget.cs          |  36 +
 Ryujinx.Common/Logging/Targets/ILogTarget.cs  |   9 +
 .../Logging/Targets/JsonLogTarget.cs          |  35 +
 Ryujinx.Common/Pools/ObjectPool.cs            |  75 ++
 Ryujinx.Common/Pools/SharedPools.cs           |  17 +
 Ryujinx.Common/Ryujinx.Common.csproj          |   4 +
 Ryujinx.Common/{ => Utilities}/BitUtils.cs    |   0
 Ryujinx.Common/{ => Utilities}/HexUtils.cs    |   0
 Ryujinx.HLE/Switch.cs                         |   1 -
 Ryujinx.sln                                   |   6 +
 Ryujinx/Config.cs                             | 203 -----
 Ryujinx/Config.jsonc                          | 124 +++
 Ryujinx/Configuration.cs                      | 239 +++++
 Ryujinx/Program.cs                            |  25 +-
 Ryujinx/Ryujinx.conf                          | 107 ---
 Ryujinx/Ryujinx.csproj                        |   2 +-
 Ryujinx/Ui/GLScreen.cs                        |  12 +-
 Ryujinx/Ui/Log.cs                             | 140 ---
 Ryujinx/Ui/NpadController.cs                  | 117 +--
 Ryujinx/Ui/NpadKeyboard.cs                    | 113 ++-
 Ryujinx/_schema.json                          | 823 ++++++++++++++++++
 31 files changed, 1844 insertions(+), 691 deletions(-)
 rename Ryujinx.Common/{StructIOExtension.cs => Extensions/BinaryReaderExtensions.cs} (83%)
 create mode 100644 Ryujinx.Common/Extensions/EnumExtensions.cs
 create mode 100644 Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
 create mode 100644 Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
 create mode 100644 Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
 create mode 100644 Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
 create mode 100644 Ryujinx.Common/Logging/Targets/FileLogTarget.cs
 create mode 100644 Ryujinx.Common/Logging/Targets/ILogTarget.cs
 create mode 100644 Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
 create mode 100644 Ryujinx.Common/Pools/ObjectPool.cs
 create mode 100644 Ryujinx.Common/Pools/SharedPools.cs
 rename Ryujinx.Common/{ => Utilities}/BitUtils.cs (100%)
 rename Ryujinx.Common/{ => Utilities}/HexUtils.cs (100%)
 delete mode 100644 Ryujinx/Config.cs
 create mode 100644 Ryujinx/Config.jsonc
 create mode 100644 Ryujinx/Configuration.cs
 delete mode 100644 Ryujinx/Ryujinx.conf
 delete mode 100644 Ryujinx/Ui/Log.cs
 create mode 100644 Ryujinx/_schema.json

diff --git a/CONFIG.md b/CONFIG.md
index eef7b68d9..b63f5e832 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -1,123 +1,124 @@
 ## Config File
 
-`Ryujinx.conf` should be present in executable folder (It's an *.ini file) following this format:
+`Config.jsonc` should be present in executable folder. The available settings follow:
 
-- `Logging_Enable_Info` *(bool)*
+- `graphics_shaders_dump_path` *(string)*
+
+  Dump shaders in local directory (e.g. `C:\ShaderDumps`)
+
+- `logging_enable_debug` *(bool)*
+
+  Enable the Debug Logging.
+
+- `logging_enable_stub` *(bool)*
+
+  Enable the Trace Logging.
+
+- `logging_enable_info` *(bool)*
 
   Enable the Informations Logging.
 
-- `Logging_Enable_Trace` *(bool)*
+- `logging_enable_warn` *(bool)*
 
-  Enable the Trace Logging (Enabled in Debug recommended).
-  
-- `Logging_Enable_Debug` *(bool)*
+  Enable the Warning Logging.
 
-   Enable the Debug Logging (Enabled in Debug recommended).
+- `logging_enable_error` *(bool)*
 
-- `Logging_Enable_Warn` *(bool)*
+  Enable the Error Logging.
 
-  Enable the Warning Logging (Enabled in Debug recommended).
-
-- `Logging_Enable_Error` *(bool)*
-
-  Enable the Error Logging (Enabled in Debug recommended).
-
-- `Logging_Enable_Fatal` *(bool)*
-
-  Enable the Fatal Logging (Enabled in Debug recommended).
-
-- `Logging_Enable_Ipc` *(bool)*
-
-  Enable the Ipc Message Logging.
-
-- `Logging_Enable_LogFile` *(bool)*
+- `enable_file_log` *(bool)*
 
   Enable writing the logging inside a Ryujinx.log file.
-  
-- `GamePad_Index` *(int)*
 
-  The index of the Controller Device.
-  
-- `GamePad_Deadzone` *(float)*
+- `system_language` *(string)*
 
-  The deadzone of both analog sticks on the Controller.
+  Change System Language, [System Language list](https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b)
 
-- `GamePad_Enable` *(bool)*
-  
-  Whether or not to enable Controller Support.
-  
-- `Controls_Left_JoyConKeyboard_XX` *(int)*
-  ```
-  Controls_Left_JoyConKeyboard_Stick_Up (int)
-  Controls_Left_JoyConKeyboard_Stick_Down (int)
-  Controls_Left_JoyConKeyboard_Stick_Left (int)
-  Controls_Left_JoyConKeyboard_Stick_Right (int)
-  Controls_Left_JoyConKeyboard_Stick_Button (int)
-  Controls_Left_JoyConKeyboard_DPad_Up (int)
-  Controls_Left_JoyConKeyboard_DPad_Down (int)
-  Controls_Left_JoyConKeyboard_DPad_Left (int)
-  Controls_Left_JoyConKeyboard_DPad_Right (int)
-  Controls_Left_JoyConKeyboard_Button_Minus (int)
-  Controls_Left_JoyConKeyboard_Button_L (int)
-  Controls_Left_JoyConKeyboard_Button_ZL (int)
-  ```
-  
-  Keys of the Left Emulated Joycon, the values depend of the [OpenTK Enum Keys](https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs).
-  
-  OpenTK use a QWERTY layout, so pay attention if you use another Keyboard Layout.
-  
-  Ex: `Controls_Left_JoyConKeyboard_Button_Minus = 52` > Tab key (All Layout).
+- `docked_mode` *(bool)*
 
-- `Controls_Right_JoyConKeyboard_XX` *(int)*
-  ```
-  Controls_Right_JoyConKeyboard_Stick_Up (int)
-  Controls_Right_JoyConKeyboard_Stick_Down (int)
-  Controls_Right_JoyConKeyboard_Stick_Left (int)
-  Controls_Right_JoyConKeyboard_Stick_Right (int)
-  Controls_Right_JoyConKeyboard_Stick_Button (int)
-  Controls_Right_JoyConKeyboard_Button_A (int)
-  Controls_Right_JoyConKeyboard_Button_B (int)
-  Controls_Right_JoyConKeyboard_Button_X (int)
-  Controls_Right_JoyConKeyboard_Button_Y (int)
-  Controls_Right_JoyConKeyboard_Button_Plus (int)
-  Controls_Right_JoyConKeyboard_Button_R (int)
-  Controls_Right_JoyConKeyboard_Button_ZR (int)
-  ```
+  Enable or Disable Docked Mode
 
-  Keys of the right Emulated Joycon, the values depend of the [OpenTK Enum Keys](https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs).
-  
-  OpenTK use a QWERTY layout, so pay attention if you use another Keyboard Layout.
-  
-  Ex: `Controls_Right_JoyConKeyboard_Button_A = 83` > A key (QWERTY Layout) / Q key (AZERTY Layout).
-  
-- `Controls_Left_JoyConController_XX` *(String)*
-  ```
-  Controls_Left_JoyConController_Stick (String)
-  Controls_Left_JoyConController_Stick_Button (String)
-  Controls_Left_JoyConController_DPad_Up (String)
-  Controls_Left_JoyConController_DPad_Down (String)
-  Controls_Left_JoyConController_DPad_Left (String)
-  Controls_Left_JoyConController_DPad_Right (String)
-  Controls_Left_JoyConController_Button_Minus (String)
-  Controls_Left_JoyConController_Button_L (String)
-  Controls_Left_JoyConController_Button_ZL (String)
-  ```
-  
-- `Controls_Right_JoyConController_XX` *(String)*
-  ```
-  Controls_Right_JoyConController_Stick (String)
-  Controls_Right_JoyConController_Stick_Button (String)
-  Controls_Right_JoyConController_Button_A (String)
-  Controls_Right_JoyConController_Button_B (String)
-  Controls_Right_JoyConController_Button_X (String)
-  Controls_Right_JoyConController_Button_Y (String)
-  Controls_Right_JoyConController_Button_Plus (String)
-  Controls_Right_JoyConController_Button_R (String)
-  Controls_Right_JoyConController_Button_ZR (String)
-  ```
+- `enable_vsync` *(bool)*
 
-- Default Mapping
-   - Controller
+  Enable or Disable Game Vsync
+
+- `enable_multicore_scheduling` *(bool)*
+
+  Enable or Disable Multi-core scheduling of threads
+
+- `enable_fs_integrity_checks` *(bool)*
+
+  Enable integrity checks on Switch content files
+
+- `controller_type` *(string)*
+
+  The primary controller's type.
+  Supported Values: `Handheld`, `ProController`, `NpadPair`, `NpadLeft`, `NpadRight`
+
+- `keyboard_controls` *(object)* :
+  - `left_joycon` *(object)* :
+    Left JoyCon Keyboard Bindings
+    - `stick_up` *(string)*
+    - `stick_down` *(string)*
+    - `stick_left` *(string)*
+    - `stick_right` *(string)*
+    - `stick_button` *(string)*
+    - `dpad_up` *(string)*
+    - `dpad_down` *(string)*
+    - `dpad_left` *(string)*
+    - `dpad_right` *(string)*
+    - `button_minus` *(string)*
+    - `button_l` *(string)*
+    - `button_zl` *(string)*
+  - `right_joycon` *(object)* :
+    Right JoyCon Keyboard Bindings
+    - `stick_up` *(string)*
+    - `stick_down` *(string)*
+    - `stick_left` *(string)*
+    - `stick_right` *(string)*
+    - `stick_button` *(string)*
+    - `button_a` *(string)*
+    - `button_b` *(string)*
+    - `button_x` *(string)*
+    - `button_y` *(string)*
+    - `button_plus` *(string)*
+    - `button_r` *(string)*
+    - `button_zr` *(string)*
+
+- `gamepad_controls` *(object)* :
+  - `enabled` *(bool)*
+    Whether or not to enable Controller Support.
+  - `index` *(int)*
+    The index of the Controller Device.
+  - `deadzone` *(number)*
+    The deadzone of both analog sticks on the Controller.
+  - `trigger_threshold` *(number)*
+    The value of how pressed down each trigger has to be in order to register a button press
+  - `left_joycon` *(object)* :
+    Left JoyCon Controller Bindings
+    - `stick` *(string)*
+    - `stick_button` *(string)*
+    - `dpad_up` *(string)*
+    - `dpad_down` *(string)*
+    - `dpad_left` *(string)*
+    - `dpad_right` *(string)*
+    - `button_minus` *(string)*
+    - `button_l` *(string)*
+    - `button_zl` *(string)*
+  - `right_joycon` *(object)* :
+  Right JoyCon Controller Bindings
+    - `stick` *(string)*
+    - `stick_button` *(string)*
+    - `button_a` *(string)*
+    - `button_b` *(string)*
+    - `button_x` *(string)*
+    - `button_y` *(string)*
+    - `button_plus` *(string)*
+    - `button_r` *(string)*
+    - `button_zr` *(string)*
+  
+### Default Mapping
+   #### Controller
      - Left Joycon:
        - Analog Stick = Left Analog Stick
 	   - DPad Up = DPad Up
@@ -137,7 +138,8 @@
 	   - Plus = Start / Options
 	   - R = Right Shoulder Button
 	   - ZR = Right Trigger
-   - Keyboard
+
+   #### Keyboard
      - Left Joycon:
 	   - Stick Up = W
 	   - Stick Down = S
@@ -166,7 +168,7 @@
 	   - R = U
 	   - ZR = O
   
-- Valid Button Mappings
+### Valid Button Mappings
   - A = The A / Cross Button
   - B = The B / Circle Button
   - X = The X / Square Button
diff --git a/README.md b/README.md
index bdf8588ae..10279b505 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ or just drag'n'drop the homebrew *.NRO / *.NSO or the game *.NSP / *.XCI on the
 
  - Controller Input is supported, see [CONFIG.md](CONFIG.md)
 
- - Config File: `Ryujinx.conf` should be present in executable folder.
+ - Config File: `Config.jsonc` should be present in executable folder.
    For more information [you can go here](CONFIG.md).
 
 **Help**
diff --git a/Ryujinx.Common/StructIOExtension.cs b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs
similarity index 83%
rename from Ryujinx.Common/StructIOExtension.cs
rename to Ryujinx.Common/Extensions/BinaryReaderExtensions.cs
index 8671b1920..49af946fd 100644
--- a/Ryujinx.Common/StructIOExtension.cs
+++ b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs
@@ -1,14 +1,13 @@
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.Runtime.InteropServices;
-using System.Text;
 
 namespace Ryujinx.Common
 {
-    public static class StructIOExtension
+    public static class BinaryReaderExtensions
     {
-        public unsafe static T ReadStruct<T>(this BinaryReader reader) where T : struct
+        public unsafe static T ReadStruct<T>(this BinaryReader reader)
+            where T : struct
         {
             int size = Marshal.SizeOf<T>();
 
@@ -20,7 +19,8 @@ namespace Ryujinx.Common
             }
         }
 
-        public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value) where T : struct
+        public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
+            where T : struct
         {
             long size = Marshal.SizeOf<T>();
 
diff --git a/Ryujinx.Common/Extensions/EnumExtensions.cs b/Ryujinx.Common/Extensions/EnumExtensions.cs
new file mode 100644
index 000000000..560af8829
--- /dev/null
+++ b/Ryujinx.Common/Extensions/EnumExtensions.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Common
+{
+    public static class EnumExtensions
+    {
+        public static T[] GetValues<T>()
+        {
+            return (T[])Enum.GetValues(typeof(T));
+        }
+    }
+}
diff --git a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
new file mode 100644
index 000000000..0c4396e7f
--- /dev/null
+++ b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
@@ -0,0 +1,53 @@
+using System.Reflection;
+using System.Text;
+
+namespace Ryujinx.Common.Logging
+{
+    internal class DefaultLogFormatter : ILogFormatter
+    {
+        private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
+
+        public string Format(LogEventArgs args)
+        {
+            StringBuilder sb = _stringBuilderPool.Allocate();
+
+            try
+            {
+                sb.Clear();
+
+                sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
+                sb.Append(" | ");
+                sb.AppendFormat("{0:d4}", args.ThreadId);
+                sb.Append(' ');
+                sb.Append(args.Message);
+
+                if (args.Data != null)
+                {
+                    PropertyInfo[] props = args.Data.GetType().GetProperties();
+
+                    sb.Append(' ');
+
+                    foreach (var prop in props)
+                    {
+                        sb.Append(prop.Name);
+                        sb.Append(": ");
+                        sb.Append(prop.GetValue(args.Data));
+                        sb.Append(" - ");
+                    }
+
+                    // We remove the final '-' from the string
+                    if (props.Length > 0)
+                    {
+                        sb.Remove(sb.Length - 3, 3);
+                    }
+                }
+
+                return sb.ToString();
+            }
+            finally
+            {
+                _stringBuilderPool.Release(sb);
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
new file mode 100644
index 000000000..9a55bc6b0
--- /dev/null
+++ b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Common.Logging
+{
+    interface ILogFormatter
+    {
+        string Format(LogEventArgs args);
+    }
+}
diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs
index f20347b6b..66a83b376 100644
--- a/Ryujinx.Common/Logging/LogClass.cs
+++ b/Ryujinx.Common/Logging/LogClass.cs
@@ -2,6 +2,7 @@ namespace Ryujinx.Common.Logging
 {
     public enum LogClass
     {
+        Application,
         Audio,
         Cpu,
         Font,
diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs
index 35ca416bc..88ebe4731 100644
--- a/Ryujinx.Common/Logging/Logger.cs
+++ b/Ryujinx.Common/Logging/Logger.cs
@@ -1,8 +1,7 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
-using System.Reflection;
 using System.Runtime.CompilerServices;
-using System.Text;
 using System.Threading;
 
 namespace Ryujinx.Common.Logging
@@ -14,9 +13,9 @@ namespace Ryujinx.Common.Logging
         private static readonly bool[] m_EnabledLevels;
         private static readonly bool[] m_EnabledClasses;
 
-        public static event EventHandler<LogEventArgs> Updated;
+        private static readonly List<ILogTarget> m_LogTargets;
 
-        public static bool EnableFileLog { get; set; }
+        public static event EventHandler<LogEventArgs> Updated;
 
         static Logger()
         {
@@ -33,9 +32,30 @@ namespace Ryujinx.Common.Logging
                 m_EnabledClasses[index] = true;
             }
 
+            m_LogTargets = new List<ILogTarget>();
+
             m_Time = Stopwatch.StartNew();
         }
 
+        public static void AddTarget(ILogTarget target)
+        {
+            m_LogTargets.Add(target);
+
+            Updated += target.Log;
+        }
+
+        public static void Shutdown()
+        {
+            Updated = null;
+
+            foreach(var target in m_LogTargets)
+            {
+                target.Dispose();
+            }
+
+            m_LogTargets.Clear();
+        }
+
         public static void SetEnable(LogLevel logLevel, bool enabled)
         {
             m_EnabledLevels[(int)logLevel] = enabled;
diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
new file mode 100644
index 000000000..a805a83b6
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.Common.Logging
+{
+    public enum AsyncLogTargetOverflowAction
+    {
+        /// <summary>
+        /// Block until there's more room in the queue
+        /// </summary>
+        Block = 0,
+
+        /// <summary>
+        /// Discard the overflowing item
+        /// </summary>
+        Discard = 1
+    }
+
+    public class AsyncLogTargetWrapper : ILogTarget
+    {
+        private ILogTarget _target;
+
+        private Thread _messageThread;
+
+        private BlockingCollection<LogEventArgs> _messageQueue;
+
+        private readonly int _overflowTimeout;
+
+        public AsyncLogTargetWrapper(ILogTarget target)
+            : this(target, -1, AsyncLogTargetOverflowAction.Block)
+        { }
+
+        public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
+        {
+            _target          = target;
+            _messageQueue    = new BlockingCollection<LogEventArgs>(queueLimit);
+            _overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0;
+
+            _messageThread = new Thread(() => {
+                while (!_messageQueue.IsCompleted)
+                {
+                    try
+                    {
+                        _target.Log(this, _messageQueue.Take());
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // IOE means that Take() was called on a completed collection.
+                        // Some other thread can call CompleteAdding after we pass the
+                        // IsCompleted check but before we call Take.
+                        // We can simply catch the exception since the loop will break
+                        // on the next iteration.
+                    }
+                }
+            });
+
+            _messageThread.IsBackground = true;
+            _messageThread.Start();
+        }
+
+        public void Log(object sender, LogEventArgs e)
+        {
+            if (!_messageQueue.IsAddingCompleted)
+            {
+                _messageQueue.TryAdd(e, _overflowTimeout);
+            }
+        }
+
+        public void Dispose()
+        {
+            _messageQueue.CompleteAdding();
+            _messageThread.Join();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
new file mode 100644
index 000000000..871076a41
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Common.Logging
+{
+    public class ConsoleLogTarget : ILogTarget
+    {
+        private static readonly ConcurrentDictionary<LogLevel, ConsoleColor> _logColors;
+
+        private readonly ILogFormatter _formatter;
+
+        static ConsoleLogTarget()
+        {
+            _logColors = new ConcurrentDictionary<LogLevel, ConsoleColor> {
+                [ LogLevel.Stub    ] = ConsoleColor.DarkGray,
+                [ LogLevel.Info    ] = ConsoleColor.White,
+                [ LogLevel.Warning ] = ConsoleColor.Yellow,
+                [ LogLevel.Error   ] = ConsoleColor.Red
+            };
+        }
+
+        public ConsoleLogTarget()
+        {
+            _formatter = new DefaultLogFormatter();
+        }
+
+        public void Log(object sender, LogEventArgs args)
+        {
+            if (_logColors.TryGetValue(args.Level, out ConsoleColor color))
+            {
+                Console.ForegroundColor = color;
+
+                Console.WriteLine(_formatter.Format(args));
+
+                Console.ResetColor();
+            }
+            else
+            {
+                Console.WriteLine(_formatter.Format(args));
+            }
+        }
+
+        public void Dispose()
+        {
+            Console.ResetColor();
+        }
+    }
+}
diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
new file mode 100644
index 000000000..85dc82497
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.Common.Logging
+{
+    public class FileLogTarget : ILogTarget
+    {
+        private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
+
+        private readonly StreamWriter  _logWriter;
+        private readonly ILogFormatter _formatter;
+
+        public FileLogTarget(string path)
+            : this(path, FileShare.Read, FileMode.Append)
+        { }
+
+        public FileLogTarget(string path, FileShare fileShare, FileMode fileMode)
+        {
+            _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
+            _formatter = new DefaultLogFormatter();
+        }
+
+        public void Log(object sender, LogEventArgs args)
+        {
+            _logWriter.WriteLine(_formatter.Format(args));
+            _logWriter.Flush();
+        }
+
+        public void Dispose()
+        {
+            _logWriter.WriteLine("---- End of Log ----");
+            _logWriter.Flush();
+            _logWriter.Dispose();
+        }
+    }
+}
diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs
new file mode 100644
index 000000000..261c5e64b
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.Common.Logging
+{
+    public interface ILogTarget : IDisposable
+    {
+        void Log(object sender, LogEventArgs args);
+    }
+}
diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
new file mode 100644
index 000000000..410394aa2
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
@@ -0,0 +1,35 @@
+using System.IO;
+using Utf8Json;
+
+namespace Ryujinx.Common.Logging
+{
+    public class JsonLogTarget : ILogTarget
+    {
+        private Stream _stream;
+        private bool   _leaveOpen;
+
+        public JsonLogTarget(Stream stream)
+        {
+            _stream = stream;
+        }
+
+        public JsonLogTarget(Stream stream, bool leaveOpen)
+        {
+            _stream    = stream;
+            _leaveOpen = leaveOpen;
+        }
+
+        public void Log(object sender, LogEventArgs e)
+        {
+            JsonSerializer.Serialize(_stream, e);
+        }
+
+        public void Dispose()
+        {
+            if (!_leaveOpen)
+            {
+                _stream.Dispose();
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Common/Pools/ObjectPool.cs b/Ryujinx.Common/Pools/ObjectPool.cs
new file mode 100644
index 000000000..dba671bb7
--- /dev/null
+++ b/Ryujinx.Common/Pools/ObjectPool.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.Common
+{
+    public class ObjectPool<T>
+        where T : class
+    {
+        private T _firstItem;
+        private readonly T[] _items;
+
+        private readonly Func<T> _factory;
+
+        public ObjectPool(Func<T> factory, int size)
+        {
+            _items   = new T[size - 1];
+            _factory = factory;
+        }
+
+        public T Allocate()
+        {
+            var instance = _firstItem;
+
+            if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))
+            {
+                instance = AllocateInternal();
+            }
+
+            return instance;
+        }
+
+        private T AllocateInternal()
+        {
+            var items = _items;
+
+            for (int i = 0; i < items.Length; i++)
+            {
+                var instance = items[i];
+
+                if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance))
+                {
+                    return instance;
+                }
+            }
+
+            return _factory();
+        }
+
+        public void Release(T obj)
+        {
+            if (_firstItem == null)
+            {
+                _firstItem = obj;
+            }
+            else
+            {
+                ReleaseInternal(obj);
+            }
+        }
+
+        private void ReleaseInternal(T obj)
+        {
+            var items = _items;
+
+            for (int i = 0; i < items.Length; i++)
+            {
+                if (items[i] == null)
+                {
+                    items[i] = obj;
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Common/Pools/SharedPools.cs b/Ryujinx.Common/Pools/SharedPools.cs
new file mode 100644
index 000000000..b4860b85b
--- /dev/null
+++ b/Ryujinx.Common/Pools/SharedPools.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Common
+{
+    public static class SharedPools
+    {
+        private static class DefaultPool<T>
+            where T : class, new()
+        {
+            public static readonly ObjectPool<T> Instance = new ObjectPool<T>(() => new T(), 20);
+        }
+
+        public static ObjectPool<T> Default<T>()
+            where T : class, new()
+        {
+            return DefaultPool<T>.Instance;
+        }
+    }
+}
diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj
index 5c9293b70..bba481e6d 100644
--- a/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/Ryujinx.Common/Ryujinx.Common.csproj
@@ -13,4 +13,8 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="Utf8Json" Version="1.3.7" />
+  </ItemGroup>
+
 </Project>
diff --git a/Ryujinx.Common/BitUtils.cs b/Ryujinx.Common/Utilities/BitUtils.cs
similarity index 100%
rename from Ryujinx.Common/BitUtils.cs
rename to Ryujinx.Common/Utilities/BitUtils.cs
diff --git a/Ryujinx.Common/HexUtils.cs b/Ryujinx.Common/Utilities/HexUtils.cs
similarity index 100%
rename from Ryujinx.Common/HexUtils.cs
rename to Ryujinx.Common/Utilities/HexUtils.cs
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index d661b273a..4a15f616e 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -112,7 +112,6 @@ namespace Ryujinx.HLE
             if (disposing)
             {
                 System.Dispose();
-
                 VsyncEvent.Dispose();
             }
         }
diff --git a/Ryujinx.sln b/Ryujinx.sln
index 148224faa..990a89a2e 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.cs
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{464D8AB7-B056-4A99-B207-B8DCFB47AAA9}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -73,6 +75,10 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9} = {464D8AB7-B056-4A99-B207-B8DCFB47AAA9}
+		{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15} = {464D8AB7-B056-4A99-B207-B8DCFB47AAA9}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A}
 	EndGlobalSection
diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs
deleted file mode 100644
index a1d8cddf4..000000000
--- a/Ryujinx/Config.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-using LibHac.IO;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE;
-using Ryujinx.HLE.HOS.SystemState;
-using Ryujinx.HLE.Input;
-using Ryujinx.UI.Input;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-
-namespace Ryujinx
-{
-    public static class Config
-    {
-        public static NpadKeyboard   NpadKeyboard   { get; private set; }
-        public static NpadController NpadController { get; private set; }
-
-        public static void Read(Switch device)
-        {
-            string iniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
-
-            string iniPath = Path.Combine(iniFolder, "Ryujinx.conf");
-
-            IniParser parser = new IniParser(iniPath);
-
-            GraphicsConfig.ShadersDumpPath = parser.Value("Graphics_Shaders_Dump_Path");
-
-            Logger.SetEnable(LogLevel.Debug,   Convert.ToBoolean(parser.Value("Logging_Enable_Debug")));
-            Logger.SetEnable(LogLevel.Stub,    Convert.ToBoolean(parser.Value("Logging_Enable_Stub")));
-            Logger.SetEnable(LogLevel.Info,    Convert.ToBoolean(parser.Value("Logging_Enable_Info")));
-            Logger.SetEnable(LogLevel.Warning, Convert.ToBoolean(parser.Value("Logging_Enable_Warn")));
-            Logger.SetEnable(LogLevel.Error,   Convert.ToBoolean(parser.Value("Logging_Enable_Error")));
-
-            string[] filteredLogClasses = parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries);
-
-            //When the classes are specified on the list, we only
-            //enable the classes that are on the list.
-            //So, first disable everything, then enable
-            //the classes that the user added to the list.
-            if (filteredLogClasses.Length > 0)
-            {
-                foreach (LogClass Class in Enum.GetValues(typeof(LogClass)))
-                {
-                    Logger.SetEnable(Class, false);
-                }
-            }
-
-            foreach (string logClass in filteredLogClasses)
-            {
-                if (!string.IsNullOrEmpty(logClass.Trim()))
-                {
-                    foreach (LogClass Class in Enum.GetValues(typeof(LogClass)))
-                    {
-                        if (Class.ToString().ToLower().Contains(logClass.Trim().ToLower()))
-                        {
-                            Logger.SetEnable(Class, true);
-                        }
-                    }
-                }
-            }
-
-            Logger.EnableFileLog = Convert.ToBoolean(parser.Value("Enable_File_Log"));
-
-            SystemLanguage SetLanguage = Enum.Parse<SystemLanguage>(parser.Value("System_Language"));
-
-            device.System.State.SetLanguage(SetLanguage);
-
-            device.System.State.DockedMode = Convert.ToBoolean(parser.Value("Docked_Mode"));
-
-            device.EnableDeviceVsync = Convert.ToBoolean(parser.Value("Enable_Vsync"));
-
-            if (Convert.ToBoolean(parser.Value("Enable_MultiCore_Scheduling")))
-            {
-                device.System.EnableMultiCoreScheduling();
-            }
-
-            device.System.FsIntegrityCheckLevel = Convert.ToBoolean(parser.Value("Enable_FS_Integrity_Checks"))
-                ? IntegrityCheckLevel.ErrorOnInvalid
-                : IntegrityCheckLevel.None;
-
-            HidControllerType ControllerType = Enum.Parse<HidControllerType>(parser.Value("Controller_Type"));
-
-            device.Hid.InitilizePrimaryController(ControllerType);
-
-            NpadKeyboard = new NpadKeyboard(
-
-                new NpadKeyboardLeft
-                {
-                    StickUp     = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Stick_Up")),
-                    StickDown   = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Stick_Down")),
-                    StickLeft   = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Stick_Left")),
-                    StickRight  = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Stick_Right")),
-                    StickButton = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Stick_Button")),
-                    DPadUp      = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_DPad_Up")),
-                    DPadDown    = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_DPad_Down")),
-                    DPadLeft    = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_DPad_Left")),
-                    DPadRight   = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_DPad_Right")),
-                    ButtonMinus = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Button_Minus")),
-                    ButtonL     = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Button_L")),
-                    ButtonZl    = Convert.ToInt16(parser.Value("Controls_Left_JoyConKeyboard_Button_ZL"))
-                },
-
-                new NpadKeyboardRight
-                {
-                    StickUp     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Stick_Up")),
-                    StickDown   = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Stick_Down")),
-                    StickLeft   = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Stick_Left")),
-                    StickRight  = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Stick_Right")),
-                    StickButton = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Stick_Button")),
-                    ButtonA     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_A")),
-                    ButtonB     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_B")),
-                    ButtonX     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_X")),
-                    ButtonY     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_Y")),
-                    ButtonPlus  = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_Plus")),
-                    ButtonR     = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_R")),
-                    ButtonZr    = Convert.ToInt16(parser.Value("Controls_Right_JoyConKeyboard_Button_ZR"))
-                });
-
-            NpadController = new NpadController(
-                       Convert.ToBoolean(parser.Value("GamePad_Enable")),
-                       Convert.ToInt32  (parser.Value("GamePad_Index")),
-                (float)Convert.ToDouble (parser.Value("GamePad_Deadzone"),          CultureInfo.InvariantCulture),
-                (float)Convert.ToDouble (parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture),
-
-                new NpadControllerLeft
-                {
-                    Stick       = ToId(parser.Value("Controls_Left_JoyConController_Stick")),
-                    StickButton = ToId(parser.Value("Controls_Left_JoyConController_Stick_Button")),
-                    DPadUp      = ToId(parser.Value("Controls_Left_JoyConController_DPad_Up")),
-                    DPadDown    = ToId(parser.Value("Controls_Left_JoyConController_DPad_Down")),
-                    DPadLeft    = ToId(parser.Value("Controls_Left_JoyConController_DPad_Left")),
-                    DPadRight   = ToId(parser.Value("Controls_Left_JoyConController_DPad_Right")),
-                    ButtonMinus = ToId(parser.Value("Controls_Left_JoyConController_Button_Minus")),
-                    ButtonL     = ToId(parser.Value("Controls_Left_JoyConController_Button_L")),
-                    ButtonZl    = ToId(parser.Value("Controls_Left_JoyConController_Button_ZL"))
-                },
-
-                new NpadControllerRight
-                {
-                    Stick       = ToId(parser.Value("Controls_Right_JoyConController_Stick")),
-                    StickButton = ToId(parser.Value("Controls_Right_JoyConController_Stick_Button")),
-                    ButtonA     = ToId(parser.Value("Controls_Right_JoyConController_Button_A")),
-                    ButtonB     = ToId(parser.Value("Controls_Right_JoyConController_Button_B")),
-                    ButtonX     = ToId(parser.Value("Controls_Right_JoyConController_Button_X")),
-                    ButtonY     = ToId(parser.Value("Controls_Right_JoyConController_Button_Y")),
-                    ButtonPlus  = ToId(parser.Value("Controls_Right_JoyConController_Button_Plus")),
-                    ButtonR     = ToId(parser.Value("Controls_Right_JoyConController_Button_R")),
-                    ButtonZr    = ToId(parser.Value("Controls_Right_JoyConController_Button_ZR"))
-                });
-        }
-
-        private static ControllerInputId ToId(string key)
-        {
-            switch (key.ToUpper())
-            {
-                case "LSTICK":    return ControllerInputId.LStick;
-                case "DPADUP":    return ControllerInputId.DPadUp;
-                case "DPADDOWN":  return ControllerInputId.DPadDown;
-                case "DPADLEFT":  return ControllerInputId.DPadLeft;
-                case "DPADRIGHT": return ControllerInputId.DPadRight;
-                case "BACK":      return ControllerInputId.Back;
-                case "LSHOULDER": return ControllerInputId.LShoulder;
-                case "LTRIGGER":  return ControllerInputId.LTrigger;
-
-                case "RSTICK":    return ControllerInputId.RStick;
-                case "A":         return ControllerInputId.A;
-                case "B":         return ControllerInputId.B;
-                case "X":         return ControllerInputId.X;
-                case "Y":         return ControllerInputId.Y;
-                case "START":     return ControllerInputId.Start;
-                case "RSHOULDER": return ControllerInputId.RShoulder;
-                case "RTRIGGER":  return ControllerInputId.RTrigger;
-
-                case "LJOYSTICK": return ControllerInputId.LJoystick;
-                case "RJOYSTICK": return ControllerInputId.RJoystick;
-
-                default: return ControllerInputId.Invalid;
-            }
-        }
-    }
-
-    //https://stackoverflow.com/a/37772571
-    public class IniParser
-    {
-        private readonly Dictionary<string, string> _values;
-
-        public IniParser(string path)
-        {
-            _values = File.ReadLines(path)
-                .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))
-                .Select(line => line.Split('=', 2))
-                .ToDictionary(parts => parts[0].Trim(), parts => parts.Length > 1 ? parts[1].Trim() : null);
-        }
-
-        public string Value(string name)
-        {
-            return _values.TryGetValue(name, out string value) ? value : null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx/Config.jsonc b/Ryujinx/Config.jsonc
new file mode 100644
index 000000000..1ba601647
--- /dev/null
+++ b/Ryujinx/Config.jsonc
@@ -0,0 +1,124 @@
+{
+    "$schema": "./_schema.json",
+
+    // Dump shaders in local directory (e.g. `C:\ShaderDumps`)
+    "graphics_shaders_dump_path": "",
+
+    // Enable print debug logs
+    "logging_enable_debug": false,
+
+    // Enable print stubbed calls logs
+    "logging_enable_stub": true,
+
+    // Enable print informations logs
+    "logging_enable_info": true,
+
+    // Enable print warning logs
+    "logging_enable_warn": true,
+
+    // Enable print error logs
+    "logging_enable_error": true,
+
+    // 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 Game Vsync
+    "enable_vsync": true,
+    
+    // Enable or Disable Multi-core scheduling of threads
+    "enable_multicore_scheduling": false,
+    
+    // Enable integrity checks on Switch content files
+    "enable_fs_integrity_checks": true,
+    
+    // The primary controller's type
+    // Supported Values: Handheld, ProController, NpadPair, NpadLeft, NpadRight
+    "controller_type": "Handheld",
+
+    // Keyboard Controls
+    // https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/Key.cs
+    "keyboard_controls": {
+        // Left JoyCon Keyboard Bindings
+        "left_joycon": {
+            "stick_up": "W",
+            "stick_down": "S",
+            "stick_left": "A",
+            "stick_right": "D",
+            "stick_button": "F",
+            "dpad_up": "Up",
+            "dpad_down": "Down",
+            "dpad_left": "Left",
+            "dpad_right": "Right",
+            "button_minus": "Minus",
+            "button_l": "E",
+            "button_zl": "Q"
+        },
+
+        // Right JoyCon Keyboard Bindings
+        "right_joycon": {
+            "stick_up": "I",
+            "stick_down": "K",
+            "stick_left": "J",
+            "stick_right": "L",
+            "stick_button": "H",
+            "button_a": "Z",
+            "button_b": "X",
+            "button_x": "C",
+            "button_y": "V",
+            "button_plus": "Plus",
+            "button_r": "U",
+            "button_zr": "O"
+        }
+    },
+
+    // Controller Controls
+    "gamepad_controls": {
+        // Whether or not to enable Controller support
+        "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": "LJoystick",
+            "stick_button": "LStick",
+            "dpad_up": "DPadUp",
+            "dpad_down": "DPadDown",
+            "dpad_left": "DPadLeft",
+            "dpad_right": "DPadRight",
+            "button_minus": "Back",
+            "button_l": "LShoulder",
+            "button_zl": "LTrigger"
+        },
+
+        // Right JoyCon Controller Bindings
+        "right_joycon": {
+            "stick": "RJoystick",
+            "stick_button": "RStick",
+            "button_a": "B",
+            "button_b": "A",
+            "button_x": "Y",
+            "button_y": "X",
+            "button_plus": "Start",
+            "button_r": "RShoulder",
+            "button_zr": "RTrigger"
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Configuration.cs b/Ryujinx/Configuration.cs
new file mode 100644
index 000000000..5f1f86789
--- /dev/null
+++ b/Ryujinx/Configuration.cs
@@ -0,0 +1,239 @@
+using LibHac.IO;
+using OpenTK.Input;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Input;
+using Ryujinx.UI.Input;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Utf8Json;
+using Utf8Json.Resolvers;
+
+namespace Ryujinx
+{
+    public class Configuration
+    {
+        /// <summary>
+        /// The default configuration instance
+        /// </summary>
+        public static Configuration Instance { get; private set; }
+
+        /// <summary>
+        /// Dumps shaders in this local directory
+        /// </summary>
+        public string GraphicsShadersDumpPath { get; private set; }
+
+        /// <summary>
+        /// Enables printing debug log messages
+        /// </summary>
+        public bool LoggingEnableDebug { get; private set; }
+
+        /// <summary>
+        /// Enables printing stub log messages
+        /// </summary>
+        public bool LoggingEnableStub { get; private set; }
+
+        /// <summary>
+        /// Enables printing info log messages
+        /// </summary>
+        public bool LoggingEnableInfo { get; private set; }
+
+        /// <summary>
+        /// Enables printing warning log messages
+        /// </summary>
+        public bool LoggingEnableWarn { get; private set; }
+
+        /// <summary>
+        /// Enables printing error log messages
+        /// </summary>
+        public bool LoggingEnableError { get; private set; }
+
+        /// <summary>
+        /// Controls which log messages are written to the log targets
+        /// </summary>
+        public LogClass[] LoggingFilteredClasses { get; private set; }
+
+        /// <summary>
+        /// Enables or disables logging to a file on disk
+        /// </summary>
+        public bool EnableFileLog { get; private set; }
+
+        /// <summary>
+        /// Change System Language
+        /// </summary>
+        public SystemLanguage SystemLanguage { get; private set; }
+
+        /// <summary>
+        /// Enables or disables Docked Mode
+        /// </summary>
+        public bool DockedMode { get; private set; }
+
+        /// <summary>
+        /// Enables or disables Vertical Sync
+        /// </summary>
+        public bool EnableVsync { get; private set; }
+
+        /// <summary>
+        /// Enables or disables multi-core scheduling of threads
+        /// </summary>
+        public bool EnableMultiCoreScheduling { get; private set; }
+
+        /// <summary>
+        /// Enables integrity checks on Game content files
+        /// </summary>
+        public bool EnableFsIntegrityChecks { get; private set; }
+
+        /// <summary>
+        ///  The primary controller's type
+        /// </summary>
+        public HidControllerType ControllerType { get; private set; }
+
+        /// <summary>
+        /// Keyboard control bindings
+        /// </summary>
+        public NpadKeyboard KeyboardControls { get; private set; }
+
+        /// <summary>
+        /// Controller control bindings
+        /// </summary>
+        public NpadController GamepadControls { get; private set; }
+
+        /// <summary>
+        /// Loads a configuration file from disk
+        /// </summary>
+        /// <param name="path">The path to the JSON configuration file</param>
+        public static void Load(string path)
+        {
+            var resolver = CompositeResolver.Create(
+                new[] { new ConfigurationEnumFormatter<Key>() },
+                new[] { StandardResolver.AllowPrivateSnakeCase }
+            );
+
+            using (Stream stream = File.OpenRead(path))
+            {
+                Instance = JsonSerializer.Deserialize<Configuration>(stream, resolver);
+            }
+        }
+
+        /// <summary>
+        /// Loads a configuration file asynchronously from disk
+        /// </summary>
+        /// <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>() },
+                new[] { StandardResolver.AllowPrivateSnakeCase }
+            );
+
+            using (Stream stream = File.OpenRead(path))
+            {
+                Instance = await JsonSerializer.DeserializeAsync<Configuration>(stream, resolver);
+            }
+        }
+
+        /// <summary>
+        /// Configures a <see cref="Switch"/> instance
+        /// </summary>
+        /// <param name="device">The instance to configure</param>
+        public static void Configure(Switch device)
+        {
+            if (Instance == null)
+            {
+                throw new InvalidOperationException("Configuration has not been loaded yet.");
+            }
+
+            GraphicsConfig.ShadersDumpPath = Instance.GraphicsShadersDumpPath;
+
+            Logger.AddTarget(new AsyncLogTargetWrapper(
+                new ConsoleLogTarget(),
+                1000,
+                AsyncLogTargetOverflowAction.Block
+            ));
+
+            if (Instance.EnableFileLog)
+            {
+                Logger.AddTarget(new AsyncLogTargetWrapper(
+                    new FileLogTarget("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);
+
+            if (Instance.LoggingFilteredClasses.Length > 0)
+            {
+                foreach (var logClass in EnumExtensions.GetValues<LogClass>())
+                {
+                    Logger.SetEnable(logClass, false);
+                }
+
+                foreach (var logClass in Instance.LoggingFilteredClasses)
+                {
+                    Logger.SetEnable(logClass, true);
+                }
+            }
+
+            device.EnableDeviceVsync = Instance.EnableVsync;
+
+            device.System.State.DockedMode = Instance.DockedMode;
+
+            device.System.State.SetLanguage(Instance.SystemLanguage);
+
+            if (Instance.EnableMultiCoreScheduling)
+            {
+                device.System.EnableMultiCoreScheduling();
+            }
+
+            device.System.FsIntegrityCheckLevel = Instance.EnableFsIntegrityChecks
+                ? IntegrityCheckLevel.ErrorOnInvalid
+                : IntegrityCheckLevel.None;
+
+            if(Instance.GamepadControls.Enabled)
+            {
+                if (GamePad.GetName(Instance.GamepadControls.Index) == "Unmapped Controller")
+                {
+                    Instance.GamepadControls.SetEnabled(false);
+                }
+            }
+
+            device.Hid.InitilizePrimaryController(Instance.ControllerType);
+        }
+
+        private class ConfigurationEnumFormatter<T> : IJsonFormatter<T>
+            where T : struct
+        {
+            public void Serialize(ref JsonWriter writer, T value, IJsonFormatterResolver formatterResolver)
+            {
+                formatterResolver.GetFormatterWithVerify<string>()
+                                 .Serialize(ref writer, value.ToString(), formatterResolver);
+            }
+
+            public T Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+            {
+                if (reader.ReadIsNull())
+                {
+                    return default(T);
+                }
+
+                var enumName = formatterResolver.GetFormatterWithVerify<string>()
+                                                .Deserialize(ref reader, formatterResolver);
+
+                if(Enum.TryParse<T>(enumName, out T result))
+                {
+                    return result;
+                }
+
+                return default(T);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index 335aa0ea8..19916fe9f 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -20,9 +20,8 @@ namespace Ryujinx
 
             Switch device = new Switch(renderer, audioOut);
 
-            Config.Read(device);
-
-            Logger.Updated += Log.LogMessage;
+            Configuration.Load("Config.jsonc");
+            Configuration.Configure(device);
 
             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
             AppDomain.CurrentDomain.ProcessExit        += CurrentDomain_ProcessExit;
@@ -40,13 +39,13 @@ namespace Ryujinx
 
                     if (romFsFiles.Length > 0)
                     {
-                        Console.WriteLine("Loading as cart with RomFS.");
+                        Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS.");
 
                         device.LoadCart(args[0], romFsFiles[0]);
                     }
                     else
                     {
-                        Console.WriteLine("Loading as cart WITHOUT RomFS.");
+                        Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS.");
 
                         device.LoadCart(args[0]);
                     }
@@ -56,20 +55,20 @@ namespace Ryujinx
                     switch (Path.GetExtension(args[0]).ToLowerInvariant())
                     {
                         case ".xci":
-                            Console.WriteLine("Loading as XCI.");
+                            Logger.PrintInfo(LogClass.Application, "Loading as XCI.");
                             device.LoadXci(args[0]);
                             break;
                         case ".nca":
-                            Console.WriteLine("Loading as NCA.");
+                            Logger.PrintInfo(LogClass.Application, "Loading as NCA.");
                             device.LoadNca(args[0]);
                             break;
                         case ".nsp":
                         case ".pfs0":
-                            Console.WriteLine("Loading as NSP.");
+                            Logger.PrintInfo(LogClass.Application, "Loading as NSP.");
                             device.LoadNsp(args[0]);
                             break;
                         default:
-                            Console.WriteLine("Loading as homebrew.");
+                            Logger.PrintInfo(LogClass.Application, "Loading as homebrew.");
                             device.LoadProgram(args[0]);
                             break;
                     }
@@ -77,7 +76,7 @@ namespace Ryujinx
             }
             else
             {
-                Console.WriteLine("Please specify the folder with the NSOs/IStorage or a NSO/NRO.");
+                Logger.PrintInfo(LogClass.Application, "Please specify the folder with the NSOs/IStorage or a NSO/NRO.");
             }
 
             using (GlScreen screen = new GlScreen(device, renderer))
@@ -88,11 +87,13 @@ namespace Ryujinx
             }
 
             audioOut.Dispose();
+
+            Logger.Shutdown();
         }
 
         private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
         {
-            Log.Close();
+            Logger.Shutdown();
         }
 
         private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
@@ -103,7 +104,7 @@ namespace Ryujinx
 
             if (e.IsTerminating)
             {
-                Log.Close();
+                Logger.Shutdown();
             }
         }
 
diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf
deleted file mode 100644
index 604b14ed6..000000000
--- a/Ryujinx/Ryujinx.conf
+++ /dev/null
@@ -1,107 +0,0 @@
-#Enable cpu memory checks (slow)
-Enable_Memory_Checks = false
-
-#Dump shaders in local directory (e.g. `C:\ShaderDumps`)
-Graphics_Shaders_Dump_Path =
-
-#Enable print debug logs
-Logging_Enable_Debug = false
-
-#Enable print stubbed calls logs
-Logging_Enable_Stub = true
-
-#Enable print informations logs
-Logging_Enable_Info = true
-
-#Enable print warning logs
-Logging_Enable_Warn = true
-
-#Enable print error logs
-Logging_Enable_Error = true
-
-#Filtered log classes, seperated by ", ", eg. `Logging_Filtered_Classes = Loader, ServiceFS`
-Logging_Filtered_Classes =
-
-#Enable file logging
-Enable_File_Log = false
-
-#System Language list: https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b
-#Change System Language
-System_Language = AmericanEnglish
-
-#Enable or Disable Docked Mode
-Docked_Mode = false
-
-#Enable Game Vsync
-Enable_Vsync = true
-
-#Enable or Disable Multi-core scheduling of threads
-Enable_MultiCore_Scheduling = false
-
-#Enable integrity checks on Switch content files
-Enable_FS_Integrity_Checks = true
-
-#Controller Device Index
-GamePad_Index = 0
-
-#Controller Analog Stick Deadzone
-GamePad_Deadzone = 0.05
-
-#The value of how pressed down each trigger has to be in order to register a button press
-GamePad_Trigger_Threshold = 0.5
-
-#Whether or not to enable Controller support
-GamePad_Enable = true
-
-#The primary controller's type. Supported Values: ProController, Handheld, NpadPair, NpadLeft, NpadRight
-Controller_Type = Handheld
-
-#https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs
-Controls_Left_JoyConKeyboard_Stick_Up = 105
-Controls_Left_JoyConKeyboard_Stick_Down = 101
-Controls_Left_JoyConKeyboard_Stick_Left = 83
-Controls_Left_JoyConKeyboard_Stick_Right = 86
-Controls_Left_JoyConKeyboard_Stick_Button = 88
-Controls_Left_JoyConKeyboard_DPad_Up = 45
-Controls_Left_JoyConKeyboard_DPad_Down = 46
-Controls_Left_JoyConKeyboard_DPad_Left = 47
-Controls_Left_JoyConKeyboard_DPad_Right = 48
-Controls_Left_JoyConKeyboard_Button_Minus = 120
-Controls_Left_JoyConKeyboard_Button_L = 87
-Controls_Left_JoyConKeyboard_Button_ZL = 99
-
-Controls_Right_JoyConKeyboard_Stick_Up = 91
-Controls_Right_JoyConKeyboard_Stick_Down = 93
-Controls_Right_JoyConKeyboard_Stick_Left = 92
-Controls_Right_JoyConKeyboard_Stick_Right = 94
-Controls_Right_JoyConKeyboard_Stick_Button = 90
-Controls_Right_JoyConKeyboard_Button_A = 108
-Controls_Right_JoyConKeyboard_Button_B = 106
-Controls_Right_JoyConKeyboard_Button_X = 85
-Controls_Right_JoyConKeyboard_Button_Y = 104
-Controls_Right_JoyConKeyboard_Button_Plus = 121
-Controls_Right_JoyConKeyboard_Button_R = 103
-Controls_Right_JoyConKeyboard_Button_ZR = 97
-
-#Controller Controls
-
-Controls_Left_JoyConController_Stick_Button = LStick
-Controls_Left_JoyConController_DPad_Up = DPadUp
-Controls_Left_JoyConController_DPad_Down = DPadDown
-Controls_Left_JoyConController_DPad_Left = DPadLeft
-Controls_Left_JoyConController_DPad_Right = DPadRight
-Controls_Left_JoyConController_Button_Minus = Back
-Controls_Left_JoyConController_Button_L = LShoulder
-Controls_Left_JoyConController_Button_ZL = LTrigger
-
-Controls_Right_JoyConController_Stick_Button = RStick
-Controls_Right_JoyConController_Button_A = B
-Controls_Right_JoyConController_Button_B = A
-Controls_Right_JoyConController_Button_X = Y
-Controls_Right_JoyConController_Button_Y = X
-Controls_Right_JoyConController_Button_Plus = Start
-Controls_Right_JoyConController_Button_R = RShoulder
-Controls_Right_JoyConController_Button_ZR = RTrigger
-
-Controls_Left_JoyConController_Stick = LJoystick
-Controls_Right_JoyConController_Stick = RJoystick
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 1789ef2e9..087258464 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -20,7 +20,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <None Update="Ryujinx.conf">
+    <None Update="Config.jsonc">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
   </ItemGroup>
diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs
index e3a6d299b..c7aaea239 100644
--- a/Ryujinx/Ui/GLScreen.cs
+++ b/Ryujinx/Ui/GLScreen.cs
@@ -142,24 +142,24 @@ namespace Ryujinx
             {
                 KeyboardState keyboard = _keyboard.Value;
 
-                currentButton = Config.NpadKeyboard.GetButtons(keyboard);
+                currentButton = Configuration.Instance.KeyboardControls.GetButtons(keyboard);
 
-                (leftJoystickDx, leftJoystickDy) = Config.NpadKeyboard.GetLeftStick(keyboard);
+                (leftJoystickDx, leftJoystickDy) = Configuration.Instance.KeyboardControls.GetLeftStick(keyboard);
 
-                (rightJoystickDx, rightJoystickDy) = Config.NpadKeyboard.GetRightStick(keyboard);
+                (rightJoystickDx, rightJoystickDy) = Configuration.Instance.KeyboardControls.GetRightStick(keyboard);
             }
             
-            currentButton |= Config.NpadController.GetButtons();
+            currentButton |= Configuration.Instance.GamepadControls.GetButtons();
 
             //Keyboard has priority stick-wise
             if (leftJoystickDx == 0 && leftJoystickDy == 0)
             {
-                (leftJoystickDx, leftJoystickDy) = Config.NpadController.GetLeftStick();
+                (leftJoystickDx, leftJoystickDy) = Configuration.Instance.GamepadControls.GetLeftStick();
             }
 
             if (rightJoystickDx == 0 && rightJoystickDy == 0)
             {
-                (rightJoystickDx, rightJoystickDy) = Config.NpadController.GetRightStick();
+                (rightJoystickDx, rightJoystickDy) = Configuration.Instance.GamepadControls.GetRightStick();
             }
 
             leftJoystick = new HidJoystickPosition
diff --git a/Ryujinx/Ui/Log.cs b/Ryujinx/Ui/Log.cs
deleted file mode 100644
index 5daae1402..000000000
--- a/Ryujinx/Ui/Log.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using Ryujinx.Common.Logging;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Text;
-using System.Threading;
-
-namespace Ryujinx
-{
-    static class Log
-    {
-        private static readonly string _path;
-
-        private static StreamWriter _logWriter;
-
-        private static Thread _messageThread;
-
-        private static BlockingCollection<LogEventArgs> _messageQueue;
-
-        private static Dictionary<LogLevel, ConsoleColor> _logColors;
-
-        static Log()
-        {
-            _logColors = new Dictionary<LogLevel, ConsoleColor>()
-            {
-                { LogLevel.Stub,    ConsoleColor.DarkGray },
-                { LogLevel.Info,    ConsoleColor.White    },
-                { LogLevel.Warning, ConsoleColor.Yellow   },
-                { LogLevel.Error,   ConsoleColor.Red      }
-            };
-
-            _messageQueue = new BlockingCollection<LogEventArgs>(10);
-
-            _messageThread = new Thread(() =>
-            {
-                while (!_messageQueue.IsCompleted)
-                {
-                    try
-                    {
-                        PrintLog(_messageQueue.Take());
-                    }
-                    catch (InvalidOperationException)
-                    {
-                        // IOE means that Take() was called on a completed collection.
-                        // Some other thread can call CompleteAdding after we pass the
-                        // IsCompleted check but before we call Take.
-                        // We can simply catch the exception since the loop will break
-                        // on the next iteration.
-                    }
-                }
-            });
-
-            _path = Path.Combine(Environment.CurrentDirectory, "Ryujinx.log");
-
-            if (Logger.EnableFileLog)
-            {
-                _logWriter = new StreamWriter(File.Open(_path,FileMode.Create, FileAccess.Write));
-            }
-
-            _messageThread.IsBackground = true;
-            _messageThread.Start();
-        }
-
-        private static void PrintLog(LogEventArgs e)
-        {
-            StringBuilder sb = new StringBuilder();
-
-            sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
-            sb.Append(" | ");
-            sb.AppendFormat("{0:d4}", e.ThreadId);
-            sb.Append(' ');
-            sb.Append(e.Message);
-
-            if (e.Data != null)
-            {
-                PropertyInfo[] props = e.Data.GetType().GetProperties();
-
-                sb.Append(' ');
-
-                foreach (var prop in props)
-                {
-                    sb.Append(prop.Name);
-                    sb.Append(": ");
-                    sb.Append(prop.GetValue(e.Data));
-                    sb.Append(" - ");
-                }
-
-                // We remove the final '-' from the string
-                if (props.Length > 0)
-                {
-                    sb.Remove(sb.Length - 3, 3);
-                }
-            }
-
-            string message = sb.ToString();
-
-            if (_logColors.TryGetValue(e.Level, out ConsoleColor color))
-            {
-                Console.ForegroundColor = color;
-
-                Console.WriteLine(message);
-
-                Console.ResetColor();
-            }
-            else
-            {
-                Console.WriteLine(message);
-            }
-
-            if (Logger.EnableFileLog)
-            {
-                _logWriter.WriteLine(message);
-            }
-        }
-
-        public static void LogMessage(object sender, LogEventArgs e)
-        {
-            if (!_messageQueue.IsAddingCompleted)
-            {
-                _messageQueue.Add(e);
-            }
-        }
-
-        public static void Close()
-        {
-            _messageQueue.CompleteAdding();
-
-            _messageThread.Join();
-
-            if (Logger.EnableFileLog)
-            {
-                _logWriter.Flush();
-                _logWriter.Close();
-                _logWriter.Dispose();
-            }
-        }
-    }
-}
diff --git a/Ryujinx/Ui/NpadController.cs b/Ryujinx/Ui/NpadController.cs
index 58d2d46a2..1b677c4f8 100644
--- a/Ryujinx/Ui/NpadController.cs
+++ b/Ryujinx/Ui/NpadController.cs
@@ -8,28 +8,24 @@ namespace Ryujinx.UI.Input
     public enum ControllerInputId
     {
         Invalid,
-
         LStick,
+        RStick,
+        LShoulder,
+        RShoulder,
+        LTrigger,
+        RTrigger,
+        LJoystick,
+        RJoystick,
         DPadUp,
         DPadDown,
         DPadLeft,
         DPadRight,
+        Start,
         Back,
-        LShoulder,
-
-        RStick,
         A,
         B,
         X,
-        Y,
-        Start,
-        RShoulder,
-
-        LTrigger,
-        RTrigger,
-
-        LJoystick,
-        RJoystick
+        Y
     }
 
     public struct NpadControllerLeft
@@ -60,34 +56,55 @@ namespace Ryujinx.UI.Input
 
     public class NpadController
     {
-        public bool  Enabled          { private set; get; }
-        public int   Index            { private set; get; }
-        public float Deadzone         { private set; get; }
-        public float TriggerThreshold { private set; get; }
+        /// <summary>
+        /// Enables or disables controller support
+        /// </summary>
+        public bool Enabled { get; private set; }
 
-        public NpadControllerLeft  Left  { private set; get; }
-        public NpadControllerRight Right { private set; get; }
+        /// <summary>
+        /// Controller Device Index
+        /// </summary>
+        public int Index { get; private set; }
+
+        /// <summary>
+        /// Controller Analog Stick Deadzone
+        /// </summary>
+        public float Deadzone { get; private set; }
+
+        /// <summary>
+        /// Controller Trigger Threshold
+        /// </summary>
+        public float TriggerThreshold { get; private set; }
+
+        /// <summary>
+        /// Left JoyCon Controller Bindings
+        /// </summary>
+        public NpadControllerLeft LeftJoycon { get; private set; }
+
+        /// <summary>
+        /// Right JoyCon Controller Bindings
+        /// </summary>
+        public NpadControllerRight RightJoycon { get; private set; }
 
         public NpadController(
-            bool                  enabled,
-            int                   index,
-            float                 deadzone,
-            float                 triggerThreshold,
-            NpadControllerLeft    left,
-            NpadControllerRight   right)
+            bool                enabled,
+            int                 index,
+            float               deadzone,
+            float               triggerThreshold,
+            NpadControllerLeft  leftJoycon,
+            NpadControllerRight rightJoycon)
         {
             Enabled          = enabled;
             Index            = index;
             Deadzone         = deadzone;
             TriggerThreshold = triggerThreshold;
-            Left             = left;
-            Right            = right;
+            LeftJoycon       = leftJoycon;
+            RightJoycon      = rightJoycon;
+        }
 
-            //Unmapped controllers are problematic, skip them
-            if (GamePad.GetName(index) == "Unmapped Controller")
-            {
-                Enabled = false;
-            }
+        public void SetEnabled(bool enabled)
+        {
+            Enabled = enabled;
         }
 
         public HidControllerButtons GetButtons()
@@ -101,23 +118,23 @@ namespace Ryujinx.UI.Input
 
             HidControllerButtons buttons = 0;
 
-            if (IsPressed(gpState, Left.DPadUp))       buttons |= HidControllerButtons.DpadUp;
-            if (IsPressed(gpState, Left.DPadDown))     buttons |= HidControllerButtons.DpadDown;
-            if (IsPressed(gpState, Left.DPadLeft))     buttons |= HidControllerButtons.DpadLeft;
-            if (IsPressed(gpState, Left.DPadRight))    buttons |= HidControllerButtons.DPadRight;
-            if (IsPressed(gpState, Left.StickButton))  buttons |= HidControllerButtons.StickLeft;
-            if (IsPressed(gpState, Left.ButtonMinus))  buttons |= HidControllerButtons.Minus;
-            if (IsPressed(gpState, Left.ButtonL))      buttons |= HidControllerButtons.L;
-            if (IsPressed(gpState, Left.ButtonZl))     buttons |= HidControllerButtons.Zl;
+            if (IsPressed(gpState, LeftJoycon.DPadUp))       buttons |= HidControllerButtons.DpadUp;
+            if (IsPressed(gpState, LeftJoycon.DPadDown))     buttons |= HidControllerButtons.DpadDown;
+            if (IsPressed(gpState, LeftJoycon.DPadLeft))     buttons |= HidControllerButtons.DpadLeft;
+            if (IsPressed(gpState, LeftJoycon.DPadRight))    buttons |= HidControllerButtons.DPadRight;
+            if (IsPressed(gpState, LeftJoycon.StickButton))  buttons |= HidControllerButtons.StickLeft;
+            if (IsPressed(gpState, LeftJoycon.ButtonMinus))  buttons |= HidControllerButtons.Minus;
+            if (IsPressed(gpState, LeftJoycon.ButtonL))      buttons |= HidControllerButtons.L;
+            if (IsPressed(gpState, LeftJoycon.ButtonZl))     buttons |= HidControllerButtons.Zl;
 
-            if (IsPressed(gpState, Right.ButtonA))     buttons |= HidControllerButtons.A;
-            if (IsPressed(gpState, Right.ButtonB))     buttons |= HidControllerButtons.B;
-            if (IsPressed(gpState, Right.ButtonX))     buttons |= HidControllerButtons.X;
-            if (IsPressed(gpState, Right.ButtonY))     buttons |= HidControllerButtons.Y;
-            if (IsPressed(gpState, Right.StickButton)) buttons |= HidControllerButtons.StickRight;
-            if (IsPressed(gpState, Right.ButtonPlus))  buttons |= HidControllerButtons.Plus;
-            if (IsPressed(gpState, Right.ButtonR))     buttons |= HidControllerButtons.R;
-            if (IsPressed(gpState, Right.ButtonZr))    buttons |= HidControllerButtons.Zr;
+            if (IsPressed(gpState, RightJoycon.ButtonA))     buttons |= HidControllerButtons.A;
+            if (IsPressed(gpState, RightJoycon.ButtonB))     buttons |= HidControllerButtons.B;
+            if (IsPressed(gpState, RightJoycon.ButtonX))     buttons |= HidControllerButtons.X;
+            if (IsPressed(gpState, RightJoycon.ButtonY))     buttons |= HidControllerButtons.Y;
+            if (IsPressed(gpState, RightJoycon.StickButton)) buttons |= HidControllerButtons.StickRight;
+            if (IsPressed(gpState, RightJoycon.ButtonPlus))  buttons |= HidControllerButtons.Plus;
+            if (IsPressed(gpState, RightJoycon.ButtonR))     buttons |= HidControllerButtons.R;
+            if (IsPressed(gpState, RightJoycon.ButtonZr))    buttons |= HidControllerButtons.Zr;
 
             return buttons;
         }
@@ -129,7 +146,7 @@ namespace Ryujinx.UI.Input
                 return (0, 0);
             }
 
-            return GetStick(Left.Stick);
+            return GetStick(LeftJoycon.Stick);
         }
 
         public (short, short) GetRightStick()
@@ -139,7 +156,7 @@ namespace Ryujinx.UI.Input
                 return (0, 0);
             }
 
-            return GetStick(Right.Stick);
+            return GetStick(RightJoycon.Stick);
         }
 
         private (short, short) GetStick(ControllerInputId joystick)
diff --git a/Ryujinx/Ui/NpadKeyboard.cs b/Ryujinx/Ui/NpadKeyboard.cs
index 704c61abc..1604da5bd 100644
--- a/Ryujinx/Ui/NpadKeyboard.cs
+++ b/Ryujinx/Ui/NpadKeyboard.cs
@@ -5,70 +5,69 @@ namespace Ryujinx.UI.Input
 {
     public struct NpadKeyboardLeft
     {
-        public int StickUp;
-        public int StickDown;
-        public int StickLeft;
-        public int StickRight;
-        public int StickButton;
-        public int DPadUp;
-        public int DPadDown;
-        public int DPadLeft;
-        public int DPadRight;
-        public int ButtonMinus;
-        public int ButtonL;
-        public int ButtonZl;
+        public Key StickUp;
+        public Key StickDown;
+        public Key StickLeft;
+        public Key StickRight;
+        public Key StickButton;
+        public Key DPadUp;
+        public Key DPadDown;
+        public Key DPadLeft;
+        public Key DPadRight;
+        public Key ButtonMinus;
+        public Key ButtonL;
+        public Key ButtonZl;
     }
 
     public struct NpadKeyboardRight
     {
-        public int StickUp;
-        public int StickDown;
-        public int StickLeft;
-        public int StickRight;
-        public int StickButton;
-        public int ButtonA;
-        public int ButtonB;
-        public int ButtonX;
-        public int ButtonY;
-        public int ButtonPlus;
-        public int ButtonR;
-        public int ButtonZr;
+        public Key StickUp;
+        public Key StickDown;
+        public Key StickLeft;
+        public Key StickRight;
+        public Key StickButton;
+        public Key ButtonA;
+        public Key ButtonB;
+        public Key ButtonX;
+        public Key ButtonY;
+        public Key ButtonPlus;
+        public Key ButtonR;
+        public Key ButtonZr;
     }
 
     public class NpadKeyboard
     {
-        public NpadKeyboardLeft  Left;
-        public NpadKeyboardRight Right;
+        /// <summary>
+        /// Left JoyCon Keyboard Bindings
+        /// </summary>
+        public NpadKeyboardLeft LeftJoycon { get; private set; }
 
-        public NpadKeyboard(
-            NpadKeyboardLeft  left,
-            NpadKeyboardRight right)
-        {
-            Left  = left;
-            Right = right;
-        }
+        /// <summary>
+        /// Right JoyCon Keyboard Bindings
+        /// </summary>
+        public NpadKeyboardRight RightJoycon { get; private set; }
 
         public HidControllerButtons GetButtons(KeyboardState keyboard)
         {
             HidControllerButtons buttons = 0;
 
-            if (keyboard[(Key)Left.StickButton]) buttons |= HidControllerButtons.StickLeft;
-            if (keyboard[(Key)Left.DPadUp])      buttons |= HidControllerButtons.DpadUp;
-            if (keyboard[(Key)Left.DPadDown])    buttons |= HidControllerButtons.DpadDown;
-            if (keyboard[(Key)Left.DPadLeft])    buttons |= HidControllerButtons.DpadLeft;
-            if (keyboard[(Key)Left.DPadRight])   buttons |= HidControllerButtons.DPadRight;
-            if (keyboard[(Key)Left.ButtonMinus]) buttons |= HidControllerButtons.Minus;
-            if (keyboard[(Key)Left.ButtonL])     buttons |= HidControllerButtons.L;
-            if (keyboard[(Key)Left.ButtonZl])    buttons |= HidControllerButtons.Zl;
+            if (keyboard[(Key)LeftJoycon.StickButton]) buttons |= HidControllerButtons.StickLeft;
+            if (keyboard[(Key)LeftJoycon.DPadUp])      buttons |= HidControllerButtons.DpadUp;
+            if (keyboard[(Key)LeftJoycon.DPadDown])    buttons |= HidControllerButtons.DpadDown;
+            if (keyboard[(Key)LeftJoycon.DPadLeft])    buttons |= HidControllerButtons.DpadLeft;
+            if (keyboard[(Key)LeftJoycon.DPadRight])   buttons |= HidControllerButtons.DPadRight;
+            if (keyboard[(Key)LeftJoycon.ButtonMinus]) buttons |= HidControllerButtons.Minus;
+            if (keyboard[(Key)LeftJoycon.ButtonL])     buttons |= HidControllerButtons.L;
+            if (keyboard[(Key)LeftJoycon.ButtonZl])    buttons |= HidControllerButtons.Zl;
             
-            if (keyboard[(Key)Right.StickButton]) buttons |= HidControllerButtons.StickRight;
-            if (keyboard[(Key)Right.ButtonA])     buttons |= HidControllerButtons.A;
-            if (keyboard[(Key)Right.ButtonB])     buttons |= HidControllerButtons.B;
-            if (keyboard[(Key)Right.ButtonX])     buttons |= HidControllerButtons.X;
-            if (keyboard[(Key)Right.ButtonY])     buttons |= HidControllerButtons.Y;
-            if (keyboard[(Key)Right.ButtonPlus])  buttons |= HidControllerButtons.Plus;
-            if (keyboard[(Key)Right.ButtonR])     buttons |= HidControllerButtons.R;
-            if (keyboard[(Key)Right.ButtonZr])    buttons |= HidControllerButtons.Zr;
+            if (keyboard[(Key)RightJoycon.StickButton]) buttons |= HidControllerButtons.StickRight;
+            if (keyboard[(Key)RightJoycon.ButtonA])     buttons |= HidControllerButtons.A;
+            if (keyboard[(Key)RightJoycon.ButtonB])     buttons |= HidControllerButtons.B;
+            if (keyboard[(Key)RightJoycon.ButtonX])     buttons |= HidControllerButtons.X;
+            if (keyboard[(Key)RightJoycon.ButtonY])     buttons |= HidControllerButtons.Y;
+            if (keyboard[(Key)RightJoycon.ButtonPlus])  buttons |= HidControllerButtons.Plus;
+            if (keyboard[(Key)RightJoycon.ButtonR])     buttons |= HidControllerButtons.R;
+            if (keyboard[(Key)RightJoycon.ButtonZr])    buttons |= HidControllerButtons.Zr;
 
             return buttons;
         }
@@ -78,10 +77,10 @@ namespace Ryujinx.UI.Input
             short dx = 0;
             short dy = 0;
             
-            if (keyboard[(Key)Left.StickUp])    dy =  short.MaxValue;
-            if (keyboard[(Key)Left.StickDown])  dy = -short.MaxValue;
-            if (keyboard[(Key)Left.StickLeft])  dx = -short.MaxValue;
-            if (keyboard[(Key)Left.StickRight]) dx =  short.MaxValue;
+            if (keyboard[(Key)LeftJoycon.StickUp])    dy =  short.MaxValue;
+            if (keyboard[(Key)LeftJoycon.StickDown])  dy = -short.MaxValue;
+            if (keyboard[(Key)LeftJoycon.StickLeft])  dx = -short.MaxValue;
+            if (keyboard[(Key)LeftJoycon.StickRight]) dx =  short.MaxValue;
 
             return (dx, dy);
         }
@@ -91,10 +90,10 @@ namespace Ryujinx.UI.Input
             short dx = 0;
             short dy = 0;
 
-            if (keyboard[(Key)Right.StickUp])    dy =  short.MaxValue;
-            if (keyboard[(Key)Right.StickDown])  dy = -short.MaxValue;
-            if (keyboard[(Key)Right.StickLeft])  dx = -short.MaxValue;
-            if (keyboard[(Key)Right.StickRight]) dx =  short.MaxValue;
+            if (keyboard[(Key)RightJoycon.StickUp])    dy =  short.MaxValue;
+            if (keyboard[(Key)RightJoycon.StickDown])  dy = -short.MaxValue;
+            if (keyboard[(Key)RightJoycon.StickLeft])  dx = -short.MaxValue;
+            if (keyboard[(Key)RightJoycon.StickRight]) dx =  short.MaxValue;
 
             return (dx, dy);
         }
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
new file mode 100644
index 000000000..28f351118
--- /dev/null
+++ b/Ryujinx/_schema.json
@@ -0,0 +1,823 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "https://ryujinx.org/_schema/config.json",
+  "type": "object",
+  "title": "Ryujinx Configuration Schema",
+  "required": [
+    "graphics_shaders_dump_path",
+    "logging_enable_debug",
+    "logging_enable_stub",
+    "logging_enable_info",
+    "logging_enable_warn",
+    "logging_enable_error",
+    "logging_filtered_classes",
+    "enable_file_log",
+    "system_language",
+    "docked_mode",
+    "enable_vsync",
+    "enable_multicore_scheduling",
+    "enable_fs_integrity_checks",
+    "controller_type",
+    "keyboard_controls",
+    "gamepad_controls"
+  ],
+  "definitions": {
+    "key": {
+      "type": "string",
+      "enum": [
+        "ShiftLeft",
+        "LShift",
+        "ShiftRight",
+        "RShift",
+        "ControlLeft",
+        "LControl",
+        "ControlRight",
+        "RControl",
+        "AltLeft",
+        "LAlt",
+        "AltRight",
+        "RAlt",
+        "WinLeft",
+        "LWin",
+        "WinRight",
+        "RWin",
+        "Menu",
+        "F1",
+        "F2",
+        "F3",
+        "F4",
+        "F5",
+        "F6",
+        "F7",
+        "F8",
+        "F9",
+        "F10",
+        "F11",
+        "F12",
+        "F13",
+        "F14",
+        "F15",
+        "F16",
+        "F17",
+        "F18",
+        "F19",
+        "F20",
+        "F21",
+        "F22",
+        "F23",
+        "F24",
+        "F25",
+        "F26",
+        "F27",
+        "F28",
+        "F29",
+        "F30",
+        "F31",
+        "F32",
+        "F33",
+        "F34",
+        "F35",
+        "Up",
+        "Down",
+        "Left",
+        "Right",
+        "Enter",
+        "Escape",
+        "Space",
+        "Tab",
+        "BackSpace",
+        "Back",
+        "Insert",
+        "Delete",
+        "PageUp",
+        "PageDown",
+        "Home",
+        "End",
+        "CapsLock",
+        "ScrollLock",
+        "PrintScreen",
+        "Pause",
+        "NumLock",
+        "Clear",
+        "Sleep",
+        "Keypad0",
+        "Keypad1",
+        "Keypad2",
+        "Keypad3",
+        "Keypad4",
+        "Keypad5",
+        "Keypad6",
+        "Keypad7",
+        "Keypad8",
+        "Keypad9",
+        "KeypadDivide",
+        "KeypadMultiply",
+        "KeypadSubtract",
+        "KeypadMinus",
+        "KeypadAdd",
+        "KeypadPlus",
+        "KeypadDecimal",
+        "KeypadPeriod",
+        "KeypadEnter",
+        "A",
+        "B",
+        "C",
+        "D",
+        "E",
+        "F",
+        "G",
+        "H",
+        "I",
+        "J",
+        "K",
+        "L",
+        "M",
+        "N",
+        "O",
+        "P",
+        "Q",
+        "R",
+        "S",
+        "T",
+        "U",
+        "V",
+        "W",
+        "X",
+        "Y",
+        "Z",
+        "Number0",
+        "Number1",
+        "Number2",
+        "Number3",
+        "Number4",
+        "Number5",
+        "Number6",
+        "Number7",
+        "Number8",
+        "Number9",
+        "Tilde",
+        "Grave",
+        "Minus",
+        "Plus",
+        "BracketLeft",
+        "LBracket",
+        "BracketRight",
+        "RBracket",
+        "Semicolon",
+        "Quote",
+        "Comma",
+        "Period",
+        "Slash",
+        "BackSlash",
+        "NonUSBackSlash",
+        "LastKey"
+      ]
+    },
+    "input": {
+      "type": "string",
+      "enum": [
+        "DPadUp",
+        "DPadDown",
+        "DPadLeft",
+        "DPadRight",
+        "LStick",
+        "RStick",
+        "LShoulder",
+        "RShoulder",
+        "LTrigger",
+        "RTrigger",
+        "LJoystick",
+        "RJoystick",
+        "A",
+        "B",
+        "X",
+        "Y",
+        "Start",
+        "Back"
+      ]
+    }
+  },
+  "properties": {
+    "graphics_shaders_dump_path": {
+      "$id": "#/properties/graphics_shaders_dump_path",
+      "type": "string",
+      "title": "Graphics Shaders Dump Path",
+      "description": "Dumps shaders in this local directory",
+      "default": "",
+      "examples": [
+        "C:\\ShaderDumps"
+      ]
+    },
+    "logging_enable_debug": {
+      "$id": "#/properties/logging_enable_debug",
+      "type": "boolean",
+      "title": "Logging Enable Debug",
+      "description": "Enables printing debug log messages",
+      "default": false,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "logging_enable_stub": {
+      "$id": "#/properties/logging_enable_stub",
+      "type": "boolean",
+      "title": "Logging Enable Stub",
+      "description": "Enables printing stub log messages",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "logging_enable_info": {
+      "$id": "#/properties/logging_enable_info",
+      "type": "boolean",
+      "title": "Logging Enable Info",
+      "description": "Enables printing info log messages",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "logging_enable_warn": {
+      "$id": "#/properties/logging_enable_warn",
+      "type": "boolean",
+      "title": "Logging Enable Warn",
+      "description": "Enables printing warning log messages",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "logging_enable_error": {
+      "$id": "#/properties/logging_enable_error",
+      "type": "boolean",
+      "title": "Logging Enable Error",
+      "description": "Enables printing error log messages",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "logging_filtered_classes": {
+      "$id": "#/properties/logging_filtered_classes",
+      "type": "array",
+      "title": "Logging Filtered Classes",
+      "description": "Controls which log messages are written to the log targets",
+      "items": {
+        "type": "string",
+        "enum": [
+          "Application",
+          "Audio",
+          "Cpu",
+          "Font",
+          "Emulation",
+          "Gpu",
+          "Hid",
+          "Kernel",
+          "KernelIpc",
+          "KernelScheduler",
+          "KernelSvc",
+          "Loader",
+          "Service",
+          "ServiceAcc",
+          "ServiceAm",
+          "ServiceApm",
+          "ServiceAudio",
+          "ServiceBsd",
+          "ServiceCaps",
+          "ServiceFriend",
+          "ServiceFs",
+          "ServiceHid",
+          "ServiceIrs",
+          "ServiceLdr",
+          "ServiceLm",
+          "ServiceMm",
+          "ServiceNfp",
+          "ServiceNifm",
+          "ServiceNs",
+          "ServiceNv",
+          "ServicePctl",
+          "ServicePl",
+          "ServicePrepo",
+          "ServicePsm",
+          "ServiceSet",
+          "ServiceSfdnsres",
+          "ServiceSm",
+          "ServiceSsl",
+          "ServiceSss",
+          "ServiceTime",
+          "ServiceVi"
+        ]
+      }
+    },
+    "enable_file_log": {
+      "$id": "#/properties/enable_file_log",
+      "type": "boolean",
+      "title": "Enable File Log",
+      "description": "Enables logging to a file on disk",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "system_language": {
+      "$id": "#/properties/system_language",
+      "type": "string",
+      "title": "System Language",
+      "description": "Change System Language",
+      "default": "AmericanEnglish",
+      "enum": [
+        "Japanese",
+        "AmericanEnglish",
+        "French",
+        "German",
+        "Italian",
+        "Spanish",
+        "Chinese",
+        "Korean",
+        "Dutch",
+        "Portuguese",
+        "Russian",
+        "Taiwanese",
+        "BritishEnglish",
+        "CanadianFrench",
+        "LatinAmericanSpanish",
+        "SimplifiedChinese",
+        "TraditionalChinese"
+      ],
+      "examples": [
+        "AmericanEnglish"
+      ]
+    },
+    "docked_mode": {
+      "$id": "#/properties/docked_mode",
+      "type": "boolean",
+      "title": "Enable Docked Mode",
+      "description": "Enables or disables Docked Mode",
+      "default": false,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "enable_vsync": {
+      "$id": "#/properties/enable_vsync",
+      "type": "boolean",
+      "title": "Enable Vertical Sync",
+      "description": "Enables or disables Vertical Sync",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "enable_multicore_scheduling": {
+      "$id": "#/properties/enable_multicore_scheduling",
+      "type": "boolean",
+      "title": "Enable Multicore Scheduling",
+      "description": "Enables or disables multi-core scheduling of threads",
+      "default": false,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "enable_fs_integrity_checks": {
+      "$id": "#/properties/enable_fs_integrity_checks",
+      "type": "boolean",
+      "title": "Enable Filesystem Integrity Checks",
+      "description": "Enables integrity checks on Game content files. Only applies to ROMs loaded as XCI files",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
+    "controller_type": {
+      "$id": "#/properties/controller_type",
+      "type": "string",
+      "title": "Controller Type",
+      "default": "Handheld",
+      "enum": [
+        "Handheld",
+        "ProController",
+        "NpadPair",
+        "NpadLeft",
+        "NpadRight"
+      ],
+      "examples": [
+        "Handheld",
+        "ProController",
+        "NpadPair",
+        "NpadLeft",
+        "NpadRight"
+      ]
+    },
+    "keyboard_controls": {
+      "$id": "#/properties/keyboard_controls",
+      "type": "object",
+      "title": "Keyboard Controls",
+      "required": [
+        "left_joycon",
+        "right_joycon"
+      ],
+      "properties": {
+        "left_joycon": {
+          "$id": "#/properties/keyboard_controls/properties/left_joycon",
+          "type": "object",
+          "title": "Left JoyCon Controls",
+          "required": [
+            "stick_up",
+            "stick_down",
+            "stick_left",
+            "stick_right",
+            "stick_button",
+            "dpad_up",
+            "dpad_down",
+            "dpad_left",
+            "dpad_right",
+            "button_minus",
+            "button_l",
+            "button_zl"
+          ],
+          "properties": {
+            "stick_up": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_up",
+              "$ref": "#/definitions/key",
+              "title": "Stick Up",
+              "default": "w"
+            },
+            "stick_down": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_down",
+              "$ref": "#/definitions/key",
+              "title": "Stick Down",
+              "default": "S"
+            },
+            "stick_left": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_left",
+              "$ref": "#/definitions/key",
+              "title": "Stick Left",
+              "default": "A"
+            },
+            "stick_right": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_right",
+              "$ref": "#/definitions/key",
+              "title": "Stick Right",
+              "default": "D"
+            },
+            "stick_button": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_button",
+              "$ref": "#/definitions/key",
+              "title": "Stick Button",
+              "default": "F"
+            },
+            "dpad_up": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_up",
+              "$ref": "#/definitions/key",
+              "title": "Dpad Up",
+              "default": "Up"
+            },
+            "dpad_down": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_down",
+              "$ref": "#/definitions/key",
+              "title": "Dpad Down",
+              "default": "Down"
+            },
+            "dpad_left": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_left",
+              "$ref": "#/definitions/key",
+              "title": "Dpad Left",
+              "default": "Left"
+            },
+            "dpad_right": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_right",
+              "$ref": "#/definitions/key",
+              "title": "Dpad Right",
+              "default": "Right"
+            },
+            "button_minus": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_minus",
+              "$ref": "#/definitions/key",
+              "title": "Button Minus",
+              "default": "Minus"
+            },
+            "button_l": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_l",
+              "$ref": "#/definitions/key",
+              "title": "Button L",
+              "default": "E"
+            },
+            "button_zl": {
+              "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_zl",
+              "$ref": "#/definitions/key",
+              "title": "Button ZL",
+              "default": "Q"
+            }
+          }
+        },
+        "right_joycon": {
+          "$id": "#/properties/keyboard_controls/properties/right_joycon",
+          "type": "object",
+          "title": "Right JoyCon Controls",
+          "required": [
+            "stick_up",
+            "stick_down",
+            "stick_left",
+            "stick_right",
+            "stick_button",
+            "button_a",
+            "button_b",
+            "button_x",
+            "button_y",
+            "button_plus",
+            "button_r",
+            "button_zr"
+          ],
+          "properties": {
+            "stick_up": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_up",
+              "$ref": "#/definitions/key",
+              "title": "Stick Up",
+              "default": "I"
+            },
+            "stick_down": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_down",
+              "$ref": "#/definitions/key",
+              "title": "Stick Down",
+              "default": "K"
+            },
+            "stick_left": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_left",
+              "$ref": "#/definitions/key",
+              "title": "Stick Left",
+              "default": "J"
+            },
+            "stick_right": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_right",
+              "$ref": "#/definitions/key",
+              "title": "Stick Right",
+              "default": "L"
+            },
+            "stick_button": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_button",
+              "$ref": "#/definitions/key",
+              "title": "Stick Button",
+              "default": "H"
+            },
+            "button_a": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_a",
+              "$ref": "#/definitions/key",
+              "title": "Button A",
+              "default": "Z"
+            },
+            "button_b": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_b",
+              "$ref": "#/definitions/key",
+              "title": "Button B",
+              "default": "X"
+            },
+            "button_x": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_x",
+              "$ref": "#/definitions/key",
+              "title": "Button X",
+              "default": "C"
+            },
+            "button_y": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_y",
+              "$ref": "#/definitions/key",
+              "title": "Button Y",
+              "default": "V"
+            },
+            "button_plus": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_plus",
+              "$ref": "#/definitions/key",
+              "title": "Button Plus",
+              "default": "Plus"
+            },
+            "button_r": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_r",
+              "$ref": "#/definitions/key",
+              "title": "Button R",
+              "default": "U"
+            },
+            "button_zr": {
+              "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_zr",
+              "$ref": "#/definitions/key",
+              "title": "Button Zr",
+              "default": "O"
+            }
+          }
+        }
+      }
+    },
+    "gamepad_controls": {
+      "$id": "#/properties/gamepad_controls",
+      "type": "object",
+      "title": "GamePad Controls",
+      "required": [
+        "left_joycon",
+        "right_joycon"
+      ],
+      "properties": {
+        "enable": {
+          "$id": "#/properties/gamepad_controls/properties/enable",
+          "type": "boolean",
+          "title": "Gamepad Enable",
+          "description": "Enables or disables controller support",
+          "default": true,
+          "examples": [
+            true,
+            false
+          ]
+        },
+        "index": {
+          "$id": "#/properties/gamepad_controls/properties/index",
+          "type": "integer",
+          "title": "Gamepad Index",
+          "description": "Controller Device Index",
+          "default": 0,
+          "minimum": 0,
+          "examples": [
+            0,
+            1,
+            2
+          ]
+        },
+        "deadzone": {
+          "$id": "#/properties/gamepad_controls/properties/deadzone",
+          "type": "number",
+          "title": "Gamepad Deadzone",
+          "description": "Controller Analog Stick Deadzone",
+          "default": 0.05,
+          "minimum": -32768.0,
+          "maximum": 32767.0,
+          "examples": [
+            0.05
+          ]
+        },
+        "trigger_threshold": {
+          "$id": "#/properties/gamepad_controls/properties/trigger_threshold",
+          "type": "number",
+          "title": "Controller Trigger Threshold",
+          "description": "The value of how pressed down each trigger has to be in order to register a button press",
+          "default": 0.5,
+          "minimum": 0.0,
+          "maximum": 1.0,
+          "examples": [
+            0.5
+          ]
+        },
+        "left_joycon": {
+          "$id": "#/properties/gamepad_controls/properties/left_joycon",
+          "type": "object",
+          "title": "Left JoyCon Controls",
+          "required": [
+            "stick",
+            "stick_button",
+            "dpad_up",
+            "dpad_down",
+            "dpad_left",
+            "dpad_right",
+            "button_minus",
+            "button_l",
+            "button_zl"
+          ],
+          "properties": {
+            "stick": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/stick",
+              "$ref": "#/definitions/input",
+              "title": "Stick",
+              "default": "LJoystick"
+            },
+            "stick_button": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/stick_button",
+              "$ref": "#/definitions/input",
+              "title": "Stick Button",
+              "default": "LStick"
+            },
+            "dpad_up": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/dpad_up",
+              "$ref": "#/definitions/input",
+              "title": "Dpad Up",
+              "default": "DPadUp"
+            },
+            "dpad_down": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/dpad_down",
+              "$ref": "#/definitions/input",
+              "title": "Dpad Down",
+              "default": "DPadDown"
+            },
+            "dpad_left": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/dpad_left",
+              "$ref": "#/definitions/input",
+              "title": "Dpad Left",
+              "default": "DPadLeft"
+            },
+            "dpad_right": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/dpad_right",
+              "$ref": "#/definitions/input",
+              "title": "Dpad Right",
+              "default": "DPadRight"
+            },
+            "button_minus": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/button_minus",
+              "$ref": "#/definitions/input",
+              "title": "Button Minus",
+              "default": "Back"
+            },
+            "button_l": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/button_l",
+              "$ref": "#/definitions/input",
+              "title": "Button L",
+              "default": "LShoulder"
+            },
+            "button_zl": {
+              "$id": "#/properties/gamepad_controls/properties/left_joycon/properties/button_zl",
+              "$ref": "#/definitions/input",
+              "title": "Button ZL",
+              "default": "LTrigger"
+            }
+          }
+        },
+        "right_joycon": {
+          "$id": "#/properties/gamepad_controls/properties/right_joycon",
+          "type": "object",
+          "title": "Right JoyCon Controls",
+          "required": [
+            "stick",
+            "stick_button",
+            "button_a",
+            "button_b",
+            "button_x",
+            "button_y",
+            "button_plus",
+            "button_r",
+            "button_zr"
+          ],
+          "properties": {
+            "stick": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/stick",
+              "$ref": "#/definitions/input",
+              "title": "Stick",
+              "default": "RJoystick"
+            },
+            "stick_button": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/stick_button",
+              "$ref": "#/definitions/input",
+              "title": "Stick Button",
+              "default": "RStick"
+            },
+            "button_a": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_a",
+              "$ref": "#/definitions/input",
+              "title": "Button A",
+              "default": "B"
+            },
+            "button_b": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_b",
+              "$ref": "#/definitions/input",
+              "title": "Button B",
+              "default": "A"
+            },
+            "button_x": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_x",
+              "$ref": "#/definitions/input",
+              "title": "Button X",
+              "default": "Y"
+            },
+            "button_y": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_y",
+              "$ref": "#/definitions/input",
+              "title": "Button Y",
+              "default": "X"
+            },
+            "button_plus": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_plus",
+              "$ref": "#/definitions/input",
+              "title": "Button Plus",
+              "default": "Start"
+            },
+            "button_r": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_r",
+              "$ref": "#/definitions/input",
+              "title": "Button R",
+              "default": "RShoulder"
+            },
+            "button_zr": {
+              "$id": "#/properties/gamepad_controls/properties/right_joycon/properties/button_zr",
+              "$ref": "#/definitions/input",
+              "title": "Button ZR",
+              "default": "RTrigger"
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file