diff --git a/docs/compatibility.csv b/docs/compatibility.csv index 0fd8eadca..4cf9102bd 100644 --- a/docs/compatibility.csv +++ b/docs/compatibility.csv @@ -2480,6 +2480,7 @@ 010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49 010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17 010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36 +010071E0145F8000,"Rustler",,playable,2025-02-10 20:17:12 0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01 010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43 0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09 diff --git a/src/Ryujinx.Common/TitleIDs.cs b/src/Ryujinx.Common/TitleIDs.cs index 28d332a61..76d873f60 100644 --- a/src/Ryujinx.Common/TitleIDs.cs +++ b/src/Ryujinx.Common/TitleIDs.cs @@ -219,6 +219,7 @@ namespace Ryujinx.Common //Misc Games "010056e00853a000", // A Hat in Time "0100fd1014726000", // Baldurs Gate: Dark Alliance + "01008c2019598000", // Bluey: The Video Game "0100c6800b934000", // Brawlhalla "0100dbf01000a000", // Burnout Paradise Remastered "0100744001588000", // Cars 3: Driven to Win diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 71a86348c..1098a88e1 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -3425,26 +3425,101 @@ { "ID": "SettingsTabGeneralCheckUpdatesOnLaunch", "Translations": { - "ar_SA": "التحقق من وجود تحديثات عند التشغيل", - "de_DE": "Beim Start nach Updates suchen", - "el_GR": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", - "en_US": "Check for Updates on Launch", - "es_ES": "Buscar actualizaciones al iniciar", - "fr_FR": "Vérifier les mises à jour au démarrage", - "he_IL": "בדוק אם קיימים עדכונים בהפעלה", - "it_IT": "Controlla aggiornamenti all'avvio", - "ja_JP": "起動時にアップデートを確認する", - "ko_KR": "시작 시, 업데이트 확인", - "no_NO": "Se etter oppdateringer ved oppstart", - "pl_PL": "Sprawdzaj aktualizacje przy uruchomieniu", - "pt_BR": "Verificar se há atualizações ao iniciar", - "ru_RU": "Проверять наличие обновлений при запуске", - "sv_SE": "Leta efter uppdatering vid uppstart", - "th_TH": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", - "tr_TR": "Her Açılışta Güncellemeleri Denetle", - "uk_UA": "Перевіряти наявність оновлень під час запуску", - "zh_CN": "启动时检查更新", - "zh_TW": "啟動時檢查更新" + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Check for Updates:", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchOff", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Off", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Prompt", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchBackground", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Background", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" } }, { @@ -17772,6 +17847,31 @@ "zh_TW": "更新已停用!" } }, + { + "ID": "UpdaterBackgroundStatusBarButtonText", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Update Available!", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "ControllerSettingsRotate90", "Translations": { diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index d78ccc78d..db04e687b 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -7,6 +7,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using DynamicData; using DynamicData.Binding; using FluentAvalonia.UI.Controls; @@ -104,6 +105,13 @@ namespace Ryujinx.Ava.UI.ViewModels [ObservableProperty] private bool _isSubMenuOpen; [ObservableProperty] private ApplicationContextMenu _listAppContextMenu; [ObservableProperty] private ApplicationContextMenu _gridAppContextMenu; + [ObservableProperty] private bool _updateAvailable; + + public static AsyncRelayCommand UpdateCommand => Commands.Create(async () => + { + if (Updater.CanUpdate(true)) + await Updater.BeginUpdateAsync(true); + }); private bool _showLoadProgress; private bool _isGameRunning; diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 40f936e29..5e8fb0a83 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -15,6 +15,7 @@ using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration.System; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.GraphicsDriver; @@ -135,6 +136,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool RememberWindowState { get; set; } public bool ShowTitleBar { get; set; } public int HideCursor { get; set; } + public int UpdateCheckerType { get; set; } public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } public bool EnableMouse { get; set; } @@ -532,6 +534,7 @@ namespace Ryujinx.Ava.UI.ViewModels { ConfigurationState config = ConfigurationState.Instance; + //It is necessary that the data is used from the global configuration file if (string.IsNullOrEmpty(GameId)) { @@ -542,7 +545,8 @@ namespace Ryujinx.Ava.UI.ViewModels RememberWindowState = config.RememberWindowState; ShowTitleBar = config.ShowTitleBar; HideCursor = (int)config.HideCursor.Value; - + UpdateCheckerType = (int)config.UpdateCheckerType.Value; + GameDirectories.Clear(); GameDirectories.AddRange(config.UI.GameDirs.Value); @@ -643,6 +647,7 @@ namespace Ryujinx.Ava.UI.ViewModels ConfigurationState config = ConfigurationState.Instance; bool userConfigFile = string.IsNullOrEmpty(GameId); + if (userConfigFile) { // User Interface @@ -652,6 +657,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.RememberWindowState.Value = RememberWindowState; config.ShowTitleBar.Value = ShowTitleBar; config.HideCursor.Value = (HideCursorMode)HideCursor; + config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType; if (GameDirectoryChanged) { diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml index a0259c723..98416654b 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml @@ -23,7 +23,7 @@ Background="{DynamicResource ThemeContentBackgroundColor}" DockPanel.Dock="Bottom" IsVisible="{Binding ShowMenuAndStatusBar}" - ColumnDefinitions="Auto,Auto,*,Auto,Auto"> + ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto"> - + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index 77f9b7bf5..d71a7d795 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -6,6 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common" mc:Ignorable="d" x:DataType="viewModels:SettingsViewModel"> @@ -30,18 +31,33 @@ ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}" Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> - - - - + + + + + + + + + + + + + + + AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true)); AddAutoloadDirButton.Command = diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 120b82ff3..1def5c2fb 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -19,6 +19,7 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common; using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; @@ -400,10 +401,21 @@ namespace Ryujinx.Ava.UI.Windows await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); } - if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate()) + if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates) + return; + + switch (ConfigurationState.Instance.UpdateCheckerType.Value) { - await Updater.BeginUpdateAsync() - .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); + case UpdaterType.PromptAtStartup: + await Updater.BeginUpdateAsync() + .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); + break; + case UpdaterType.CheckInBackground: + if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions)) + { + Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming); + } + break; } } diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index f8b4b2beb..338e9de43 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -43,7 +43,18 @@ namespace Ryujinx.Ava private const int ConnectionCount = 4; private static string _buildVer; - private static string _platformExt; + + private static readonly string _platformExt = + RunningPlatform.IsMacOS + ? "macos_universal.app.tar.gz" + : RunningPlatform.IsWindows + ? "win_x64.zip" + : RunningPlatform.IsX64Linux + ? "linux_x64.tar.gz" + : RunningPlatform.IsArmLinux + ? "linux_arm64.tar.gz" + : throw new PlatformNotSupportedException(); + private static string _buildUrl; private static long _buildSize; private static bool _updateSuccessful; @@ -51,30 +62,8 @@ namespace Ryujinx.Ava private static readonly string[] _windowsDependencyDirs = []; - public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) + public static async Task> CheckVersionAsync(bool showVersionUpToDate = false) { - if (_running) - { - return; - } - - _running = true; - - // Detect current platform - if (OperatingSystem.IsMacOS()) - { - _platformExt = "macos_universal.app.tar.gz"; - } - else if (OperatingSystem.IsWindows()) - { - _platformExt = "win_x64.zip"; - } - else if (OperatingSystem.IsLinux()) - { - string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; - _platformExt = $"linux_{arch}.tar.gz"; - } - if (!Version.TryParse(Program.Version, out Version currentVersion)) { Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!"); @@ -85,7 +74,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } Logger.Info?.Print(LogClass.Application, "Checking for updates."); @@ -123,7 +112,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } break; @@ -149,7 +138,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } } catch (Exception exception) @@ -161,7 +150,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } if (!Version.TryParse(_buildVer, out Version newVersion)) @@ -174,9 +163,27 @@ namespace Ryujinx.Ava _running = false; + return default; + } + + return (currentVersion, newVersion); + } + + public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) + { + if (_running) + { return; } + _running = true; + + Optional<(Version, Version)> versionTuple = await CheckVersionAsync(showVersionUpToDate); + + if (_running is false || !versionTuple.HasValue) return; + + (Version currentVersion, Version newVersion) = versionTuple.Value; + if (newVersion <= currentVersion) { if (showVersionUpToDate) diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index 95364873b..d7235ef27 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 64; + public const int CurrentVersion = 65; /// /// Version of the configuration file format @@ -163,9 +163,14 @@ namespace Ryujinx.Ava.Utilities.Configuration public bool EnableDiscordIntegration { get; set; } /// - /// Checks for updates when Ryujinx starts when enabled + /// DEPRECATED: Checks for updates when Ryujinx starts when enabled /// public bool CheckUpdatesOnStart { get; set; } + + /// + /// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification. + /// + public UpdaterType UpdateCheckerType { get; set; } /// /// Show "Confirm Exit" Dialog diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 18f801a0e..571e93991 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -49,14 +49,16 @@ namespace Ryujinx.Ava.Utilities.Configuration configurationFileUpdated = true; } + EnableDiscordIntegration.Value = LoadSetting ? cff.EnableDiscordIntegration : EnableDiscordIntegration.Value; // Get from global config only CheckUpdatesOnStart.Value = LoadSetting ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only + UpdateCheckerType.Value = cff.UpdateCheckerType; ShowConfirmExit.Value = LoadSetting ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only RememberWindowState.Value = LoadSetting ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only ShowTitleBar.Value = LoadSetting ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only EnableHardwareAcceleration.Value = LoadSetting ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only HideCursor.Value = LoadSetting ? cff.HideCursor : HideCursor.Value; // Get from global config only - + Logger.EnableFileLog.Value = cff.EnableFileLog; Logger.EnableDebug.Value = cff.LoggingEnableDebug; Logger.EnableStub.Value = cff.LoggingEnableStub; @@ -438,7 +440,8 @@ namespace Ryujinx.Ava.Utilities.Configuration }), (62, static cff => cff.RainbowSpeed = 1f), (63, static cff => cff.MatchSystemTime = false), - (64, static cff => cff.LoggingEnableAvalonia = false) + (64, static cff => cff.LoggingEnableAvalonia = false), + (65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off) ); } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 8fbe20e05..b511b32dd 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -1,6 +1,7 @@ using ARMeilleure; using Gommon; using Ryujinx.Ava.Utilities.Configuration.System; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -767,6 +768,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Checks for updates when Ryujinx starts when enabled /// public ReactiveObject CheckUpdatesOnStart { get; private set; } + + /// + /// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification. + /// + public ReactiveObject UpdateCheckerType { get; private set; } /// /// Show "Confirm Exit" Dialog @@ -804,6 +810,7 @@ namespace Ryujinx.Ava.Utilities.Configuration Hacks = new HacksSection(); EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); + UpdateCheckerType = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); RememberWindowState = new ReactiveObject(); ShowTitleBar = new ReactiveObject(); diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index f8fbc90d8..7e4b79445 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -56,6 +56,7 @@ namespace Ryujinx.Ava.Utilities.Configuration DockedMode = System.EnableDockedMode, EnableDiscordIntegration = EnableDiscordIntegration, CheckUpdatesOnStart = CheckUpdatesOnStart, + UpdateCheckerType = UpdateCheckerType, ShowConfirmExit = ShowConfirmExit, RememberWindowState = RememberWindowState, ShowTitleBar = ShowTitleBar, @@ -175,7 +176,7 @@ namespace Ryujinx.Ava.Utilities.Configuration System.SystemTimeOffset.Value = 0; System.EnableDockedMode.Value = true; EnableDiscordIntegration.Value = true; - CheckUpdatesOnStart.Value = true; + UpdateCheckerType.Value = UpdaterType.PromptAtStartup; ShowConfirmExit.Value = true; RememberWindowState.Value = true; ShowTitleBar.Value = !OperatingSystem.IsWindows(); diff --git a/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs b/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs new file mode 100644 index 000000000..2ab17a497 --- /dev/null +++ b/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Ava.Utilities.Configuration.UI +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum UpdaterType + { + Off, + PromptAtStartup, + CheckInBackground + } +}