2024-03-02 11:51:05 +00:00
using Gtk ;
2022-05-15 11:30:15 +00:00
using ICSharpCode.SharpZipLib.GZip ;
using ICSharpCode.SharpZipLib.Tar ;
using ICSharpCode.SharpZipLib.Zip ;
using Ryujinx.Common ;
using Ryujinx.Common.Logging ;
2023-04-03 10:14:19 +00:00
using Ryujinx.Common.Utilities ;
2024-03-02 11:51:05 +00:00
using Ryujinx.UI ;
2024-02-11 02:09:18 +00:00
using Ryujinx.UI.Common.Models.Github ;
2024-03-02 11:51:05 +00:00
using Ryujinx.UI.Widgets ;
2022-05-15 11:30:15 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net ;
using System.Net.Http ;
using System.Net.NetworkInformation ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
namespace Ryujinx.Modules
{
2024-03-02 11:51:05 +00:00
public static class Updater
2022-05-15 11:30:15 +00:00
{
2023-07-07 21:03:27 +00:00
private const string GitHubApiUrl = "https://api.github.com" ;
2024-03-02 11:51:05 +00:00
private const int ConnectionCount = 4 ;
internal static bool Running ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
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" ) ;
2022-05-15 11:30:15 +00:00
private static string _buildVer ;
private static string _platformExt ;
private static string _buildUrl ;
2023-07-07 21:03:27 +00:00
private static long _buildSize ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new ( JsonHelper . GetDefaultSerializerOptions ( ) ) ;
// On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates.
private static readonly string [ ] _windowsDependencyDirs = { "bin" , "etc" , "lib" , "share" } ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
private static HttpClient ConstructHttpClient ( )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
HttpClient result = new ( ) ;
// Required by GitHub to interact with APIs.
result . DefaultRequestHeaders . Add ( "User-Agent" , "Ryujinx-Updater/1.0.0" ) ;
return result ;
}
public static async Task BeginParse ( MainWindow mainWindow , bool showVersionUpToDate )
{
if ( Running )
2022-05-15 11:30:15 +00:00
{
return ;
}
2024-03-02 11:51:05 +00:00
Running = true ;
mainWindow . UpdateMenuItem . Sensitive = false ;
int artifactIndex = - 1 ;
2022-05-15 11:30:15 +00:00
// Detect current platform
if ( OperatingSystem . IsMacOS ( ) )
{
2024-03-02 11:51:05 +00:00
_platformExt = "osx_x64.zip" ;
artifactIndex = 1 ;
2022-05-15 11:30:15 +00:00
}
else if ( OperatingSystem . IsWindows ( ) )
{
_platformExt = "win_x64.zip" ;
2024-03-02 11:51:05 +00:00
artifactIndex = 2 ;
2022-05-15 11:30:15 +00:00
}
else if ( OperatingSystem . IsLinux ( ) )
{
2024-02-15 09:41:43 +00:00
var arch = RuntimeInformation . OSArchitecture = = Architecture . Arm64 ? "arm64" : "x64" ;
_platformExt = $"linux_{arch}.tar.gz" ;
2024-03-02 11:51:05 +00:00
artifactIndex = 0 ;
}
if ( artifactIndex = = - 1 )
{
GtkDialog . CreateErrorDialog ( "Your platform is not supported!" ) ;
return ;
2022-05-15 11:30:15 +00:00
}
Version newVersion ;
Version currentVersion ;
try
{
currentVersion = Version . Parse ( Program . Version ) ;
}
catch
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateWarningDialog ( "Failed to convert the current Ryujinx version." , "Cancelling Update!" ) ;
2022-05-15 11:30:15 +00:00
Logger . Error ? . Print ( LogClass . Application , "Failed to convert the current Ryujinx version!" ) ;
2023-01-20 20:30:21 +00:00
2022-05-15 11:30:15 +00:00
return ;
}
// Get latest version number from GitHub API
try
{
2023-01-20 20:30:21 +00:00
using HttpClient jsonClient = ConstructHttpClient ( ) ;
2023-07-07 21:03:27 +00:00
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest" ;
2024-03-02 11:51:05 +00:00
// Fetch latest build information
2023-07-07 21:03:27 +00:00
string fetchedJson = await jsonClient . GetStringAsync ( buildInfoUrl ) ;
var fetched = JsonHelper . Deserialize ( fetchedJson , _serializerContext . GithubReleasesJsonResponse ) ;
2023-04-03 10:14:19 +00:00
_buildVer = fetched . Name ;
2022-05-15 11:30:15 +00:00
2023-04-03 10:14:19 +00:00
foreach ( var asset in fetched . Assets )
2023-01-20 20:30:21 +00:00
{
2024-03-02 11:51:05 +00:00
if ( asset . Name . StartsWith ( "gtk-ryujinx" ) & & asset . Name . EndsWith ( _platformExt ) )
2022-05-15 11:30:15 +00:00
{
2023-04-03 10:14:19 +00:00
_buildUrl = asset . BrowserDownloadUrl ;
2022-05-15 11:30:15 +00:00
2023-04-03 10:14:19 +00:00
if ( asset . State ! = "uploaded" )
2022-05-15 11:30:15 +00:00
{
2023-01-20 20:30:21 +00:00
if ( showVersionUpToDate )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateUpdaterInfoDialog ( "You are already using the latest version of Ryujinx!" , "" ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
return ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
break ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
}
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
if ( _buildUrl = = null )
2023-01-20 20:30:21 +00:00
{
if ( showVersionUpToDate )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateUpdaterInfoDialog ( "You are already using the latest version of Ryujinx!" , "" ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
return ;
2022-05-15 11:30:15 +00:00
}
}
catch ( Exception exception )
{
Logger . Error ? . Print ( LogClass . Application , exception . Message ) ;
2024-03-02 11:51:05 +00:00
GtkDialog . CreateErrorDialog ( "An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes." ) ;
2023-01-20 20:30:21 +00:00
2022-05-15 11:30:15 +00:00
return ;
}
try
{
newVersion = Version . Parse ( _buildVer ) ;
}
catch
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateWarningDialog ( "Failed to convert the received Ryujinx version from GitHub Release." , "Cancelling Update!" ) ;
Logger . Error ? . Print ( LogClass . Application , "Failed to convert the received Ryujinx version from GitHub Release!" ) ;
2023-01-20 20:30:21 +00:00
2022-05-15 11:30:15 +00:00
return ;
}
if ( newVersion < = currentVersion )
{
if ( showVersionUpToDate )
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateUpdaterInfoDialog ( "You are already using the latest version of Ryujinx!" , "" ) ;
2022-05-15 11:30:15 +00:00
}
2024-03-02 11:51:05 +00:00
Running = false ;
mainWindow . UpdateMenuItem . Sensitive = true ;
2022-05-15 11:30:15 +00:00
return ;
}
// Fetch build size information to learn chunk sizes.
2023-07-07 21:03:27 +00:00
using HttpClient buildSizeClient = ConstructHttpClient ( ) ;
try
2022-05-15 11:30:15 +00:00
{
2023-07-07 21:03:27 +00:00
buildSizeClient . DefaultRequestHeaders . Add ( "Range" , "bytes=0-0" ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
HttpResponseMessage message = await buildSizeClient . GetAsync ( new Uri ( _buildUrl ) , HttpCompletionOption . ResponseHeadersRead ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
_buildSize = message . Content . Headers . ContentRange . Length . Value ;
}
catch ( Exception ex )
{
Logger . Warning ? . Print ( LogClass . Application , ex . Message ) ;
Logger . Warning ? . Print ( LogClass . Application , "Couldn't determine build size for update, using single-threaded updater" ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
_buildSize = - 1 ;
2022-05-15 11:30:15 +00:00
}
2022-10-03 14:25:25 +00:00
2024-03-02 11:51:05 +00:00
// Show a message asking the user if they want to update
UpdateDialog updateDialog = new ( mainWindow , newVersion , _buildUrl ) ;
updateDialog . Show ( ) ;
2022-05-15 11:30:15 +00:00
}
2024-03-02 11:51:05 +00:00
public static void UpdateRyujinx ( UpdateDialog updateDialog , string downloadUrl )
2022-05-15 11:30:15 +00:00
{
// Empty update dir, although it shouldn't ever have anything inside it
2023-07-07 21:03:27 +00:00
if ( Directory . Exists ( _updateDir ) )
2022-05-15 11:30:15 +00:00
{
2023-07-07 21:03:27 +00:00
Directory . Delete ( _updateDir , true ) ;
2022-05-15 11:30:15 +00:00
}
2023-07-07 21:03:27 +00:00
Directory . CreateDirectory ( _updateDir ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
string updateFile = Path . Combine ( _updateDir , "update.bin" ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
// Download the update .zip
updateDialog . MainText . Text = "Downloading Update..." ;
updateDialog . ProgressBar . Value = 0 ;
updateDialog . ProgressBar . MaxValue = 100 ;
2022-10-03 14:25:25 +00:00
2024-03-02 11:51:05 +00:00
if ( _buildSize > = 0 )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
DoUpdateWithMultipleThreads ( updateDialog , downloadUrl , updateFile ) ;
}
else
2022-10-03 14:25:25 +00:00
{
2024-03-02 11:51:05 +00:00
DoUpdateWithSingleThread ( updateDialog , downloadUrl , updateFile ) ;
2022-10-03 14:25:25 +00:00
}
2022-05-15 11:30:15 +00:00
}
2024-03-02 11:51:05 +00:00
private static void DoUpdateWithMultipleThreads ( UpdateDialog updateDialog , string downloadUrl , string updateFile )
2022-05-15 11:30:15 +00:00
{
// Multi-Threaded Updater
2023-08-13 22:07:57 +00:00
long chunkSize = _buildSize / ConnectionCount ;
long remainderChunk = _buildSize % ConnectionCount ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
int completedRequests = 0 ;
int totalProgressPercentage = 0 ;
2023-08-13 22:07:57 +00:00
int [ ] progressPercentage = new int [ ConnectionCount ] ;
2022-05-15 11:30:15 +00:00
2023-08-13 22:07:57 +00:00
List < byte [ ] > list = new ( ConnectionCount ) ;
List < WebClient > webClients = new ( ConnectionCount ) ;
2022-05-15 11:30:15 +00:00
2023-08-13 22:07:57 +00:00
for ( int i = 0 ; i < ConnectionCount ; i + + )
2022-05-15 11:30:15 +00:00
{
list . Add ( Array . Empty < byte > ( ) ) ;
}
2023-08-13 22:07:57 +00:00
for ( int i = 0 ; i < ConnectionCount ; i + + )
2022-05-15 11:30:15 +00:00
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
2023-01-20 20:30:21 +00:00
using WebClient client = new ( ) ;
2022-05-15 11:30:15 +00:00
#pragma warning restore SYSLIB0014
2023-01-20 20:30:21 +00:00
webClients . Add ( client ) ;
2023-08-13 22:07:57 +00:00
if ( i = = ConnectionCount - 1 )
2022-05-15 11:30:15 +00:00
{
2023-01-20 20:30:21 +00:00
client . Headers . Add ( "Range" , $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}" ) ;
}
else
{
client . Headers . Add ( "Range" , $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}" ) ;
}
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
client . DownloadProgressChanged + = ( _ , args ) = >
{
int index = ( int ) args . UserState ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
Interlocked . Add ( ref totalProgressPercentage , - 1 * progressPercentage [ index ] ) ;
Interlocked . Exchange ( ref progressPercentage [ index ] , args . ProgressPercentage ) ;
Interlocked . Add ( ref totalProgressPercentage , args . ProgressPercentage ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
updateDialog . ProgressBar . Value = totalProgressPercentage / ConnectionCount ;
2023-01-20 20:30:21 +00:00
} ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
client . DownloadDataCompleted + = ( _ , args ) = >
{
int index = ( int ) args . UserState ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
if ( args . Cancelled )
2022-05-15 11:30:15 +00:00
{
2023-01-20 20:30:21 +00:00
webClients [ index ] . Dispose ( ) ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
return ;
}
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
list [ index ] = args . Result ;
Interlocked . Increment ( ref completedRequests ) ;
2022-05-15 11:30:15 +00:00
2023-08-13 22:07:57 +00:00
if ( Equals ( completedRequests , ConnectionCount ) )
2023-01-20 20:30:21 +00:00
{
byte [ ] mergedFileBytes = new byte [ _buildSize ] ;
2023-08-13 22:07:57 +00:00
for ( int connectionIndex = 0 , destinationOffset = 0 ; connectionIndex < ConnectionCount ; connectionIndex + + )
2022-05-15 11:30:15 +00:00
{
2023-01-20 20:30:21 +00:00
Array . Copy ( list [ connectionIndex ] , 0 , mergedFileBytes , destinationOffset , list [ connectionIndex ] . Length ) ;
destinationOffset + = list [ connectionIndex ] . Length ;
}
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
File . WriteAllBytes ( updateFile , mergedFileBytes ) ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
try
{
2024-03-02 11:51:05 +00:00
InstallUpdate ( updateDialog , updateFile ) ;
2023-01-20 20:30:21 +00:00
}
catch ( Exception e )
{
Logger . Warning ? . Print ( LogClass . Application , e . Message ) ;
Logger . Warning ? . Print ( LogClass . Application , "Multi-Threaded update failed, falling back to single-threaded updater." ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
DoUpdateWithSingleThread ( updateDialog , downloadUrl , updateFile ) ;
return ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
}
} ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
try
{
client . DownloadDataAsync ( new Uri ( downloadUrl ) , i ) ;
}
catch ( WebException ex )
{
Logger . Warning ? . Print ( LogClass . Application , ex . Message ) ;
Logger . Warning ? . Print ( LogClass . Application , "Multi-Threaded update failed, falling back to single-threaded updater." ) ;
2023-02-15 22:36:35 +00:00
foreach ( WebClient webClient in webClients )
2022-05-15 11:30:15 +00:00
{
2023-02-15 22:36:35 +00:00
webClient . CancelAsync ( ) ;
2022-05-15 11:30:15 +00:00
}
2024-03-02 11:51:05 +00:00
DoUpdateWithSingleThread ( updateDialog , downloadUrl , updateFile ) ;
2022-05-15 11:30:15 +00:00
2023-01-20 20:30:21 +00:00
return ;
2022-05-15 11:30:15 +00:00
}
}
}
2024-03-02 11:51:05 +00:00
private static void DoUpdateWithSingleThreadWorker ( UpdateDialog updateDialog , string downloadUrl , string updateFile )
2022-05-15 11:30:15 +00:00
{
2023-01-20 20:30:21 +00:00
using HttpClient client = new ( ) ;
// We do not want to timeout while downloading
client . Timeout = TimeSpan . FromDays ( 1 ) ;
2023-07-07 21:03:27 +00:00
using HttpResponseMessage response = client . GetAsync ( downloadUrl , HttpCompletionOption . ResponseHeadersRead ) . Result ;
using Stream remoteFileStream = response . Content . ReadAsStreamAsync ( ) . Result ;
2024-04-07 18:58:41 +00:00
using FileStream updateFileStream = File . Open ( updateFile , FileMode . Create ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
long totalBytes = response . Content . Headers . ContentLength . Value ;
long byteWritten = 0 ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
byte [ ] buffer = new byte [ 32 * 1024 ] ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
while ( true )
{
int readSize = remoteFileStream . Read ( buffer ) ;
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
if ( readSize = = 0 )
{
break ;
}
2022-05-15 11:30:15 +00:00
2023-07-07 21:03:27 +00:00
byteWritten + = readSize ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
updateDialog . ProgressBar . Value = ( ( double ) byteWritten / totalBytes ) * 100 ;
2023-07-07 21:03:27 +00:00
updateFileStream . Write ( buffer , 0 , readSize ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
2024-03-02 11:51:05 +00:00
InstallUpdate ( updateDialog , updateFile ) ;
2022-10-03 14:25:25 +00:00
}
2024-03-02 11:51:05 +00:00
private static void DoUpdateWithSingleThread ( UpdateDialog updateDialog , string downloadUrl , string updateFile )
2022-10-03 14:25:25 +00:00
{
2024-03-02 11:51:05 +00:00
Thread worker = new ( ( ) = > DoUpdateWithSingleThreadWorker ( updateDialog , downloadUrl , updateFile ) )
2023-01-20 20:30:21 +00:00
{
2023-07-07 21:03:27 +00:00
Name = "Updater.SingleThreadWorker" ,
2023-01-20 20:30:21 +00:00
} ;
2022-05-15 11:30:15 +00:00
worker . Start ( ) ;
}
2024-03-02 11:51:05 +00:00
private static async void InstallUpdate ( UpdateDialog updateDialog , string updateFile )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
// Extract Update
updateDialog . MainText . Text = "Extracting Update..." ;
updateDialog . ProgressBar . Value = 0 ;
2023-01-20 20:30:21 +00:00
2024-03-02 11:51:05 +00:00
if ( OperatingSystem . IsLinux ( ) )
2023-02-25 11:30:48 +00:00
{
2024-03-02 11:51:05 +00:00
using Stream inStream = File . OpenRead ( updateFile ) ;
using Stream gzipStream = new GZipInputStream ( inStream ) ;
using TarInputStream tarStream = new ( gzipStream , Encoding . ASCII ) ;
updateDialog . ProgressBar . MaxValue = inStream . Length ;
await Task . Run ( ( ) = >
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
TarEntry tarEntry ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
if ( ! OperatingSystem . IsWindows ( ) )
{
while ( ( tarEntry = tarStream . GetNextEntry ( ) ) ! = null )
{
if ( tarEntry . IsDirectory )
{
continue ;
}
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
string outPath = Path . Combine ( _updateDir , tarEntry . Name ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
Directory . CreateDirectory ( Path . GetDirectoryName ( outPath ) ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
using FileStream outStream = File . OpenWrite ( outPath ) ;
tarStream . CopyEntryContents ( outStream ) ;
2023-02-22 08:13:50 +00:00
2024-03-02 11:51:05 +00:00
File . SetUnixFileMode ( outPath , ( UnixFileMode ) tarEntry . TarHeader . Mode ) ;
File . SetLastWriteTime ( outPath , DateTime . SpecifyKind ( tarEntry . ModTime , DateTimeKind . Utc ) ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
TarEntry entry = tarEntry ;
2023-01-20 20:30:21 +00:00
2024-03-02 11:51:05 +00:00
Application . Invoke ( delegate
{
updateDialog . ProgressBar . Value + = entry . Size ;
} ) ;
}
}
} ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
updateDialog . ProgressBar . Value = inStream . Length ;
}
else
2023-02-25 11:30:48 +00:00
{
2024-03-02 11:51:05 +00:00
using Stream inStream = File . OpenRead ( updateFile ) ;
using ZipFile zipFile = new ( inStream ) ;
updateDialog . ProgressBar . MaxValue = zipFile . Count ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
await Task . Run ( ( ) = >
{
foreach ( ZipEntry zipEntry in zipFile )
{
if ( zipEntry . IsDirectory )
{
continue ;
}
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
string outPath = Path . Combine ( _updateDir , zipEntry . Name ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
Directory . CreateDirectory ( Path . GetDirectoryName ( outPath ) ) ;
2023-07-07 21:03:27 +00:00
2024-03-02 11:51:05 +00:00
using Stream zipStream = zipFile . GetInputStream ( zipEntry ) ;
using FileStream outStream = File . OpenWrite ( outPath ) ;
zipStream . CopyTo ( outStream ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
File . SetLastWriteTime ( outPath , DateTime . SpecifyKind ( zipEntry . DateTime , DateTimeKind . Utc ) ) ;
2023-02-25 11:30:48 +00:00
2024-03-02 11:51:05 +00:00
Application . Invoke ( delegate
{
updateDialog . ProgressBar . Value + + ;
} ) ;
}
2023-01-20 20:30:21 +00:00
} ) ;
2022-05-15 11:30:15 +00:00
}
// Delete downloaded zip
File . Delete ( updateFile ) ;
List < string > allFiles = EnumerateFilesToDelete ( ) . ToList ( ) ;
2024-03-02 11:51:05 +00:00
updateDialog . MainText . Text = "Renaming Old Files..." ;
updateDialog . ProgressBar . Value = 0 ;
updateDialog . ProgressBar . MaxValue = allFiles . Count ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
// Replace old files
await Task . Run ( ( ) = >
2022-05-15 11:30:15 +00:00
{
2023-09-25 22:04:58 +00:00
foreach ( string file in allFiles )
2022-05-15 11:30:15 +00:00
{
2023-09-25 22:04:58 +00:00
try
2022-05-15 11:30:15 +00:00
{
2023-09-25 22:04:58 +00:00
File . Move ( file , file + ".ryuold" ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
Application . Invoke ( delegate
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
updateDialog . ProgressBar . Value + + ;
2023-09-25 22:04:58 +00:00
} ) ;
2022-05-15 11:30:15 +00:00
}
2023-09-25 22:04:58 +00:00
catch
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
Logger . Warning ? . Print ( LogClass . Application , "Updater was unable to rename file: " + file ) ;
2023-09-25 22:04:58 +00:00
}
}
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
Application . Invoke ( delegate
2023-09-25 22:04:58 +00:00
{
2024-03-02 11:51:05 +00:00
updateDialog . MainText . Text = "Adding New Files..." ;
updateDialog . ProgressBar . Value = 0 ;
updateDialog . ProgressBar . MaxValue = Directory . GetFiles ( _updatePublishDir , "*" , SearchOption . AllDirectories ) . Length ;
2022-05-15 11:30:15 +00:00
} ) ;
2024-03-02 11:51:05 +00:00
MoveAllFilesOver ( _updatePublishDir , _homeDir , updateDialog ) ;
} ) ;
2023-09-25 22:04:58 +00:00
2024-03-02 11:51:05 +00:00
Directory . Delete ( _updateDir , true ) ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
updateDialog . MainText . Text = "Update Complete!" ;
updateDialog . SecondaryText . Text = "Do you want to restart Ryujinx now?" ;
updateDialog . Modal = true ;
2022-05-15 11:30:15 +00:00
2024-03-02 11:51:05 +00:00
updateDialog . ProgressBar . Hide ( ) ;
updateDialog . YesButton . Show ( ) ;
updateDialog . NoButton . Show ( ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-20 20:30:21 +00:00
public static bool CanUpdate ( bool showWarnings )
2022-05-15 11:30:15 +00:00
{
#if ! DISABLE_UPDATER
if ( ! NetworkInterface . GetIsNetworkAvailable ( ) )
{
if ( showWarnings )
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateWarningDialog ( "You are not connected to the Internet!" , "Please verify that you have a working Internet connection!" ) ;
2022-05-15 11:30:15 +00:00
}
return false ;
}
2024-01-29 18:58:18 +00:00
if ( Program . Version . Contains ( "dirty" ) | | ! ReleaseInformation . IsValid )
2022-05-15 11:30:15 +00:00
{
if ( showWarnings )
{
2024-03-02 11:51:05 +00:00
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." ) ;
2022-05-15 11:30:15 +00:00
}
return false ;
}
return true ;
#else
if ( showWarnings )
{
2024-01-29 18:58:18 +00:00
if ( ReleaseInformation . IsFlatHubBuild )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateWarningDialog ( "Updater Disabled!" , "Please update Ryujinx via FlatHub." ) ;
2022-05-15 11:30:15 +00:00
}
else
{
2024-03-02 11:51:05 +00:00
GtkDialog . CreateWarningDialog ( "Updater Disabled!" , "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version." ) ;
2022-05-15 11:30:15 +00:00
}
}
return false ;
#endif
}
2023-02-25 11:30:48 +00:00
// NOTE: This method should always reflect the latest build layout.
2022-05-15 11:30:15 +00:00
private static IEnumerable < string > EnumerateFilesToDelete ( )
{
2023-07-07 21:03:27 +00:00
var files = Directory . EnumerateFiles ( _homeDir ) ; // All files directly in base dir.
2022-05-15 11:30:15 +00:00
2023-06-05 12:19:17 +00:00
// Determine and exclude user files only when the updater is running, not when cleaning old files
2024-03-02 11:51:05 +00:00
if ( Running )
2023-06-05 12:19:17 +00:00
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
2023-07-07 21:03:27 +00:00
var oldFiles = Directory . EnumerateFiles ( _homeDir , "*" , SearchOption . TopDirectoryOnly ) . Select ( Path . GetFileName ) ;
var newFiles = Directory . EnumerateFiles ( _updatePublishDir , "*" , SearchOption . TopDirectoryOnly ) . Select ( Path . GetFileName ) ;
var userFiles = oldFiles . Except ( newFiles ) . Select ( filename = > Path . Combine ( _homeDir , filename ) ) ;
2023-06-05 12:19:17 +00:00
// Remove user files from the paths in files.
files = files . Except ( userFiles ) ;
}
2022-05-15 11:30:15 +00:00
if ( OperatingSystem . IsWindows ( ) )
{
2023-07-07 21:03:27 +00:00
foreach ( string dir in _windowsDependencyDirs )
2022-05-15 11:30:15 +00:00
{
2023-07-07 21:03:27 +00:00
string dirPath = Path . Combine ( _homeDir , dir ) ;
2022-05-15 11:30:15 +00:00
if ( Directory . Exists ( dirPath ) )
{
files = files . Concat ( Directory . EnumerateFiles ( dirPath , "*" , SearchOption . AllDirectories ) ) ;
}
}
}
2023-04-16 09:19:33 +00:00
return files . Where ( f = > ! new FileInfo ( f ) . Attributes . HasFlag ( FileAttributes . Hidden | FileAttributes . System ) ) ;
2022-05-15 11:30:15 +00:00
}
2024-03-02 11:51:05 +00:00
private static void MoveAllFilesOver ( string root , string dest , UpdateDialog dialog )
2022-05-15 11:30:15 +00:00
{
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 ) ) ;
}
2024-03-02 11:51:05 +00:00
MoveAllFilesOver ( directory , Path . Combine ( dest , dirName ) , dialog ) ;
2022-05-15 11:30:15 +00:00
}
foreach ( string file in Directory . GetFiles ( root ) )
{
File . Move ( file , Path . Combine ( dest , Path . GetFileName ( file ) ) , true ) ;
2024-03-02 11:51:05 +00:00
Application . Invoke ( delegate
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
dialog . ProgressBar . Value + + ;
2022-05-15 11:30:15 +00:00
} ) ;
}
}
public static void CleanupUpdate ( )
{
2024-03-02 11:51:05 +00:00
foreach ( string file in EnumerateFilesToDelete ( ) )
2022-05-15 11:30:15 +00:00
{
2024-03-02 11:51:05 +00:00
if ( Path . GetExtension ( file ) . EndsWith ( ".ryuold" ) )
{
File . Delete ( file ) ;
}
2022-05-15 11:30:15 +00:00
}
}
}
2023-07-07 21:03:27 +00:00
}