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