From a15459366e7cd76ddb33eb882fd117f5aa79493d Mon Sep 17 00:00:00 2001
From: MelonSpeedruns <melonspeedruns@outlook.com>
Date: Tue, 29 Sep 2020 16:05:25 -0400
Subject: [PATCH] Appveyor Ryujinx Updater (#1403)

Co-authored-by: Xpl0itR <xpl0itr@outlook.com>
---
 .../Configuration/ConfigurationFileFormat.cs  |   7 +-
 .../Configuration/ConfigurationState.cs       |  18 +
 Ryujinx.sln                                   |   1 +
 Ryujinx/Config.json                           |   3 +-
 Ryujinx/Program.cs                            |  11 +-
 Ryujinx/Ryujinx.csproj                        |   5 +-
 Ryujinx/Ui/GLRenderer.cs                      |   2 +-
 Ryujinx/Ui/GtkDialog.cs                       |  23 +-
 Ryujinx/Ui/MainWindow.cs                      |  72 ++--
 Ryujinx/Ui/MainWindow.glade                   |   6 +-
 Ryujinx/Ui/SettingsWindow.cs                  |   7 +
 Ryujinx/Ui/SettingsWindow.glade               |  18 +-
 Ryujinx/Updater/UpdateDialog.cs               |  86 +++++
 Ryujinx/Updater/UpdateDialog.glade            | 127 +++++++
 Ryujinx/Updater/Updater.cs                    | 347 ++++++++++++++++++
 Ryujinx/_schema.json                          |  13 +-
 16 files changed, 685 insertions(+), 61 deletions(-)
 create mode 100644 Ryujinx/Updater/UpdateDialog.cs
 create mode 100644 Ryujinx/Updater/UpdateDialog.glade
 create mode 100644 Ryujinx/Updater/Updater.cs

diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index ae3fa4937..cab38046e 100644
--- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 12;
+        public const int CurrentVersion = 14;
 
         public int Version { get; set; }
 
@@ -118,6 +118,11 @@ namespace Ryujinx.Configuration
         /// </summary>
         public bool EnableDiscordIntegration { get; set; }
 
+        /// <summary>
+        /// Checks for updates when Ryujinx starts when enabled
+        /// </summary>
+        public bool CheckUpdatesOnStart { get; set; }
+
         /// <summary>
         /// Enables or disables Vertical Sync
         /// </summary>
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index 7f79dd6e1..df07019dc 100644
--- a/Ryujinx.Common/Configuration/ConfigurationState.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationState.cs
@@ -343,6 +343,11 @@ namespace Ryujinx.Configuration
         /// </summary>
         public ReactiveObject<bool> EnableDiscordIntegration { get; private set; }
 
+        /// <summary>
+        /// Checks for updates when Ryujinx starts when enabled
+        /// </summary>
+        public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; }
+
         private ConfigurationState()
         {
             Ui                       = new UiSection();
@@ -351,6 +356,7 @@ namespace Ryujinx.Configuration
             Graphics                 = new GraphicsSection();
             Hid                      = new HidSection();
             EnableDiscordIntegration = new ReactiveObject<bool>();
+            CheckUpdatesOnStart      = new ReactiveObject<bool>();
         }
 
         public ConfigurationFileFormat ToFileFormat()
@@ -393,6 +399,7 @@ namespace Ryujinx.Configuration
                 SystemTimeOffset          = System.SystemTimeOffset,
                 DockedMode                = System.EnableDockedMode,
                 EnableDiscordIntegration  = EnableDiscordIntegration,
+                CheckUpdatesOnStart       = CheckUpdatesOnStart,
                 EnableVsync               = Graphics.EnableVsync,
                 EnableMulticoreScheduling = System.EnableMulticoreScheduling,
                 EnablePtc                 = System.EnablePtc,
@@ -452,6 +459,7 @@ namespace Ryujinx.Configuration
             System.SystemTimeOffset.Value          = 0;
             System.EnableDockedMode.Value          = false;
             EnableDiscordIntegration.Value         = true;
+            CheckUpdatesOnStart.Value              = true;
             Graphics.EnableVsync.Value             = true;
             System.EnableMulticoreScheduling.Value = true;
             System.EnablePtc.Value                 = false;
@@ -696,6 +704,15 @@ namespace Ryujinx.Configuration
                 configurationFileUpdated = true;
             }
 
+            if (configurationFileFormat.Version < 14)
+            {
+                Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 14.");
+
+                configurationFileFormat.CheckUpdatesOnStart = true;
+
+                configurationFileUpdated = true;
+            }
+
             List<InputConfig> inputConfig = new List<InputConfig>();
             inputConfig.AddRange(configurationFileFormat.ControllerConfig);
             inputConfig.AddRange(configurationFileFormat.KeyboardConfig);
@@ -720,6 +737,7 @@ namespace Ryujinx.Configuration
             System.SystemTimeOffset.Value          = configurationFileFormat.SystemTimeOffset;
             System.EnableDockedMode.Value          = configurationFileFormat.DockedMode;
             EnableDiscordIntegration.Value         = configurationFileFormat.EnableDiscordIntegration;
+            CheckUpdatesOnStart.Value              = configurationFileFormat.CheckUpdatesOnStart;
             Graphics.EnableVsync.Value             = configurationFileFormat.EnableVsync;
             System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling;
             System.EnablePtc.Value                 = configurationFileFormat.EnablePtc;
diff --git a/Ryujinx.sln b/Ryujinx.sln
index 44ab576f6..3e557deae 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -36,6 +36,7 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
 	ProjectSection(SolutionItems) = preProject
 		.editorconfig = .editorconfig
+		appveyor.yml = appveyor.yml
 	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index d0fb1fc2b..55aaa9c2e 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -1,5 +1,5 @@
 {
-  "version": 11,
+  "version": 14,
   "res_scale": 1,
   "res_scale_custom": 1,
   "max_anisotropy": -1,
@@ -19,6 +19,7 @@
   "system_time_offset": 0,
   "docked_mode": false,
   "enable_discord_integration": true,
+  "check_updates_on_start": true,
   "enable_vsync": true,
   "enable_multicore_scheduling": true,
   "enable_ptc": false,
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index f8fb5599e..280b5c368 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -10,6 +10,7 @@ using Ryujinx.Ui.Diagnostic;
 using System;
 using System.IO;
 using System.Reflection;
+using System.Threading.Tasks;
 
 namespace Ryujinx
 {
@@ -44,6 +45,9 @@ namespace Ryujinx
                 }
             }
 
+            // Delete backup files after updating
+            Task.Run(Updater.CleanupUpdate);
+
             Toolkit.Init(new ToolkitOptions
             {
                 Backend = PlatformBackend.PreferNative,
@@ -122,6 +126,11 @@ namespace Ryujinx
                 mainWindow.LoadApplication(launchPath);
             }
 
+            if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
+            {
+                Updater.BeginParse(mainWindow, false);
+            }
+
             Application.Run();
         }
 
@@ -167,4 +176,4 @@ namespace Ryujinx
             Logger.Shutdown();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index ffb1d019a..c4cba1086 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
@@ -42,6 +42,7 @@
     <None Remove="Ui\ProfileDialog.glade" />
     <None Remove="Ui\SettingsWindow.glade" />
     <None Remove="Ui\TitleUpdateWindow.glade" />
+    <None Remove="Ui\UpdateDialog.glade" />
   </ItemGroup>
 
   <ItemGroup>
@@ -66,6 +67,7 @@
     <EmbeddedResource Include="Ui\SettingsWindow.glade" />
     <EmbeddedResource Include="Ui\DlcWindow.glade" />
     <EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
+    <EmbeddedResource Include="Updater\UpdateDialog.glade" />
   </ItemGroup>
 
   <ItemGroup>
@@ -75,6 +77,7 @@
     <PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.3.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
+    <PackageReference Include="SharpZipLib" Version="1.2.0" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
index 203df72ad..637b02392 100644
--- a/Ryujinx/Ui/GLRenderer.cs
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -135,7 +135,7 @@ namespace Ryujinx.Ui
                     {
                         if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
                         {
-                            if (GtkDialog.CreateExitDialog())
+                            if (GtkDialog.CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to stop emulation?", "All unsaved data will be lost!"))
                             {
                                 Exit();
                             }
diff --git a/Ryujinx/Ui/GtkDialog.cs b/Ryujinx/Ui/GtkDialog.cs
index 9a14f63d3..e72013484 100644
--- a/Ryujinx/Ui/GtkDialog.cs
+++ b/Ryujinx/Ui/GtkDialog.cs
@@ -5,10 +5,10 @@ namespace Ryujinx.Ui
 {
     internal class GtkDialog : MessageDialog
     {
-        internal static bool _isExitDialogOpen = false;
+        private static bool _isChoiceDialogOpen;
 
-        private GtkDialog(string title, string mainText, string secondaryText,
-            MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) : base(null, DialogFlags.Modal, messageType, buttonsType, null)
+        private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) 
+            : base(null, DialogFlags.Modal, messageType, buttonsType, null)
         {
             Title          = title;
             Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
@@ -45,19 +45,16 @@ namespace Ryujinx.Ui
             return new GtkDialog("Ryujinx - Confirmation", mainText, secondaryText, MessageType.Question, ButtonsType.YesNo);
         }
 
-        internal static bool CreateExitDialog()
+        internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText)
         {
-            if (_isExitDialogOpen)
-            {
+            if (_isChoiceDialogOpen)
                 return false;
-            }
 
-            _isExitDialogOpen = true;
-            ResponseType res  = (ResponseType)new GtkDialog("Ryujinx - Exit", "Are you sure you want to stop emulation?", 
-                "All unsaved data will be lost", MessageType.Question, ButtonsType.YesNo).Run();
-            _isExitDialogOpen = false;
-            
-            return res == ResponseType.Yes;
+            _isChoiceDialogOpen = true;
+            ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run();
+            _isChoiceDialogOpen = false;
+
+            return response == ResponseType.Yes;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 6ce069854..4cfcd4891 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -49,36 +49,38 @@ namespace Ryujinx.Ui
 
 #pragma warning disable CS0169, CS0649, IDE0044
 
-        [GUI] MenuBar        _menuBar;
-        [GUI] Box            _footerBox;
-        [GUI] Box            _statusBar;
-        [GUI] MenuItem       _stopEmulation;
-        [GUI] MenuItem       _fullScreen;
-        [GUI] CheckMenuItem  _favToggle;
-        [GUI] MenuItem       _firmwareInstallDirectory;
-        [GUI] MenuItem       _firmwareInstallFile;
-        [GUI] Label          _hostStatus;
-        [GUI] CheckMenuItem  _iconToggle;
-        [GUI] CheckMenuItem  _developerToggle;
-        [GUI] CheckMenuItem  _appToggle;
-        [GUI] CheckMenuItem  _timePlayedToggle;
-        [GUI] CheckMenuItem  _versionToggle;
-        [GUI] CheckMenuItem  _lastPlayedToggle;
-        [GUI] CheckMenuItem  _fileExtToggle;
-        [GUI] CheckMenuItem  _pathToggle;
-        [GUI] CheckMenuItem  _fileSizeToggle;
-        [GUI] Label          _dockedMode;
-        [GUI] Label          _gameStatus;
-        [GUI] TreeView       _gameTable;
-        [GUI] TreeSelection  _gameTableSelection;
-        [GUI] ScrolledWindow _gameTableWindow;
-        [GUI] Label          _gpuName;
-        [GUI] Label          _progressLabel;
-        [GUI] Label          _firmwareVersionLabel;
-        [GUI] LevelBar       _progressBar;
-        [GUI] Box            _viewBox;
-        [GUI] Label          _vSyncStatus;
-        [GUI] Box            _listStatusBox;
+        [GUI] public MenuItem ExitMenuItem;
+        [GUI] public MenuItem UpdateMenuItem;
+        [GUI] MenuBar         _menuBar;
+        [GUI] Box             _footerBox;
+        [GUI] Box             _statusBar;
+        [GUI] MenuItem        _stopEmulation;
+        [GUI] MenuItem        _fullScreen;
+        [GUI] CheckMenuItem   _favToggle;
+        [GUI] MenuItem        _firmwareInstallDirectory;
+        [GUI] MenuItem        _firmwareInstallFile;
+        [GUI] Label           _hostStatus;
+        [GUI] CheckMenuItem   _iconToggle;
+        [GUI] CheckMenuItem   _developerToggle;
+        [GUI] CheckMenuItem   _appToggle;
+        [GUI] CheckMenuItem   _timePlayedToggle;
+        [GUI] CheckMenuItem   _versionToggle;
+        [GUI] CheckMenuItem   _lastPlayedToggle;
+        [GUI] CheckMenuItem   _fileExtToggle;
+        [GUI] CheckMenuItem   _pathToggle;
+        [GUI] CheckMenuItem   _fileSizeToggle;
+        [GUI] Label           _dockedMode;
+        [GUI] Label           _gameStatus;
+        [GUI] TreeView        _gameTable;
+        [GUI] TreeSelection   _gameTableSelection;
+        [GUI] ScrolledWindow  _gameTableWindow;
+        [GUI] Label           _gpuName;
+        [GUI] Label           _progressLabel;
+        [GUI] Label           _firmwareVersionLabel;
+        [GUI] LevelBar        _progressBar;
+        [GUI] Box             _viewBox;
+        [GUI] Label           _vSyncStatus;
+        [GUI] Box             _listStatusBox;
 
 #pragma warning restore CS0649, IDE0044, CS0169
 
@@ -1163,15 +1165,9 @@ namespace Ryujinx.Ui
 
         private void Update_Pressed(object sender, EventArgs args)
         {
-            string ryuUpdater = System.IO.Path.Combine(AppDataManager.BaseDirPath, "RyuUpdater.exe");
-
-            try
+            if (Updater.CanUpdate(true))
             {
-                Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true });
-            }
-            catch(System.ComponentModel.Win32Exception)
-            {
-                GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
+                Updater.BeginParse(this, true);
             }
         }
 
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index 254d2bcd1..26a7d75ad 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -81,7 +81,7 @@
                       </object>
                     </child>
                     <child>
-                      <object class="GtkMenuItem" id="Exit">
+                      <object class="GtkMenuItem" id="ExitMenuItem">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="tooltip_text" translatable="yes">Exit Ryujinx</property>
@@ -320,10 +320,10 @@
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <child>
-                      <object class="GtkMenuItem" id="CheckUpdates">
+                      <object class="GtkMenuItem" id="UpdateMenuItem">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Check for updates to Ryujinx (requires Ryujinx Installer)</property>
+                        <property name="tooltip_text" translatable="yes">Check for updates to Ryujinx</property>
                         <property name="label" translatable="yes">Check for Updates</property>
                         <property name="use_underline">True</property>
                         <signal name="activate" handler="Update_Pressed" swapped="no"/>
diff --git a/Ryujinx/Ui/SettingsWindow.cs b/Ryujinx/Ui/SettingsWindow.cs
index efaa06cd2..9668a4bcc 100644
--- a/Ryujinx/Ui/SettingsWindow.cs
+++ b/Ryujinx/Ui/SettingsWindow.cs
@@ -40,6 +40,7 @@ namespace Ryujinx.Ui
         [GUI] ComboBoxText    _graphicsDebugLevel;
         [GUI] CheckButton     _dockedModeToggle;
         [GUI] CheckButton     _discordToggle;
+        [GUI] CheckButton     _checkUpdatesToggle;
         [GUI] CheckButton     _vSyncToggle;
         [GUI] CheckButton     _multiSchedToggle;
         [GUI] CheckButton     _ptcToggle;
@@ -170,6 +171,11 @@ namespace Ryujinx.Ui
                 _discordToggle.Click();
             }
 
+            if (ConfigurationState.Instance.CheckUpdatesOnStart)
+            {
+                _checkUpdatesToggle.Click();
+            }
+
             if (ConfigurationState.Instance.Graphics.EnableVsync)
             {
                 _vSyncToggle.Click();
@@ -519,6 +525,7 @@ namespace Ryujinx.Ui
             ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value        = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId);
             ConfigurationState.Instance.System.EnableDockedMode.Value          = _dockedModeToggle.Active;
             ConfigurationState.Instance.EnableDiscordIntegration.Value         = _discordToggle.Active;
+            ConfigurationState.Instance.CheckUpdatesOnStart.Value              = _checkUpdatesToggle.Active;
             ConfigurationState.Instance.Graphics.EnableVsync.Value             = _vSyncToggle.Active;
             ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active;
             ConfigurationState.Instance.System.EnablePtc.Value                 = _ptcToggle.Active;
diff --git a/Ryujinx/Ui/SettingsWindow.glade b/Ryujinx/Ui/SettingsWindow.glade
index 505cc2387..56a528f0f 100644
--- a/Ryujinx/Ui/SettingsWindow.glade
+++ b/Ryujinx/Ui/SettingsWindow.glade
@@ -121,7 +121,23 @@
                                     <property name="expand">False</property>
                                     <property name="fill">True</property>
                                     <property name="padding">5</property>
-                                    <property name="position">2</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="_checkUpdatesToggle">
+                                    <property name="label" translatable="yes">Check for updates on launch</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="halign">start</property>
+                                    <property name="draw_indicator">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="padding">5</property>
+                                    <property name="position">1</property>
                                   </packing>
                                 </child>
                               </object>
diff --git a/Ryujinx/Updater/UpdateDialog.cs b/Ryujinx/Updater/UpdateDialog.cs
new file mode 100644
index 000000000..5420baf23
--- /dev/null
+++ b/Ryujinx/Updater/UpdateDialog.cs
@@ -0,0 +1,86 @@
+using Gdk;
+using Gtk;
+using Mono.Unix;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui
+{
+    public class UpdateDialog : Gtk.Window
+    {
+#pragma warning disable CS0649, IDE0044
+        [Builder.Object] public Label MainText;
+        [Builder.Object] public Label SecondaryText;
+        [Builder.Object] public LevelBar ProgressBar;
+        [Builder.Object] public Button YesButton;
+        [Builder.Object] public Button NoButton;
+#pragma warning restore CS0649, IDE0044
+
+        private readonly MainWindow _mainWindow;
+        private readonly string _buildUrl;
+        private bool _restartQuery;
+
+        public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
+
+        private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle)
+        {
+            builder.Autoconnect(this);
+
+            _mainWindow = mainWindow;
+            _buildUrl   = buildUrl;
+
+            MainText.Text      = "Do you want to update Ryujinx to the latest version?";
+            SecondaryText.Text = $"{Program.Version} -> {newVersion}";
+
+            ProgressBar.Hide();
+
+            YesButton.Pressed += YesButton_Pressed;
+            NoButton.Pressed  += NoButton_Pressed;
+        }
+        
+        private void YesButton_Pressed(object sender, EventArgs args)
+        {
+            if (_restartQuery)
+            {
+                string ryuName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Ryujinx.exe" : "Ryujinx";
+                string ryuExe  = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
+
+                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    UnixFileInfo unixFileInfo = new UnixFileInfo(ryuExe);
+                    unixFileInfo.FileAccessPermissions |= FileAccessPermissions.UserExecute;
+                }
+
+                Process.Start(ryuExe);
+
+                Environment.Exit(0);
+            }
+            else
+            {
+                this.Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close;
+                _mainWindow.ExitMenuItem.Sensitive = false;
+
+                YesButton.Hide();
+                NoButton.Hide();
+                ProgressBar.Show();
+
+                SecondaryText.Text = "";
+                _restartQuery      = true;
+
+                Updater.UpdateRyujinx(this, _buildUrl);
+            }
+        }
+
+        private void NoButton_Pressed(object sender, EventArgs args)
+        {
+            Updater.Running = false;
+            _mainWindow.Window.Functions = WMFunction.All;
+
+            _mainWindow.ExitMenuItem.Sensitive   = true;
+            _mainWindow.UpdateMenuItem.Sensitive = true;
+
+            this.Dispose();
+        }
+    }
+}
diff --git a/Ryujinx/Updater/UpdateDialog.glade b/Ryujinx/Updater/UpdateDialog.glade
new file mode 100644
index 000000000..cc80167e0
--- /dev/null
+++ b/Ryujinx/Updater/UpdateDialog.glade
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkWindow" id="UpdateDialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Ryujinx - Updater</property>
+    <property name="resizable">False</property>
+    <property name="window_position">center</property>
+    <property name="default_width">400</property>
+    <property name="default_height">130</property>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkBox" id="MainBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">10</property>
+        <property name="margin_right">10</property>
+        <property name="margin_top">10</property>
+        <property name="margin_bottom">10</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="BodyBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="MainText">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                  <attribute name="size" value="10000"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="SecondaryText">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLevelBar" id="ProgressBar">
+                <property name="height_request">20</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+                <property name="max_value">100</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="ButtonBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkButton" id="YesButton">
+                <property name="label" translatable="yes">Yes</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="margin_right">5</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="NoButton">
+                <property name="label" translatable="yes">No</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="margin_left">5</property>
+                <property name="margin_top">5</property>
+                <property name="margin_bottom">5</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/Ryujinx/Updater/Updater.cs b/Ryujinx/Updater/Updater.cs
new file mode 100644
index 000000000..3c164e313
--- /dev/null
+++ b/Ryujinx/Updater/Updater.cs
@@ -0,0 +1,347 @@
+using Gtk;
+using ICSharpCode.SharpZipLib.GZip;
+using ICSharpCode.SharpZipLib.Tar;
+using ICSharpCode.SharpZipLib.Zip;
+using Newtonsoft.Json.Linq;
+using Ryujinx.Common.Logging;
+using Ryujinx.Ui;
+using System;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Ryujinx
+{
+    public static class Updater
+    {
+        internal static bool Running;
+
+        private static readonly string HomeDir          = AppDomain.CurrentDomain.BaseDirectory;
+        private static readonly string UpdateDir        = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
+        private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
+
+        private static string _jobId;
+        private static string _buildVer;
+        private static string _platformExt;
+        private static string _buildUrl;
+        
+        private const string AppveyorApiUrl = "https://ci.appveyor.com/api";
+
+        public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
+        {
+            if (Running) return;
+
+            Running = true;
+            mainWindow.UpdateMenuItem.Sensitive = false;
+
+            // Detect current platform
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                _platformExt = "osx_x64.zip";
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                _platformExt = "win_x64.zip";
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                _platformExt = "linux_x64.tar.gz";
+            }
+
+            Version newVersion;
+            Version currentVersion;
+
+            try
+            {
+                currentVersion = Version.Parse(Program.Version);
+            }
+            catch
+            {
+                GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!");
+                Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
+
+                return;
+            }
+
+            // Get latest version number from Appveyor
+            try
+            {
+                using (WebClient jsonClient = new WebClient())
+                {
+                    string  fetchedJson = await jsonClient.DownloadStringTaskAsync($"{AppveyorApiUrl}/projects/gdkchan/ryujinx/branch/master");
+                    JObject jsonRoot    = JObject.Parse(fetchedJson);
+                    JToken  buildToken  = jsonRoot["build"];
+
+                    _jobId    = (string)buildToken["jobs"][0]["jobId"];
+                    _buildVer = (string)buildToken["version"];
+                    _buildUrl = $"{AppveyorApiUrl}/buildjobs/{_jobId}/artifacts/ryujinx-{_buildVer}-{_platformExt}";
+                }
+            }
+            catch (Exception exception)
+            {
+                Logger.Error?.Print(LogClass.Application, exception.Message);
+                GtkDialog.CreateErrorDialog("An error has occurred when trying to get release information from AppVeyor.");
+
+                return;
+            }
+
+            try
+            {
+                newVersion = Version.Parse(_buildVer);
+            }
+            catch
+            {
+                GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from AppVeyor.", "Cancelling Update!");
+                Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from AppVeyor!");
+
+                return;
+            }
+
+            if (newVersion <= currentVersion)
+            {
+                if (showVersionUpToDate)
+                {
+                    GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", "");
+                }
+
+                Running = false;
+                mainWindow.UpdateMenuItem.Sensitive = true;
+
+                return;
+            }
+
+            // Show a message asking the user if they want to update
+            UpdateDialog updateDialog = new UpdateDialog(mainWindow, newVersion, _buildUrl);
+            updateDialog.Show();
+        }
+
+        public static async Task UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl)
+        {
+            // Empty update dir, although it shouldn't ever have anything inside it
+            if (Directory.Exists(UpdateDir))
+            {
+                Directory.Delete(UpdateDir, true);
+            }
+
+            Directory.CreateDirectory(UpdateDir);
+
+            string updateFile = Path.Combine(UpdateDir, "update.bin");
+
+            // Download the update .zip
+            updateDialog.MainText.Text        = "Downloading Update...";
+            updateDialog.ProgressBar.Value    = 0;
+            updateDialog.ProgressBar.MaxValue = 100;
+
+            using (WebClient client = new WebClient())
+            {
+                client.DownloadProgressChanged += (_, args) =>
+                {
+                    updateDialog.ProgressBar.Value = args.ProgressPercentage;
+                };
+
+                await client.DownloadFileTaskAsync(downloadUrl, updateFile);
+            }
+
+            // Extract Update
+            updateDialog.MainText.Text     = "Extracting Update...";
+            updateDialog.ProgressBar.Value = 0;
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                using (Stream         inStream   = File.OpenRead(updateFile))
+                using (Stream         gzipStream = new GZipInputStream(inStream))
+                using (TarInputStream tarStream  = new TarInputStream(gzipStream))
+                {
+                    updateDialog.ProgressBar.MaxValue = inStream.Length;
+
+                    await Task.Run(() =>
+                    {
+                        TarEntry tarEntry;
+                        while ((tarEntry = tarStream.GetNextEntry()) != null)
+                        {
+                            if (tarEntry.IsDirectory) continue;
+
+                            string outPath = Path.Combine(UpdateDir, tarEntry.Name);
+
+                            Directory.CreateDirectory(Path.GetDirectoryName(outPath));
+
+                            using (FileStream outStream = File.OpenWrite(outPath))
+                            {
+                                tarStream.CopyEntryContents(outStream);
+                            }
+
+                            File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
+
+                            TarEntry entry = tarEntry;
+
+                            Application.Invoke(delegate
+                            {
+                                updateDialog.ProgressBar.Value += entry.Size;
+                            });
+                        }
+                    });
+
+                    updateDialog.ProgressBar.Value = inStream.Length;
+                }
+            }
+            else
+            {
+                using (Stream  inStream = File.OpenRead(updateFile))
+                using (ZipFile zipFile  = new ZipFile(inStream))
+                {
+                    updateDialog.ProgressBar.MaxValue = zipFile.Count;
+
+                    await Task.Run(() =>
+                    {
+                        foreach (ZipEntry zipEntry in zipFile)
+                        {
+                            if (zipEntry.IsDirectory) continue;
+
+                            string outPath = Path.Combine(UpdateDir, zipEntry.Name);
+
+                            Directory.CreateDirectory(Path.GetDirectoryName(outPath));
+
+                            using (Stream     zipStream = zipFile.GetInputStream(zipEntry))
+                            using (FileStream outStream = File.OpenWrite(outPath))
+                            {
+                                zipStream.CopyTo(outStream);
+                            }
+
+                            File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
+
+                            Application.Invoke(delegate
+                            {
+                                updateDialog.ProgressBar.Value++;
+                            });
+                        }
+                    });
+                }
+            }
+
+            // Delete downloaded zip
+            File.Delete(updateFile);
+
+            string[] allFiles = Directory.GetFiles(HomeDir, "*", SearchOption.AllDirectories);
+
+            updateDialog.MainText.Text        = "Renaming Old Files...";
+            updateDialog.ProgressBar.Value    = 0;
+            updateDialog.ProgressBar.MaxValue = allFiles.Length;
+
+            // Replace old files
+            await Task.Run(() =>
+            {
+                foreach (string file in allFiles)
+                {
+                    if (!Path.GetExtension(file).Equals(".log"))
+                    {
+                        try
+                        {
+                            File.Move(file, file + ".ryuold");
+
+                            Application.Invoke(delegate
+                            {
+                                updateDialog.ProgressBar.Value++;
+                            });
+                        }
+                        catch
+                        {
+                            Logger.Warning?.Print(LogClass.Application, "Updater wasn't able to rename file: " + file);
+                        }
+                    }
+                }
+
+                Application.Invoke(delegate
+                {
+                    updateDialog.MainText.Text        = "Adding New Files...";
+                    updateDialog.ProgressBar.Value    = 0;
+                    updateDialog.ProgressBar.MaxValue = Directory.GetFiles(UpdatePublishDir, "*", SearchOption.AllDirectories).Length;
+                });
+
+                MoveAllFilesOver(UpdatePublishDir, HomeDir, updateDialog);
+            });
+
+            Directory.Delete(UpdateDir, true);
+
+            updateDialog.MainText.Text      = "Update Complete!";
+            updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?";
+            updateDialog.Modal              = true;
+
+            updateDialog.ProgressBar.Hide();
+            updateDialog.YesButton.Show();
+            updateDialog.NoButton.Show();
+        }
+
+        public static bool CanUpdate(bool showWarnings)
+        {
+            if (RuntimeInformation.OSArchitecture != Architecture.X64)
+            {
+                if (showWarnings)
+                {
+                    GtkDialog.CreateWarningDialog("You are not running a supported system architecture!", "(Only x64 systems are supported!)");
+                }
+
+                return false;
+            }
+
+            if (!NetworkInterface.GetIsNetworkAvailable())
+            {
+                if (showWarnings)
+                {
+                    GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!");
+                }
+
+                return false;
+            }
+
+            if (Program.Version.Contains("dirty"))
+            {
+                if (showWarnings)
+                {
+                    GtkDialog.CreateWarningDialog("You Cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
+                }
+
+                return false;
+            }
+
+            return true;
+        }
+
+        private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog)
+        {
+            foreach (string directory in Directory.GetDirectories(root))
+            {
+                string dirName = Path.GetFileName(directory);
+
+                if (!Directory.Exists(Path.Combine(dest, dirName)))
+                {
+                    Directory.CreateDirectory(Path.Combine(dest, dirName));
+                }
+
+                MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog);
+            }
+
+            foreach (string file in Directory.GetFiles(root))
+            {
+                File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
+
+                Application.Invoke(delegate
+                {
+                    dialog.ProgressBar.Value++;
+                });
+            }
+        }
+
+        public static void CleanupUpdate()
+        {
+            foreach (string file in Directory.GetFiles(HomeDir, "*", SearchOption.AllDirectories))
+            {
+                if (Path.GetExtension(file).EndsWith(".ryuold"))
+                {
+                    File.Delete(file);
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
index 90b993e69..d85aa4382 100644
--- a/Ryujinx/_schema.json
+++ b/Ryujinx/_schema.json
@@ -719,7 +719,7 @@
       "type": "number",
       "title": "Custom Resolution Scale",
       "description": "A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.",
-      "default": 1.0,
+      "default": 1.0
     },
     "max_anisotropy": {
       "$id": "#/properties/max_anisotropy",
@@ -966,6 +966,17 @@
         false
       ]
     },
+    "check_updates_on_start": {
+      "$id": "#/properties/check_updates_on_start",
+      "type": "boolean",
+      "title": "Checks for updates when ryujinx starts when enabled",
+      "description": "Checks for updates when ryujinx starts when enabled",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
     "enable_vsync": {
       "$id": "#/properties/enable_vsync",
       "type": "boolean",