aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.Common/Configuration/ConfigurationFileFormat.cs7
-rw-r--r--Ryujinx.Common/Configuration/ConfigurationState.cs18
-rw-r--r--Ryujinx.sln1
-rw-r--r--Ryujinx/Config.json3
-rw-r--r--Ryujinx/Program.cs11
-rw-r--r--Ryujinx/Ryujinx.csproj5
-rw-r--r--Ryujinx/Ui/GLRenderer.cs2
-rw-r--r--Ryujinx/Ui/GtkDialog.cs25
-rw-r--r--Ryujinx/Ui/MainWindow.cs72
-rw-r--r--Ryujinx/Ui/MainWindow.glade6
-rw-r--r--Ryujinx/Ui/SettingsWindow.cs7
-rw-r--r--Ryujinx/Ui/SettingsWindow.glade18
-rw-r--r--Ryujinx/Updater/UpdateDialog.cs86
-rw-r--r--Ryujinx/Updater/UpdateDialog.glade127
-rw-r--r--Ryujinx/Updater/Updater.cs347
-rw-r--r--Ryujinx/_schema.json13
16 files changed, 686 insertions, 62 deletions
diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index ae3fa493..cab38046 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; }
@@ -119,6 +119,11 @@ namespace Ryujinx.Configuration
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>
public bool EnableVsync { get; set; }
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index 7f79dd6e..df07019d 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 44ab576f..3e557dea 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 d0fb1fc2..55aaa9c2 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 f8fb5599..280b5c36 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 ffb1d019..c4cba108 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 203df72a..637b0239 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 9a14f63d..e7201348 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 6ce06985..4cfcd489 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
- {
- Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true });
- }
- catch(System.ComponentModel.Win32Exception)
+ if (Updater.CanUpdate(true))
{
- 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 254d2bcd..26a7d75a 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 efaa06cd..9668a4bc 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 505cc238..56a528f0 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 00000000..5420baf2
--- /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 00000000..cc80167e
--- /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 00000000..3c164e31
--- /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 90b993e6..d85aa438 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",