0
0
Fork 0

Appveyor Ryujinx Updater (#1403)

Co-authored-by: Xpl0itR <xpl0itr@outlook.com>
This commit is contained in:
MelonSpeedruns 2020-09-29 16:05:25 -04:00 committed by GitHub
parent 4f65043ad7
commit a15459366e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 685 additions and 61 deletions

View file

@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 12; public const int CurrentVersion = 14;
public int Version { get; set; } public int Version { get; set; }
@ -118,6 +118,11 @@ namespace Ryujinx.Configuration
/// </summary> /// </summary>
public bool EnableDiscordIntegration { get; set; } public bool EnableDiscordIntegration { get; set; }
/// <summary>
/// Checks for updates when Ryujinx starts when enabled
/// </summary>
public bool CheckUpdatesOnStart { get; set; }
/// <summary> /// <summary>
/// Enables or disables Vertical Sync /// Enables or disables Vertical Sync
/// </summary> /// </summary>

View file

@ -343,6 +343,11 @@ namespace Ryujinx.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> EnableDiscordIntegration { get; private set; } 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() private ConfigurationState()
{ {
Ui = new UiSection(); Ui = new UiSection();
@ -351,6 +356,7 @@ namespace Ryujinx.Configuration
Graphics = new GraphicsSection(); Graphics = new GraphicsSection();
Hid = new HidSection(); Hid = new HidSection();
EnableDiscordIntegration = new ReactiveObject<bool>(); EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
} }
public ConfigurationFileFormat ToFileFormat() public ConfigurationFileFormat ToFileFormat()
@ -393,6 +399,7 @@ namespace Ryujinx.Configuration
SystemTimeOffset = System.SystemTimeOffset, SystemTimeOffset = System.SystemTimeOffset,
DockedMode = System.EnableDockedMode, DockedMode = System.EnableDockedMode,
EnableDiscordIntegration = EnableDiscordIntegration, EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
EnableVsync = Graphics.EnableVsync, EnableVsync = Graphics.EnableVsync,
EnableMulticoreScheduling = System.EnableMulticoreScheduling, EnableMulticoreScheduling = System.EnableMulticoreScheduling,
EnablePtc = System.EnablePtc, EnablePtc = System.EnablePtc,
@ -452,6 +459,7 @@ namespace Ryujinx.Configuration
System.SystemTimeOffset.Value = 0; System.SystemTimeOffset.Value = 0;
System.EnableDockedMode.Value = false; System.EnableDockedMode.Value = false;
EnableDiscordIntegration.Value = true; EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
Graphics.EnableVsync.Value = true; Graphics.EnableVsync.Value = true;
System.EnableMulticoreScheduling.Value = true; System.EnableMulticoreScheduling.Value = true;
System.EnablePtc.Value = false; System.EnablePtc.Value = false;
@ -696,6 +704,15 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true; 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>(); List<InputConfig> inputConfig = new List<InputConfig>();
inputConfig.AddRange(configurationFileFormat.ControllerConfig); inputConfig.AddRange(configurationFileFormat.ControllerConfig);
inputConfig.AddRange(configurationFileFormat.KeyboardConfig); inputConfig.AddRange(configurationFileFormat.KeyboardConfig);
@ -720,6 +737,7 @@ namespace Ryujinx.Configuration
System.SystemTimeOffset.Value = configurationFileFormat.SystemTimeOffset; System.SystemTimeOffset.Value = configurationFileFormat.SystemTimeOffset;
System.EnableDockedMode.Value = configurationFileFormat.DockedMode; System.EnableDockedMode.Value = configurationFileFormat.DockedMode;
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling; System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling;
System.EnablePtc.Value = configurationFileFormat.EnablePtc; System.EnablePtc.Value = configurationFileFormat.EnablePtc;

View file

@ -36,6 +36,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
appveyor.yml = appveyor.yml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"

View file

@ -1,5 +1,5 @@
{ {
"version": 11, "version": 14,
"res_scale": 1, "res_scale": 1,
"res_scale_custom": 1, "res_scale_custom": 1,
"max_anisotropy": -1, "max_anisotropy": -1,
@ -19,6 +19,7 @@
"system_time_offset": 0, "system_time_offset": 0,
"docked_mode": false, "docked_mode": false,
"enable_discord_integration": true, "enable_discord_integration": true,
"check_updates_on_start": true,
"enable_vsync": true, "enable_vsync": true,
"enable_multicore_scheduling": true, "enable_multicore_scheduling": true,
"enable_ptc": false, "enable_ptc": false,

View file

@ -10,6 +10,7 @@ using Ryujinx.Ui.Diagnostic;
using System; using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
namespace Ryujinx namespace Ryujinx
{ {
@ -44,6 +45,9 @@ namespace Ryujinx
} }
} }
// Delete backup files after updating
Task.Run(Updater.CleanupUpdate);
Toolkit.Init(new ToolkitOptions Toolkit.Init(new ToolkitOptions
{ {
Backend = PlatformBackend.PreferNative, Backend = PlatformBackend.PreferNative,
@ -122,6 +126,11 @@ namespace Ryujinx
mainWindow.LoadApplication(launchPath); mainWindow.LoadApplication(launchPath);
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(mainWindow, false);
}
Application.Run(); Application.Run();
} }

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
@ -42,6 +42,7 @@
<None Remove="Ui\ProfileDialog.glade" /> <None Remove="Ui\ProfileDialog.glade" />
<None Remove="Ui\SettingsWindow.glade" /> <None Remove="Ui\SettingsWindow.glade" />
<None Remove="Ui\TitleUpdateWindow.glade" /> <None Remove="Ui\TitleUpdateWindow.glade" />
<None Remove="Ui\UpdateDialog.glade" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -66,6 +67,7 @@
<EmbeddedResource Include="Ui\SettingsWindow.glade" /> <EmbeddedResource Include="Ui\SettingsWindow.glade" />
<EmbeddedResource Include="Ui\DlcWindow.glade" /> <EmbeddedResource Include="Ui\DlcWindow.glade" />
<EmbeddedResource Include="Ui\TitleUpdateWindow.glade" /> <EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
<EmbeddedResource Include="Updater\UpdateDialog.glade" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -75,6 +77,7 @@
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <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="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="OpenTK.NetStandard" Version="1.0.5.12" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -135,7 +135,7 @@ namespace Ryujinx.Ui
{ {
if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape)) 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(); Exit();
} }

View file

@ -5,10 +5,10 @@ namespace Ryujinx.Ui
{ {
internal class GtkDialog : MessageDialog internal class GtkDialog : MessageDialog
{ {
internal static bool _isExitDialogOpen = false; private static bool _isChoiceDialogOpen;
private GtkDialog(string title, string mainText, string secondaryText, private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok)
MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) : base(null, DialogFlags.Modal, messageType, buttonsType, null) : base(null, DialogFlags.Modal, messageType, buttonsType, null)
{ {
Title = title; Title = title;
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); 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); 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; return false;
}
_isExitDialogOpen = true; _isChoiceDialogOpen = true;
ResponseType res = (ResponseType)new GtkDialog("Ryujinx - Exit", "Are you sure you want to stop emulation?", ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run();
"All unsaved data will be lost", MessageType.Question, ButtonsType.YesNo).Run(); _isChoiceDialogOpen = false;
_isExitDialogOpen = false;
return res == ResponseType.Yes; return response == ResponseType.Yes;
} }
} }
} }

View file

@ -49,6 +49,8 @@ namespace Ryujinx.Ui
#pragma warning disable CS0169, CS0649, IDE0044 #pragma warning disable CS0169, CS0649, IDE0044
[GUI] public MenuItem ExitMenuItem;
[GUI] public MenuItem UpdateMenuItem;
[GUI] MenuBar _menuBar; [GUI] MenuBar _menuBar;
[GUI] Box _footerBox; [GUI] Box _footerBox;
[GUI] Box _statusBar; [GUI] Box _statusBar;
@ -1163,15 +1165,9 @@ namespace Ryujinx.Ui
private void Update_Pressed(object sender, EventArgs args) private void Update_Pressed(object sender, EventArgs args)
{ {
string ryuUpdater = System.IO.Path.Combine(AppDataManager.BaseDirPath, "RyuUpdater.exe"); if (Updater.CanUpdate(true))
try
{ {
Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true }); Updater.BeginParse(this, true);
}
catch(System.ComponentModel.Win32Exception)
{
GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
} }
} }

View file

@ -81,7 +81,7 @@
</object> </object>
</child> </child>
<child> <child>
<object class="GtkMenuItem" id="Exit"> <object class="GtkMenuItem" id="ExitMenuItem">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Exit Ryujinx</property> <property name="tooltip_text" translatable="yes">Exit Ryujinx</property>
@ -320,10 +320,10 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkMenuItem" id="CheckUpdates"> <object class="GtkMenuItem" id="UpdateMenuItem">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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="label" translatable="yes">Check for Updates</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<signal name="activate" handler="Update_Pressed" swapped="no"/> <signal name="activate" handler="Update_Pressed" swapped="no"/>

View file

@ -40,6 +40,7 @@ namespace Ryujinx.Ui
[GUI] ComboBoxText _graphicsDebugLevel; [GUI] ComboBoxText _graphicsDebugLevel;
[GUI] CheckButton _dockedModeToggle; [GUI] CheckButton _dockedModeToggle;
[GUI] CheckButton _discordToggle; [GUI] CheckButton _discordToggle;
[GUI] CheckButton _checkUpdatesToggle;
[GUI] CheckButton _vSyncToggle; [GUI] CheckButton _vSyncToggle;
[GUI] CheckButton _multiSchedToggle; [GUI] CheckButton _multiSchedToggle;
[GUI] CheckButton _ptcToggle; [GUI] CheckButton _ptcToggle;
@ -170,6 +171,11 @@ namespace Ryujinx.Ui
_discordToggle.Click(); _discordToggle.Click();
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart)
{
_checkUpdatesToggle.Click();
}
if (ConfigurationState.Instance.Graphics.EnableVsync) if (ConfigurationState.Instance.Graphics.EnableVsync)
{ {
_vSyncToggle.Click(); _vSyncToggle.Click();
@ -519,6 +525,7 @@ namespace Ryujinx.Ui
ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId); ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId);
ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active;
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;
ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active; ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active;
ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active;

View file

@ -121,7 +121,23 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">5</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> </packing>
</child> </child>
</object> </object>

View file

@ -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();
}
}
}

View file

@ -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>

347
Ryujinx/Updater/Updater.cs Normal file
View file

@ -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);
}
}
}
}
}

View file

@ -719,7 +719,7 @@
"type": "number", "type": "number",
"title": "Custom Resolution Scale", "title": "Custom Resolution Scale",
"description": "A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.", "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": { "max_anisotropy": {
"$id": "#/properties/max_anisotropy", "$id": "#/properties/max_anisotropy",
@ -966,6 +966,17 @@
false 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": { "enable_vsync": {
"$id": "#/properties/enable_vsync", "$id": "#/properties/enable_vsync",
"type": "boolean", "type": "boolean",