diff --git a/KEYS.md b/KEYS.md
deleted file mode 100644
index 868e1f06a4..0000000000
--- a/KEYS.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Keys
-
-Keys are required for decrypting most of the file formats used by the Nintendo Switch.
-
- Keysets are stored as text files. These 2 filenames are automatically read:  
-* `prod.keys` - Contains common keys used by all Nintendo Switch devices.
-* `title.keys` - Contains game-specific keys.
-
-Ryujinx will first look for keys in `Ryujinx/system`, and if it doesn't find any there it will look in `$HOME/.switch`.
-To dump your `prod.keys` and `title.keys` please follow these following steps.
-1.	First off learn how to boot into RCM mode and inject payloads if you haven't already. This can be done [here](https://nh-server.github.io/switch-guide/).
-2.	Make sure you have an SD card with the latest release of [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere/releases) inserted into your Nintendo Switch.
-3.	Download the latest release of [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM/releases).
-4.	Boot into RCM mode.
-5.	Inject the `Lockpick_RCM.bin` that you have downloaded at `Step 3.` using your preferred payload injector. We recommend [TegraRCMGUI](https://github.com/eliboa/TegraRcmGUI/releases) as it is easy to use and has a decent feature set.
-6.	Using the `Vol+/-` buttons to navigate and the `Power` button to select, select `Dump from SysNAND | Key generation: X` ("X" depends on your Nintendo Switch's firmware version)
-7.	The dumping process may take a while depending on how many titles you have installed.
-8.	After its completion press any button to return to the main menu of Lockpick_RCM.
-9.	Navigate to and select `Power off` if you have an SD card reader. Or you could Navigate and select `Reboot (RCM)` if you want to mount your SD card using `TegraRCMGUI > Tools > Memloader V3 > MMC - SD Card`.
-10.	You can find your keys in `sd:/switch/prod.keys` and `sd:/switch/title.keys` respectively.
-11. Copy these files and paste them in `Ryujinx/system`.
-And you're done!
-
-## Title keys
-
-These are only used for games that are not dumped from cartridges but from games downloaded from the Nintendo eShop, these are also only used if the eShop dump does *not* have a `ticket`. If the game does have a ticket, Ryujinx will read the key directly from that ticket.
-
-Title keys are stored in the format `rights_id = key`.
-
-For example:
-
-```
-01000000000100000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-01000000000108000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-01000000000108000000000000000004 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-```
-
-## Prod keys
-
-These are typically used to decrypt system files and encrypted game files. These keys get changed in about every major system update, so make sure to keep your keys up-to-date if you want to play newer games!
\ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index caa8c6f054..f8fb5599e9 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -1,11 +1,12 @@
 using ARMeilleure.Translation.PTC;
 using Gtk;
+using OpenTK;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.SystemInfo;
 using Ryujinx.Configuration;
 using Ryujinx.Ui;
-using OpenTK;
+using Ryujinx.Ui.Diagnostic;
 using System;
 using System.IO;
 using System.Reflection;
@@ -110,7 +111,7 @@ namespace Ryujinx
             bool hasAltProdKeys    = !AppDataManager.IsCustomBasePath && File.Exists(Path.Combine(AppDataManager.KeysDirPathAlt, "prod.keys"));
             if (!hasGlobalProdKeys && !hasAltProdKeys && !Migration.IsMigrationNeeded())
             {
-                GtkDialog.CreateWarningDialog("Key file was not found", "Please refer to `KEYS.md` for more info");
+                UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys);
             }
 
             MainWindow mainWindow = new MainWindow();
diff --git a/Ryujinx/Ui/AboutWindow.cs b/Ryujinx/Ui/AboutWindow.cs
index 5f1645da5c..50b0bb8a04 100644
--- a/Ryujinx/Ui/AboutWindow.cs
+++ b/Ryujinx/Ui/AboutWindow.cs
@@ -37,51 +37,35 @@ namespace Ryujinx.Ui
             _versionText.Text = Program.Version;
         }
 
-        private static void OpenUrl(string url)
-        {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-            {
-                Process.Start("xdg-open", url);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                Process.Start("open", url);
-            }
-        }
-
         //Events
         private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://ryujinx.org");
+            UrlHelper.OpenUrl("https://ryujinx.org");
         }
 
         private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://www.patreon.com/ryujinx");
+            UrlHelper.OpenUrl("https://www.patreon.com/ryujinx");
         }
 
         private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://github.com/Ryujinx/Ryujinx");
+            UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx");
         }
 
         private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://discordapp.com/invite/N2FmfVc");
+            UrlHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc");
         }
 
         private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://twitter.com/RyujinxEmu");
+            UrlHelper.OpenUrl("https://twitter.com/RyujinxEmu");
         }
 
         private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
         {
-            OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
+            UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
         }
 
         private void CloseToggle_Activated(object sender, EventArgs args)
diff --git a/Ryujinx/Ui/Diagnostic/GuideDialog.cs b/Ryujinx/Ui/Diagnostic/GuideDialog.cs
new file mode 100644
index 0000000000..c3a0dd38cc
--- /dev/null
+++ b/Ryujinx/Ui/Diagnostic/GuideDialog.cs
@@ -0,0 +1,36 @@
+using Gtk;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace Ryujinx.Ui.Diagnostic
+{
+    internal class GuideDialog : MessageDialog
+    {
+        internal static bool _isExitDialogOpen = false;
+
+        public GuideDialog(string title, string mainText, string secondaryText) : base(null, DialogFlags.Modal, MessageType.Other, ButtonsType.None, null)
+        {
+            Title = title;
+            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+            Text = mainText;
+            SecondaryText = secondaryText;
+            WindowPosition = WindowPosition.Center;
+            Response += GtkDialog_Response;
+
+            Button guideButton = new Button();
+            guideButton.Label = "Open the Setup Guide";
+
+            ContentArea.Add(guideButton);
+
+            SetSizeRequest(100, 10);
+            ShowAll();
+        }
+
+        private void GtkDialog_Response(object sender, ResponseArgs args)
+        {
+            Dispose();
+        }
+    }
+}
diff --git a/Ryujinx/Ui/Diagnostic/SetupValidator.cs b/Ryujinx/Ui/Diagnostic/SetupValidator.cs
new file mode 100644
index 0000000000..c52dc2ef3e
--- /dev/null
+++ b/Ryujinx/Ui/Diagnostic/SetupValidator.cs
@@ -0,0 +1,118 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem.Content;
+using System;
+using System.IO;
+
+namespace Ryujinx.Ui.Diagnostic
+{
+    /// <summary>
+    /// Ensure installation validity
+    /// </summary>
+    static class SetupValidator
+    {
+        public static bool IsFirmwareValid(ContentManager contentManager, out UserError error)
+        {
+            bool hasFirmware = contentManager.GetCurrentFirmwareVersion() != null;
+
+            if (hasFirmware)
+            {
+                error = UserError.Success;
+
+                return true;
+            }
+            else
+            {
+                error = UserError.NoFirmware;
+
+                return false;
+            }
+        }
+
+        public static bool CanFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out SystemVersion firmwareVersion)
+        {
+            try
+            {
+                firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+            }
+            catch (Exception)
+            {
+                firmwareVersion = null;
+            }
+
+            return error == UserError.NoFirmware && Path.GetExtension(baseApplicationPath).ToLowerInvariant() == ".xci" && firmwareVersion != null;
+        }
+
+        public static bool TryFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out UserError outError)
+        {
+            if (error == UserError.NoFirmware)
+            {
+                string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+                // If the target app to start is a XCI, try to install firmware from it
+                if (baseApplicationExtension == ".xci")
+                {
+                    SystemVersion firmwareVersion;
+
+                    try
+                    {
+                        firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+                    }
+                    catch (Exception)
+                    {
+                        firmwareVersion = null;
+                    }
+
+                    // The XCI is a valid firmware package, try to install the firmware from it!
+                    if (firmwareVersion != null)
+                    {
+                        try
+                        {
+                            Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
+
+                            contentManager.InstallFirmware(baseApplicationPath);
+
+                            Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed.");
+
+                            outError = UserError.Success;
+
+                            return true;
+                        }
+                        catch (Exception) { }
+                    }
+
+                    outError = error;
+
+                    return false;
+                }
+            }
+
+            outError = error;
+
+            return false;
+        }
+
+        public static bool CanStartApplication(ContentManager contentManager, string baseApplicationPath, out UserError error)
+        {
+            if (Directory.Exists(baseApplicationPath) || File.Exists(baseApplicationPath))
+            {
+                string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+                // NOTE: We don't force homebrew developers to install a system firmware.
+                if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso")
+                {
+                    error = UserError.Success;
+
+                    return true;
+                }
+
+                return IsFirmwareValid(contentManager, out error);
+            }
+            else
+            {
+                error = UserError.ApplicationNotFound;
+
+                return false;
+            }
+        }
+    }
+}
diff --git a/Ryujinx/Ui/Diagnostic/UserError.cs b/Ryujinx/Ui/Diagnostic/UserError.cs
new file mode 100644
index 0000000000..eaa1bc8320
--- /dev/null
+++ b/Ryujinx/Ui/Diagnostic/UserError.cs
@@ -0,0 +1,39 @@
+namespace Ryujinx.Ui.Diagnostic
+{
+    /// <summary>
+    /// Represent a common error that could be reported to the user by the emulator.
+    /// </summary>
+    public enum UserError
+    {
+        /// <summary>
+        /// No error to report.
+        /// </summary>
+        Success = 0x0,
+
+        /// <summary>
+        /// No keys are present.
+        /// </summary>
+        NoKeys = 0x1,
+
+        /// <summary>
+        /// No firmware is installed.
+        /// </summary>
+        NoFirmware = 0x2,
+
+        /// <summary>
+        /// Firmware parsing failed.
+        /// </summary>
+        /// <remarks>Most likely related to keys.</remarks>
+        FirmwareParsingFailed = 0x3,
+
+        /// <summary>
+        /// No application was found at the given path.
+        /// </summary>
+        ApplicationNotFound = 0x4,
+
+        /// <summary>
+        /// An unknown error.
+        /// </summary>
+        Unknown = 0xDEAD
+    }
+}
diff --git a/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs b/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs
new file mode 100644
index 0000000000..646e98fdcc
--- /dev/null
+++ b/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs
@@ -0,0 +1,133 @@
+using Gtk;
+using System.Reflection;
+
+namespace Ryujinx.Ui.Diagnostic
+{
+    internal class UserErrorDialog : MessageDialog
+    {
+        private static string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide";
+        private const int OkResponseId = 0;
+        private const int SetupGuideResponseId = 1;
+
+        private UserError _userError;
+
+        private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null)
+        {
+            _userError = error;
+            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+            WindowPosition = WindowPosition.Center;
+            Response += UserErrorDialog_Response;
+
+            SetSizeRequest(120, 50);
+
+            AddButton("OK", OkResponseId);
+
+            bool isInSetupGuide = IsCoveredBySetupGuide(error);
+
+            if (isInSetupGuide)
+            {
+                AddButton("Open the Setup Guide", SetupGuideResponseId);
+            }
+
+            string errorCode = GetErrorCode(error);
+
+            SecondaryUseMarkup = true;
+
+            Title = $"Ryujinx error ({errorCode})";
+            Text = $"{errorCode}: {GetErrorTitle(error)}";
+            SecondaryText = GetErrorDescription(error);
+
+            if (isInSetupGuide)
+            {
+                SecondaryText += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>";
+            }
+        }
+
+        private static string GetErrorCode(UserError error)
+        {
+            return $"RYU-{(uint)error:X4}";
+        }
+
+        private static string GetErrorTitle(UserError error)
+        {
+            switch (error)
+            {
+                case UserError.NoKeys:
+                    return "Keys not found";
+                case UserError.NoFirmware:
+                    return "Firmware not found";
+                case UserError.FirmwareParsingFailed:
+                    return "Firmware parsing error";
+                case UserError.Unknown:
+                    return "Unknown error";
+                default:
+                    return "Undefined error";
+            }
+        }
+
+        private static string GetErrorDescription(UserError error)
+        {
+            switch (error)
+            {
+                case UserError.NoKeys:
+                    return "Ryujinx was unable to find your 'prod.keys' file";
+                case UserError.NoFirmware:
+                    return "Ryujinx was unable to find any firmwares installed";
+                case UserError.FirmwareParsingFailed:
+                    return "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.";
+                case UserError.Unknown:
+                    return "An unknown error occured!";
+                default:
+                    return "An undefined error occured! This shouldn't happen, please contact a dev!";
+            }
+        }
+
+        private static bool IsCoveredBySetupGuide(UserError error)
+        {
+            switch (error)
+            {
+                case UserError.NoKeys:
+                case UserError.NoFirmware:
+                case UserError.FirmwareParsingFailed:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        private static string GetSetupGuideUrl(UserError error)
+        {
+            if (!IsCoveredBySetupGuide(error))
+            {
+                return null;
+            }
+
+            switch (error)
+            {
+                case UserError.NoKeys:
+                    return SetupGuideUrl + "#initial-setup---placement-of-prodkeys";
+                case UserError.NoFirmware:
+                    return SetupGuideUrl + "#initial-setup-continued---installation-of-firmware";
+            }
+
+            return SetupGuideUrl;
+        }
+
+        private void UserErrorDialog_Response(object sender, ResponseArgs args)
+        {
+            int responseId = (int)args.ResponseId;
+
+            if (responseId == SetupGuideResponseId)
+            {
+                UrlHelper.OpenUrl(GetSetupGuideUrl(_userError));
+            }
+
+            Dispose();
+        }
+
+        public static void CreateUserErrorDialog(UserError error)
+        {
+            new UserErrorDialog(error).Run();
+        }
+    }
+}
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 4c9381ac5d..86a11f072f 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -11,6 +11,7 @@ using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
+using Ryujinx.Ui.Diagnostic;
 using System;
 using System.Diagnostics;
 using System.IO;
@@ -360,7 +361,70 @@ namespace Ryujinx.Ui
 
                 UpdateGraphicsConfig();
 
-                Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {_contentManager.GetCurrentFirmwareVersion()?.VersionString}");
+                SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
+
+                bool isDirectory = Directory.Exists(path);
+
+                if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
+                {
+                    if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
+                    {
+                        if (userError == UserError.NoFirmware)
+                        {
+                            MessageDialog shouldInstallFirmwareDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.YesNo, null)
+                            {
+                                Title = "Ryujinx - Info",
+                                Text = "No Firmware Installed",
+                                SecondaryText = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"
+                            };
+
+                            if (shouldInstallFirmwareDialog.Run() != (int)ResponseType.Yes)
+                            {
+                                shouldInstallFirmwareDialog.Dispose();
+
+                                UserErrorDialog.CreateUserErrorDialog(userError);
+
+                                device.Dispose();
+
+                                return;
+                            }
+                            else
+                            {
+                                shouldInstallFirmwareDialog.Dispose();
+                            }
+                        }
+
+                        if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _))
+                        {
+                            UserErrorDialog.CreateUserErrorDialog(userError);
+
+                            device.Dispose();
+
+                            return;
+                        }
+
+                        // Tell the user that we installed a firmware for them.
+                        if (userError == UserError.NoFirmware)
+                        {
+                            firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
+
+                            RefreshFirmwareLabel();
+
+                            GtkDialog.CreateInfoDialog("Ryujinx - Info", $"Firmware {firmwareVersion.VersionString} was installed",
+                                                       $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start.");
+                        }
+                    }
+                    else
+                    {
+                        UserErrorDialog.CreateUserErrorDialog(userError);
+
+                        device.Dispose();
+
+                        return;
+                    }
+                }
+
+                Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
 
                 if (Directory.Exists(path))
                 {
diff --git a/Ryujinx/Ui/UrlHelper.cs b/Ryujinx/Ui/UrlHelper.cs
new file mode 100644
index 0000000000..79eacc678e
--- /dev/null
+++ b/Ryujinx/Ui/UrlHelper.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Common.Logging;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui
+{
+    static class UrlHelper
+    {
+        public static void OpenUrl(string url)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}"));
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                Process.Start("xdg-open", url);
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                Process.Start("open", url);
+            }
+            else
+            {
+                Logger.Notice.Print(LogClass.Application, $"Cannot open url \"{url}\" on this platform!");
+            }
+        }
+    }
+}