aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMary <mary@mary.zone>2023-02-25 12:30:48 +0100
committerGitHub <noreply@github.com>2023-02-25 12:30:48 +0100
commitf663a5cd38e0ac0191f5859ed5bc25f5a7a9a907 (patch)
tree9616ee5265310589315c37baf80905eebf77875e
parentf7c2e867f4e0c9067c0c88f58b5df4cef6ee4399 (diff)
macos: Add updater support (#4464)1.1.641
This is a very basic updater but should be enough for now. --------- Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
-rw-r--r--Ryujinx.Ava/Modules/Updater/Updater.cs229
-rwxr-xr-xdistribution/macos/create_app_bundle.sh1
-rwxr-xr-xdistribution/macos/create_macos_release.sh2
-rwxr-xr-xdistribution/macos/updater.sh39
4 files changed, 177 insertions, 94 deletions
diff --git a/Ryujinx.Ava/Modules/Updater/Updater.cs b/Ryujinx.Ava/Modules/Updater/Updater.cs
index 511e273e..e89abd1d 100644
--- a/Ryujinx.Ava/Modules/Updater/Updater.cs
+++ b/Ryujinx.Ava/Modules/Updater/Updater.cs
@@ -21,6 +21,7 @@ using System.Net.Http;
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;
@@ -57,7 +58,7 @@ namespace Ryujinx.Modules
// Detect current platform
if (OperatingSystem.IsMacOS())
{
- _platformExt = "osx_x64.zip";
+ _platformExt = "macos_universal.app.tar.gz";
}
else if (OperatingSystem.IsWindows())
{
@@ -286,22 +287,40 @@ namespace Ryujinx.Modules
if (_updateSuccessful)
{
- var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
- LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
- LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
+ bool shouldRestart = true;
+
+ if (!OperatingSystem.IsMacOS())
+ {
+ shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
+ LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
+ LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
+ }
if (shouldRestart)
{
+ List<string> arguments = CommandLineState.Arguments.ToList();
string ryuName = Path.GetFileName(Environment.ProcessPath);
- string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
+ string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ string executablePath = Path.Combine(executableDirectory, ryuName);
- if (!Path.Exists(ryuExe))
+ if (!Path.Exists(executablePath))
{
- ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
+ executablePath = Path.Combine(executableDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
}
- Process.Start(ryuExe, CommandLineState.Arguments);
+ // On macOS we perform the update at relaunch.
+ if (OperatingSystem.IsMacOS())
+ {
+ string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
+ string newBundlePath = Path.Combine(UpdateDir, "Ryujinx.app");
+ string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
+ string currentPid = Process.GetCurrentProcess().Id.ToString();
+ executablePath = "/bin/bash";
+ arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
+ }
+
+ Process.Start(executablePath, arguments);
Environment.Exit(0);
}
}
@@ -381,6 +400,15 @@ namespace Ryujinx.Modules
File.WriteAllBytes(updateFile, mergedFileBytes);
+ // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
+ if (OperatingSystem.IsMacOS())
+ {
+ using (Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }))
+ {
+ xattrProcess.WaitForExit();
+ }
+ }
+
try
{
InstallUpdate(taskDialog, updateFile);
@@ -470,87 +498,98 @@ namespace Ryujinx.Modules
worker.Start();
}
- private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
+ [SupportedOSPlatform("linux")]
+ [SupportedOSPlatform("macos")]
+ private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
- // Extract Update
- taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
- taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
+ using Stream inStream = File.OpenRead(archivePath);
+ using GZipInputStream gzipStream = new(inStream);
+ using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
- if (OperatingSystem.IsLinux())
- {
- using Stream inStream = File.OpenRead(updateFile);
- using GZipInputStream gzipStream = new(inStream);
- using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
+ TarEntry tarEntry;
- await Task.Run(() =>
+ while ((tarEntry = tarStream.GetNextEntry()) is not null)
+ {
+ if (tarEntry.IsDirectory)
{
- TarEntry tarEntry;
-
- if (!OperatingSystem.IsWindows())
- {
- while ((tarEntry = tarStream.GetNextEntry()) is not null)
- {
- if (tarEntry.IsDirectory) continue;
-
- string outPath = Path.Combine(UpdateDir, tarEntry.Name);
+ continue;
+ }
- Directory.CreateDirectory(Path.GetDirectoryName(outPath));
+ string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
- using (FileStream outStream = File.OpenWrite(outPath))
- {
- tarStream.CopyEntryContents(outStream);
- }
+ Directory.CreateDirectory(Path.GetDirectoryName(outPath));
- File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
- File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
+ using (FileStream outStream = File.OpenWrite(outPath))
+ {
+ tarStream.CopyEntryContents(outStream);
+ }
- Dispatcher.UIThread.Post(() =>
- {
- if (tarEntry is null)
- {
- return;
- }
+ File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
+ File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
- taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
- });
- }
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (tarEntry is null)
+ {
+ return;
}
- });
- taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
+ taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
+ });
}
- else
- {
- using Stream inStream = File.OpenRead(updateFile);
- using ZipFile zipFile = new(inStream);
+ }
- await Task.Run(() =>
- {
- double count = 0;
- foreach (ZipEntry zipEntry in zipFile)
- {
- count++;
- if (zipEntry.IsDirectory) continue;
+ private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
+ {
+ using Stream inStream = File.OpenRead(archivePath);
+ using ZipFile zipFile = new(inStream);
- string outPath = Path.Combine(UpdateDir, zipEntry.Name);
+ double count = 0;
+ foreach (ZipEntry zipEntry in zipFile)
+ {
+ count++;
+ if (zipEntry.IsDirectory) continue;
- Directory.CreateDirectory(Path.GetDirectoryName(outPath));
+ string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
- using (Stream zipStream = zipFile.GetInputStream(zipEntry))
- using (FileStream outStream = File.OpenWrite(outPath))
- {
- zipStream.CopyTo(outStream);
- }
+ Directory.CreateDirectory(Path.GetDirectoryName(outPath));
- File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
+ using (Stream zipStream = zipFile.GetInputStream(zipEntry))
+ using (FileStream outStream = File.OpenWrite(outPath))
+ {
+ zipStream.CopyTo(outStream);
+ }
- Dispatcher.UIThread.Post(() =>
- {
- taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
- });
- }
+ File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
});
}
+ }
+
+ private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
+ {
+ // Extract Update
+ taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
+ taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
+
+ await Task.Run(() =>
+ {
+ if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
+ }
+ else if (OperatingSystem.IsWindows())
+ {
+ ExtractZipFile(taskDialog, updateFile, UpdateDir);
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ });
// Delete downloaded zip
File.Delete(updateFile);
@@ -560,38 +599,42 @@ namespace Ryujinx.Modules
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
- // Replace old files
- await Task.Run(() =>
+ // NOTE: On macOS, replacement is delayed to the restart phase.
+ if (!OperatingSystem.IsMacOS())
{
- double count = 0;
- foreach (string file in allFiles)
+ // Replace old files
+ await Task.Run(() =>
{
- count++;
- try
+ double count = 0;
+ foreach (string file in allFiles)
{
- File.Move(file, file + ".ryuold");
+ count++;
+ try
+ {
+ File.Move(file, file + ".ryuold");
- Dispatcher.UIThread.Post(() =>
+ Dispatcher.UIThread.Post(() =>
+ {
+ taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
+ });
+ }
+ catch
{
- taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
- });
+ Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
+ }
}
- catch
+
+ Dispatcher.UIThread.Post(() =>
{
- Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
- }
- }
+ taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
+ taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
+ });
- Dispatcher.UIThread.Post(() =>
- {
- taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
- taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
+ MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
});
- MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
- });
-
- Directory.Delete(UpdateDir, true);
+ Directory.Delete(UpdateDir, true);
+ }
_updateSuccessful = true;
@@ -601,7 +644,7 @@ namespace Ryujinx.Modules
public static bool CanUpdate(bool showWarnings)
{
#if !DISABLE_UPDATER
- if (RuntimeInformation.OSArchitecture != Architecture.X64)
+ if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
{
if (showWarnings)
{
@@ -674,7 +717,7 @@ namespace Ryujinx.Modules
#endif
}
- // NOTE: This method should always reflect the latest build layout.s
+ // NOTE: This method should always reflect the latest build layout.
private static IEnumerable<string> EnumerateFilesToDelete()
{
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
diff --git a/distribution/macos/create_app_bundle.sh b/distribution/macos/create_app_bundle.sh
index 8076303c..b62f3491 100755
--- a/distribution/macos/create_app_bundle.sh
+++ b/distribution/macos/create_app_bundle.sh
@@ -24,6 +24,7 @@ cp $PUBLISH_DIRECTORY/*.dylib $APP_BUNDLE_DIRECTORY/Contents/Frameworks
# Then resources
cp Info.plist $APP_BUNDLE_DIRECTORY/Contents
cp Ryujinx.icns $APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns
+cp updater.sh $APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh
cp -r $PUBLISH_DIRECTORY/THIRDPARTY.md $APP_BUNDLE_DIRECTORY/Contents/Resources
echo -n "APPL????" > $APP_BUNDLE_DIRECTORY/Contents/PkgInfo
diff --git a/distribution/macos/create_macos_release.sh b/distribution/macos/create_macos_release.sh
index 545baf20..d979ec8f 100755
--- a/distribution/macos/create_macos_release.sh
+++ b/distribution/macos/create_macos_release.sh
@@ -27,7 +27,7 @@ EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
rm -rf $TEMP_DIRECTORY
mkdir -p $TEMP_DIRECTORY
-DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID -p:ExtraDefineConstants=DISABLE_UPDATER --self-contained true"
+DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID --self-contained true"
dotnet restore
dotnet build -c Release Ryujinx.Ava
diff --git a/distribution/macos/updater.sh b/distribution/macos/updater.sh
new file mode 100755
index 00000000..b60ac34d
--- /dev/null
+++ b/distribution/macos/updater.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+set -e
+
+INSTALL_DIRECTORY=$1
+NEW_APP_DIRECTORY=$2
+APP_PID=$3
+APP_ARGUMENTS="${@:4}"
+
+error_handler() {
+ local lineno="$1"
+
+ script="""
+ set alertTitle to \"Ryujinx - Updater error\"
+ set alertMessage to \"An error occurred during Ryujinx update (updater.sh:$lineno)\n\nPlease download the update manually from our website if the problem persists.\"
+ display dialog alertMessage with icon caution with title alertTitle buttons {\"Open Download Page\", \"Exit\"}
+ set the button_pressed to the button returned of the result
+
+ if the button_pressed is \"Open Download Page\" then
+ open location \"https://ryujinx.org/download\"
+ end if
+ """
+
+ osascript -e "$script"
+ exit 1
+}
+
+trap 'error_handler ${LINENO}' ERR
+
+# Wait for Ryujinx to exit
+# NOTE: in case no fds are open, lsof could be returning with a process still living.
+# We wait 1s and assume the process stopped after that
+lsof -p $APP_PID +r 1 &>/dev/null
+sleep 1
+
+# Now replace and reopen.
+rm -rf "$INSTALL_DIRECTORY"
+mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
+open -a "$INSTALL_DIRECTORY" --args "$APP_ARGUMENTS"