diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index fbafffb27..c92ef3b6a 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -779,6 +779,11 @@ namespace Ryujinx.UI.App.Common } } + public void LoadTitleUpdates() + { + return; + } + public void LoadDownloadableContents() { _downloadableContents.Edit(it => @@ -789,8 +794,8 @@ namespace Ryujinx.UI.App.Common { var savedDlc = DownloadableContentsHelper.LoadDownloadableContentsJson(_virtualFileSystem, application.IdBase); it.AddOrUpdate(savedDlc); - - if(TryGetDownloadableContentFromFile(application.Path, out var bundledDlc)) + + if (TryGetDownloadableContentFromFile(application.Path, out var bundledDlc)) { var savedDlcLookup = savedDlc.Select(dlc => dlc.Item1).ToHashSet(); @@ -813,13 +818,13 @@ namespace Ryujinx.UI.App.Common } }); } - + private void SaveDownloadableContentsForGame(ulong titleIdBase) { var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList(); DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs); } - + public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs) { _downloadableContents.Edit(it => @@ -932,113 +937,113 @@ namespace Ryujinx.UI.App.Common return newDlcLoaded; } - public void AutoLoadTitleUpdates(List appDirs) + public int AutoLoadTitleUpdates(List appDirs) { - return; - // _cancellationToken = new CancellationTokenSource(); - // _titleUpdates.Clear(); - // - // // Builds the applications list with paths to found applications - // List applicationPaths = new(); - // - // try - // { - // foreach (string appDir in appDirs) - // { - // if (_cancellationToken.Token.IsCancellationRequested) - // { - // return; - // } - // - // if (!Directory.Exists(appDir)) - // { - // Logger.Warning?.Print(LogClass.Application, - // $"The specified game directory \"{appDir}\" does not exist."); - // - // continue; - // } - // - // try - // { - // EnumerationOptions options = new() - // { - // RecurseSubdirectories = true, - // IgnoreInaccessible = false, - // }; - // - // IEnumerable files = Directory.EnumerateFiles(appDir, "*", options) - // .Where(file => - // { - // return - // (Path.GetExtension(file).ToLower() is ".nsp" && - // ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || - // (Path.GetExtension(file).ToLower() is ".xci" && - // ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value); - // }); - // - // foreach (string app in files) - // { - // if (_cancellationToken.Token.IsCancellationRequested) - // { - // return; - // } - // - // var fileInfo = new FileInfo(app); - // - // try - // { - // var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? - // fileInfo.FullName; - // - // applicationPaths.Add(fullPath); - // } - // catch (IOException exception) - // { - // Logger.Warning?.Print(LogClass.Application, - // $"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); - // } - // } - // } - // catch (UnauthorizedAccessException) - // { - // Logger.Warning?.Print(LogClass.Application, - // $"Failed to get access to directory: \"{appDir}\""); - // } - // } - // - // // Loops through applications list, creating a struct and then firing an event containing the struct for each application - // foreach (string applicationPath in applicationPaths) - // { - // if (_cancellationToken.Token.IsCancellationRequested) - // { - // return; - // } - // - // if (TryGetTitleUpdatesFromFile(applicationPath, out List titleUpdates)) - // { - // foreach (var titleUpdate in titleUpdates) - // { - // OnTitleUpdateAdded(new TitleUpdateAddedEventArgs() - // { - // TitleUpdate = titleUpdate, - // }); - // } - // - // _titleUpdates.Edit(it => - // { - // foreach (var titleUpdate in titleUpdates) - // { - // it.AddOrUpdate((titleUpdate, false)); - // } - // }); - // } - // } - // } - // finally - // { - // _cancellationToken.Dispose(); - // _cancellationToken = null; - // } + return 0; + // _cancellationToken = new CancellationTokenSource(); + // _titleUpdates.Clear(); + // + // // Builds the applications list with paths to found applications + // List applicationPaths = new(); + // + // try + // { + // foreach (string appDir in appDirs) + // { + // if (_cancellationToken.Token.IsCancellationRequested) + // { + // return; + // } + // + // if (!Directory.Exists(appDir)) + // { + // Logger.Warning?.Print(LogClass.Application, + // $"The specified game directory \"{appDir}\" does not exist."); + // + // continue; + // } + // + // try + // { + // EnumerationOptions options = new() + // { + // RecurseSubdirectories = true, + // IgnoreInaccessible = false, + // }; + // + // IEnumerable files = Directory.EnumerateFiles(appDir, "*", options) + // .Where(file => + // { + // return + // (Path.GetExtension(file).ToLower() is ".nsp" && + // ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || + // (Path.GetExtension(file).ToLower() is ".xci" && + // ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value); + // }); + // + // foreach (string app in files) + // { + // if (_cancellationToken.Token.IsCancellationRequested) + // { + // return; + // } + // + // var fileInfo = new FileInfo(app); + // + // try + // { + // var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? + // fileInfo.FullName; + // + // applicationPaths.Add(fullPath); + // } + // catch (IOException exception) + // { + // Logger.Warning?.Print(LogClass.Application, + // $"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); + // } + // } + // } + // catch (UnauthorizedAccessException) + // { + // Logger.Warning?.Print(LogClass.Application, + // $"Failed to get access to directory: \"{appDir}\""); + // } + // } + // + // // Loops through applications list, creating a struct and then firing an event containing the struct for each application + // foreach (string applicationPath in applicationPaths) + // { + // if (_cancellationToken.Token.IsCancellationRequested) + // { + // return; + // } + // + // if (TryGetTitleUpdatesFromFile(applicationPath, out List titleUpdates)) + // { + // foreach (var titleUpdate in titleUpdates) + // { + // OnTitleUpdateAdded(new TitleUpdateAddedEventArgs() + // { + // TitleUpdate = titleUpdate, + // }); + // } + // + // _titleUpdates.Edit(it => + // { + // foreach (var titleUpdate in titleUpdates) + // { + // it.AddOrUpdate((titleUpdate, false)); + // } + // }); + // } + // } + // } + // finally + // { + // _cancellationToken.Dispose(); + // _cancellationToken = null; + // } } protected void OnApplicationAdded(ApplicationAddedEventArgs e) diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index af3ad0a1d..e60fac09b 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 51; + public const int CurrentVersion = 52; /// /// Version of the configuration file format @@ -167,6 +167,11 @@ namespace Ryujinx.UI.Common.Configuration /// public bool RememberWindowState { get; set; } + /// + /// Enables or disables automatically loading DLC/title updates on library refresh. + /// + public bool AutoloadContent { get; set; } + /// /// Enables hardware-accelerated rendering for Avalonia /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 8420dc5d9..4cd85aa01 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -632,6 +632,11 @@ namespace Ryujinx.UI.Common.Configuration /// public ReactiveObject RememberWindowState { get; private set; } + /// + /// Enables or disables automatically loading DLC/title updates on library refresh. + /// + public ReactiveObject AutoloadContent { get; private set; } + /// /// Enables hardware-accelerated rendering for Avalonia /// @@ -654,6 +659,7 @@ namespace Ryujinx.UI.Common.Configuration CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); RememberWindowState = new ReactiveObject(); + AutoloadContent = new ReactiveObject(); EnableHardwareAcceleration = new ReactiveObject(); HideCursor = new ReactiveObject(); } @@ -692,6 +698,7 @@ namespace Ryujinx.UI.Common.Configuration CheckUpdatesOnStart = CheckUpdatesOnStart, ShowConfirmExit = ShowConfirmExit, RememberWindowState = RememberWindowState, + AutoloadContent = AutoloadContent, EnableHardwareAcceleration = EnableHardwareAcceleration, HideCursor = HideCursor, EnableVsync = Graphics.EnableVsync, @@ -801,6 +808,7 @@ namespace Ryujinx.UI.Common.Configuration CheckUpdatesOnStart.Value = true; ShowConfirmExit.Value = true; RememberWindowState.Value = true; + AutoloadContent.Value = false; EnableHardwareAcceleration.Value = true; HideCursor.Value = HideCursorMode.OnIdle; Graphics.EnableVsync.Value = true; @@ -1477,6 +1485,15 @@ namespace Ryujinx.UI.Common.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 52) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 52."); + + configurationFileFormat.AutoloadContent = false; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; @@ -1508,6 +1525,7 @@ namespace Ryujinx.UI.Common.Configuration CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; RememberWindowState.Value = configurationFileFormat.RememberWindowState; + AutoloadContent.Value = configurationFileFormat.AutoloadContent; EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; HideCursor.Value = configurationFileFormat.HideCursor; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 269ece7f1..38e252076 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -98,6 +98,7 @@ "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralAutoloadContent": "Automatically load DLC/updates", "SettingsTabGeneralHideCursor": "Hide Cursor:", "SettingsTabGeneralHideCursorNever": "Never", "SettingsTabGeneralHideCursorOnIdle": "On Idle", @@ -709,7 +710,7 @@ "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Title Update Manager", - "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", + "UpdateWindowDlcAddedMessage": "{0} new update(s) added", "CheatWindowHeading": "Cheats Available for {0} [{1}]", "BuildId": "BuildId:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 70e5fa5c7..9223e578d 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -132,6 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool CheckUpdatesOnStart { get; set; } public bool ShowConfirmExit { get; set; } public bool RememberWindowState { get; set; } + public bool AutoloadContent { get; set; } public int HideCursor { get; set; } public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } @@ -392,6 +393,7 @@ namespace Ryujinx.Ava.UI.ViewModels CheckUpdatesOnStart = config.CheckUpdatesOnStart; ShowConfirmExit = config.ShowConfirmExit; RememberWindowState = config.RememberWindowState; + AutoloadContent = config.AutoloadContent; HideCursor = (int)config.HideCursor.Value; GameDirectories.Clear(); @@ -484,6 +486,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; config.ShowConfirmExit.Value = ShowConfirmExit; config.RememberWindowState.Value = RememberWindowState; + config.AutoloadContent.Value = AutoloadContent; config.HideCursor.Value = (HideCursorMode)HideCursor; if (_directoryChanged) diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index f9b9be44b..9d26effcc 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -39,6 +39,9 @@ + + + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs)); - // TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates(ConfigurationState.Instance.UI.GameDirs)); + TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates()); TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents()); - // TODO(jpr): conditional - var dlcLoaded = 0; - TimeIt("AUTO DLC", () => dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(ConfigurationState.Instance.UI.GameDirs)); - if (dlcLoaded > 0) + if (ConfigurationState.Instance.AutoloadContent) { - ShowNewContentAddedDialog(dlcLoaded, 0); + var updatesLoaded = 0; + TimeIt("auto updates", + () => updatesLoaded = + ApplicationLibrary.AutoLoadTitleUpdates(ConfigurationState.Instance.UI.GameDirs)); + + var dlcLoaded = 0; + TimeIt("auto dlc", + () => dlcLoaded = + ApplicationLibrary.AutoLoadDownloadableContents(ConfigurationState.Instance.UI.GameDirs)); + + ShowNewContentAddedDialog(dlcLoaded, updatesLoaded); } _isLoading = false; @@ -679,23 +686,29 @@ namespace Ryujinx.Ava.UI.Windows var elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine("[{0}] {1} ms", tag, elapsedMs); } - + private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded) { var msg = ""; - + if (numDlcAdded > 0 && numUpdatesAdded > 0) { msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded); - } else if (numDlcAdded > 0) + } + else if (numDlcAdded > 0) { msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded); - } else if (numUpdatesAdded > 0) + } + else if (numUpdatesAdded > 0) { msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded); } - - return msg == "" ? Task.CompletedTask : Dispatcher.UIThread.InvokeAsync(async () => + else + { + return Task.CompletedTask; + } + + return Dispatcher.UIThread.InvokeAsync(async () => { await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);