From a15459366e7cd76ddb33eb882fd117f5aa79493d Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 29 Sep 2020 16:05:25 -0400 Subject: [PATCH] Appveyor Ryujinx Updater (#1403) Co-authored-by: Xpl0itR --- .../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 /// /// The current version of the file format /// - public const int CurrentVersion = 12; + public const int CurrentVersion = 14; public int Version { get; set; } @@ -118,6 +118,11 @@ namespace Ryujinx.Configuration /// public bool EnableDiscordIntegration { get; set; } + /// + /// Checks for updates when Ryujinx starts when enabled + /// + public bool CheckUpdatesOnStart { get; set; } + /// /// Enables or disables Vertical Sync /// 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 /// public ReactiveObject EnableDiscordIntegration { get; private set; } + /// + /// Checks for updates when Ryujinx starts when enabled + /// + public ReactiveObject 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(); + CheckUpdatesOnStart = new ReactiveObject(); } 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 = new List(); 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 @@ - + netcoreapp3.1 @@ -42,6 +42,7 @@ + @@ -66,6 +67,7 @@ + @@ -75,6 +77,7 @@ + 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 @@ - + True False Exit Ryujinx @@ -320,10 +320,10 @@ True False - + True False - Check for updates to Ryujinx (requires Ryujinx Installer) + Check for updates to Ryujinx Check for Updates True 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.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 @@ False True 5 - 2 + 0 + + + + + Check for updates on launch + True + True + False + start + True + + + False + True + 5 + 1 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 @@ + + + + + + False + Ryujinx - Updater + False + center + 400 + 130 + + + + + + True + False + 10 + 10 + 10 + 10 + vertical + + + True + False + vertical + + + True + False + 5 + 5 + + + + + + + False + True + 0 + + + + + True + False + 5 + 5 + + + False + True + 1 + + + + + 20 + True + False + 5 + 5 + 100 + + + False + True + 2 + + + + + True + True + 0 + + + + + True + False + + + Yes + True + True + True + 5 + 5 + 5 + + + True + True + 0 + + + + + No + True + True + True + 5 + 5 + 5 + + + True + True + 1 + + + + + False + True + 1 + + + + + + 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",