diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml
index 3a23f2ae8..a7db99713 100644
--- a/.forgejo/workflows/build.yml
+++ b/.forgejo/workflows/build.yml
@@ -46,13 +46,17 @@ jobs:
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
+ - name: Install 7zip
+ run: |
+ sudo apt update && sudo apt install -y 7zip
+
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.forgejo/csc.json"
- name: Get version info
id: version_info
run: |
- echo "result=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
+ echo "result=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
shell: bash
@@ -83,14 +87,24 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.result }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
if: forgejo.event_name == 'pull_request'
- - name: Set executable bit
+ - name: Packing Windows builds
+ if: contains(matrix.platform.name, 'win')
run: |
- chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
- if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
+ 7z a artifact/ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}.7z publish
+ shell: bash
+
+ - name: Upload Ryujinx Windows artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
+ path: artifact
+ if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'win')
- name: Build AppImage
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
run: |
+ chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
+
PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
@@ -118,14 +132,7 @@ jobs:
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
shell: bash
- - name: Upload Ryujinx artifact
- uses: actions/upload-artifact@v4
- with:
- name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
- path: publish
- if: forgejo.event_name == 'pull_request'
-
- - name: Upload Ryujinx (AppImage) artifact
+ - name: Upload Ryujinx AppImage artifact
uses: actions/upload-artifact@v4
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
with:
diff --git a/.forgejo/workflows/canary.yml b/.forgejo/workflows/canary.yml
index 025dc2417..277d23c67 100644
--- a/.forgejo/workflows/canary.yml
+++ b/.forgejo/workflows/canary.yml
@@ -84,6 +84,7 @@ jobs:
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
+ 7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
popd
shell: bash
@@ -94,6 +95,7 @@ jobs:
rm libarmeilleure-jitsupport.dylib
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
+ tar -cJvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gx ../publish
popd
shell: bash
diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml
index c6da033b1..45123c2cd 100644
--- a/.forgejo/workflows/release.yml
+++ b/.forgejo/workflows/release.yml
@@ -81,6 +81,7 @@ jobs:
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
+ 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
popd
shell: bash
@@ -91,6 +92,7 @@ jobs:
rm libarmeilleure-jitsupport.dylib
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
+ tar -cJvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
popd
shell: bash
diff --git a/Directory.Packages.props b/Directory.Packages.props
index c17a8794d..5c7892bad 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,6 +8,7 @@
+
@@ -43,12 +44,11 @@
-
+
-
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index ecbcc7927..926af7f50 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -46,6 +46,7 @@
+
@@ -68,7 +69,6 @@
-
diff --git a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs
index 73fe9f66b..1972e1422 100644
--- a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs
+++ b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs
@@ -91,7 +91,7 @@ namespace Ryujinx.Ava.Systems
}
// If build URL not found, assume no new update is available.
- if (_versionResponse.ArtifactUrl is null or "")
+ if (string.IsNullOrEmpty(_versionResponse.ArtifactUrl))
{
if (showVersionUpToDate)
{
@@ -123,6 +123,8 @@ namespace Ryujinx.Ava.Systems
return default;
}
+ _connectionCount = (int)_versionResponse.MaxConcurrency;
+
return (currentVersion, newVersion);
}
}
diff --git a/src/Ryujinx/Systems/Updater/Updater.cs b/src/Ryujinx/Systems/Updater/Updater.cs
index ac168eb03..e1b45e4b0 100644
--- a/src/Ryujinx/Systems/Updater/Updater.cs
+++ b/src/Ryujinx/Systems/Updater/Updater.cs
@@ -1,19 +1,20 @@
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Gommon;
-using ICSharpCode.SharpZipLib.GZip;
-using ICSharpCode.SharpZipLib.Tar;
-using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
+using SharpCompress.Archives;
+using SharpCompress.Compressors.Xz;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Formats.Tar;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -21,7 +22,6 @@ using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -32,8 +32,8 @@ namespace Ryujinx.Ava.Systems
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 const int ConnectionCount = 4;
+ private static int _connectionCount = 1;
private static long _buildSize;
private static bool _updateSuccessful;
private static bool _running;
@@ -73,27 +73,6 @@ namespace Ryujinx.Ava.Systems
return;
}
- // Fetch build size information to learn chunk sizes.
- using HttpClient buildSizeClient = ConstructHttpClient();
- try
- {
- buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
-
- // Forgejo instance is located in Ukraine. Connection times will vary across the world.
- buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
-
- HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
-
- _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");
-
- _buildSize = -1;
- }
-
await Dispatcher.UIThread.InvokeAsync(async () =>
{
string newVersionString = ReleaseInformation.IsCanaryBuild
@@ -143,6 +122,14 @@ namespace Ryujinx.Ava.Systems
Directory.CreateDirectory(_updateDir);
+ // If we get a .zip url switch it to the preferred .7z file instead
+ // The update server still returns the .zip url by default for legacy support
+ downloadUrl = downloadUrl.Replace(".zip", ".7z");
+
+ // If we get a .tar.gz url switch it to the preferred .tar.xz file instead
+ // The update server still returns the .tar.gz url by default for legacy support
+ downloadUrl = downloadUrl.Replace(".tar.gz", ".tar.xz");
+
string updateFile = Path.Combine(_updateDir, "update.bin");
TaskDialog taskDialog = new()
@@ -153,6 +140,27 @@ namespace Ryujinx.Ava.Systems
ShowProgressBar = true,
XamlRoot = RyujinxApp.MainWindow,
};
+
+ // Fetch build size information to learn chunk sizes.
+ using HttpClient buildSizeClient = ConstructHttpClient();
+ try
+ {
+ buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
+
+ // Forgejo instance is located in Ukraine. Connection times will vary across the world.
+ buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
+
+ HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
+
+ _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");
+
+ _buildSize = -1;
+ }
taskDialog.Opened += (s, e) =>
{
@@ -234,22 +242,22 @@ namespace Ryujinx.Ava.Systems
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
// Multi-Threaded Updater
- long chunkSize = _buildSize / ConnectionCount;
- long remainderChunk = _buildSize % ConnectionCount;
+ long chunkSize = _buildSize / _connectionCount;
+ long remainderChunk = _buildSize % _connectionCount;
int completedRequests = 0;
int totalProgressPercentage = 0;
- int[] progressPercentage = new int[ConnectionCount];
+ int[] progressPercentage = new int[_connectionCount];
- List list = new(ConnectionCount);
- List webClients = new(ConnectionCount);
+ List list = new(_connectionCount);
+ List webClients = new(_connectionCount);
- for (int i = 0; i < ConnectionCount; i++)
+ for (int i = 0; i < _connectionCount; i++)
{
list.Add([]);
}
- for (int i = 0; i < ConnectionCount; i++)
+ for (int i = 0; i < _connectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
@@ -258,7 +266,7 @@ namespace Ryujinx.Ava.Systems
webClients.Add(client);
- if (i == ConnectionCount - 1)
+ if (i == _connectionCount - 1)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
@@ -275,7 +283,7 @@ namespace Ryujinx.Ava.Systems
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
- taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
+ taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
};
client.DownloadDataCompleted += (_, args) =>
@@ -294,10 +302,10 @@ namespace Ryujinx.Ava.Systems
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
- if (Equals(completedRequests, ConnectionCount))
+ if (Equals(completedRequests, _connectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
- for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
+ for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;
@@ -402,73 +410,33 @@ namespace Ryujinx.Ava.Systems
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
- private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
+ private static void ExtractTarGzipFile(string archivePath, string outputDirectoryPath)
{
using FileStream inStream = File.OpenRead(archivePath);
- using GZipInputStream gzipStream = new(inStream);
- using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
-
- TarEntry tarEntry;
-
- while ((tarEntry = tarStream.GetNextEntry()) is not null)
- {
- if (tarEntry.IsDirectory)
- {
- continue;
- }
-
- string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
-
- Directory.CreateDirectory(Path.GetDirectoryName(outPath));
-
- using FileStream outStream = File.OpenWrite(outPath);
- tarStream.CopyEntryContents(outStream);
-
- File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
- File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
-
- Dispatcher.UIThread.Post(() =>
- {
- if (tarEntry is null)
- {
- return;
- }
-
- taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
- });
- }
+ using GZipStream gzipStream = new(inStream, CompressionMode.Decompress);
+
+ TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
+ }
+
+ [SupportedOSPlatform("linux")]
+ [SupportedOSPlatform("macos")]
+ private static void ExtractTarXzipFile(string archivePath, string outputDirectoryPath)
+ {
+ using FileStream inStream = File.OpenRead(archivePath);
+ using XZStream gzipStream = new(inStream);
+
+ TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
}
- private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
+ private static void ExtractZipFile(string archivePath, string outputDirectoryPath)
{
- using Stream inStream = File.OpenRead(archivePath);
- using ZipFile zipFile = new(inStream);
-
- double count = 0;
- foreach (ZipEntry zipEntry in zipFile)
- {
- count++;
- if (zipEntry.IsDirectory)
- {
- continue;
- }
-
- string outPath = Path.Combine(outputDirectoryPath, 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));
-
- Dispatcher.UIThread.Post(() =>
- {
- taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
- });
- }
+ ZipFile.ExtractToDirectory(archivePath, outputDirectoryPath);
+ }
+
+ private static void Extract7ZipFile(string archivePath, string outputDirectoryPath)
+ {
+ IArchive archive = ArchiveFactory.OpenArchive(archivePath);
+ archive.WriteToDirectory(outputDirectoryPath);
}
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
@@ -479,16 +447,20 @@ namespace Ryujinx.Ava.Systems
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
- ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
+ ExtractTarXzipFile(updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
- ExtractZipFile(taskDialog, updateFile, _updateDir);
+ Extract7ZipFile(updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
+
+ // The new decompression implementations don't have a way to show progress
+ // so the progressbar is just set to 100% after the decompression is done
+ taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
// Delete downloaded zip
File.Delete(updateFile);