MainWindow.axaml.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using Avalonia.Interactivity;
  5. using Avalonia.Platform;
  6. using Avalonia.Threading;
  7. using DynamicData;
  8. using FluentAvalonia.UI.Controls;
  9. using LibHac.Tools.FsSystem;
  10. using Ryujinx.Ava.Common;
  11. using Ryujinx.Ava.Common.Locale;
  12. using Ryujinx.Ava.Input;
  13. using Ryujinx.Ava.UI.Applet;
  14. using Ryujinx.Ava.UI.Helpers;
  15. using Ryujinx.Ava.UI.ViewModels;
  16. using Ryujinx.Common.Logging;
  17. using Ryujinx.Graphics.Gpu;
  18. using Ryujinx.HLE.FileSystem;
  19. using Ryujinx.HLE.HOS;
  20. using Ryujinx.HLE.HOS.Services.Account.Acc;
  21. using Ryujinx.Input.HLE;
  22. using Ryujinx.Input.SDL2;
  23. using Ryujinx.Modules;
  24. using Ryujinx.UI.App.Common;
  25. using Ryujinx.UI.Common;
  26. using Ryujinx.UI.Common.Configuration;
  27. using Ryujinx.UI.Common.Helper;
  28. using System;
  29. using System.Collections.Generic;
  30. using System.Reactive.Linq;
  31. using System.Runtime.Versioning;
  32. using System.Threading;
  33. using System.Threading.Tasks;
  34. namespace Ryujinx.Ava.UI.Windows
  35. {
  36. public partial class MainWindow : StyleableWindow
  37. {
  38. internal static MainWindowViewModel MainWindowViewModel { get; private set; }
  39. private bool _isLoading;
  40. private bool _applicationsLoadedOnce;
  41. private UserChannelPersistence _userChannelPersistence;
  42. private static bool _deferLoad;
  43. private static string _launchPath;
  44. private static string _launchApplicationId;
  45. private static bool _startFullscreen;
  46. internal readonly AvaHostUIHandler UiHandler;
  47. private IDisposable _appLibraryAppsSubscription;
  48. public VirtualFileSystem VirtualFileSystem { get; private set; }
  49. public ContentManager ContentManager { get; private set; }
  50. public AccountManager AccountManager { get; private set; }
  51. public LibHacHorizonManager LibHacHorizonManager { get; private set; }
  52. public InputManager InputManager { get; private set; }
  53. internal MainWindowViewModel ViewModel { get; private set; }
  54. public SettingsWindow SettingsWindow { get; set; }
  55. public static bool ShowKeyErrorOnLoad { get; set; }
  56. public ApplicationLibrary ApplicationLibrary { get; set; }
  57. public readonly double StatusBarHeight;
  58. public readonly double MenuBarHeight;
  59. public MainWindow()
  60. {
  61. ViewModel = new MainWindowViewModel();
  62. MainWindowViewModel = ViewModel;
  63. DataContext = ViewModel;
  64. InitializeComponent();
  65. Load();
  66. UiHandler = new AvaHostUIHandler(this);
  67. ViewModel.Title = $"Ryujinx {Program.Version}";
  68. // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
  69. StatusBarHeight = StatusBarView.StatusBar.MinHeight;
  70. MenuBarHeight = MenuBar.MinHeight;
  71. double barHeight = MenuBarHeight + StatusBarHeight;
  72. Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
  73. Width /= Program.WindowScaleFactor;
  74. SetWindowSizePosition();
  75. if (Program.PreviewerDetached)
  76. {
  77. InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
  78. this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
  79. this.ScalingChanged += OnScalingChanged;
  80. }
  81. }
  82. /// <summary>
  83. /// Event handler for detecting OS theme change when using "Follow OS theme" option
  84. /// </summary>
  85. private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
  86. {
  87. if (Application.Current is App app)
  88. {
  89. app.ApplyConfiguredTheme();
  90. }
  91. }
  92. protected override void OnClosed(EventArgs e)
  93. {
  94. base.OnClosed(e);
  95. if (PlatformSettings != null)
  96. {
  97. PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged;
  98. }
  99. }
  100. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  101. {
  102. base.OnApplyTemplate(e);
  103. NotificationHelper.SetNotificationManager(this);
  104. }
  105. private void IsActiveChanged(bool obj)
  106. {
  107. ViewModel.IsActive = obj;
  108. }
  109. private void OnScalingChanged(object sender, EventArgs e)
  110. {
  111. Program.DesktopScaleFactor = this.RenderScaling;
  112. }
  113. private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
  114. {
  115. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
  116. Dispatcher.UIThread.Post(() =>
  117. {
  118. ViewModel.StatusBarProgressValue = e.NumAppsLoaded;
  119. ViewModel.StatusBarProgressMaximum = e.NumAppsFound;
  120. if (e.NumAppsFound == 0)
  121. {
  122. StatusBarView.LoadProgressBar.IsVisible = false;
  123. }
  124. if (e.NumAppsLoaded == e.NumAppsFound)
  125. {
  126. StatusBarView.LoadProgressBar.IsVisible = false;
  127. }
  128. });
  129. }
  130. public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
  131. {
  132. if (args.Application != null)
  133. {
  134. ViewModel.SelectedIcon = args.Application.Icon;
  135. ViewModel.LoadApplication(args.Application).Wait();
  136. }
  137. args.Handled = true;
  138. }
  139. internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg)
  140. {
  141. _deferLoad = true;
  142. _launchPath = launchPathArg;
  143. _launchApplicationId = launchApplicationId;
  144. _startFullscreen = startFullscreenArg;
  145. }
  146. public void SwitchToGameControl(bool startFullscreen = false)
  147. {
  148. ViewModel.ShowLoadProgress = false;
  149. ViewModel.ShowContent = true;
  150. ViewModel.IsLoadingIndeterminate = false;
  151. if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
  152. {
  153. ViewModel.ToggleFullscreen();
  154. }
  155. }
  156. public void ShowLoading(bool startFullscreen = false)
  157. {
  158. ViewModel.ShowContent = false;
  159. ViewModel.ShowLoadProgress = true;
  160. ViewModel.IsLoadingIndeterminate = true;
  161. if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
  162. {
  163. ViewModel.ToggleFullscreen();
  164. }
  165. }
  166. private void Initialize()
  167. {
  168. _userChannelPersistence = new UserChannelPersistence();
  169. VirtualFileSystem = VirtualFileSystem.CreateInstance();
  170. LibHacHorizonManager = new LibHacHorizonManager();
  171. ContentManager = new ContentManager(VirtualFileSystem);
  172. LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
  173. LibHacHorizonManager.InitializeArpServer();
  174. LibHacHorizonManager.InitializeBcatServer();
  175. LibHacHorizonManager.InitializeSystemClients();
  176. IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
  177. ? IntegrityCheckLevel.ErrorOnInvalid
  178. : IntegrityCheckLevel.None;
  179. ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel)
  180. {
  181. DesiredLanguage = ConfigurationState.Instance.System.Language,
  182. };
  183. // Save data created before we supported extra data in directory save data will not work properly if
  184. // given empty extra data. Luckily some of that extra data can be created using the data from the
  185. // save data indexer, which should be enough to check access permissions for user saves.
  186. // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
  187. // Consider removing this at some point in the future when we don't need to worry about old saves.
  188. VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient);
  189. AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile);
  190. VirtualFileSystem.ReloadKeySet();
  191. ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient);
  192. }
  193. [SupportedOSPlatform("linux")]
  194. private static async Task ShowVmMaxMapCountWarning()
  195. {
  196. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
  197. LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
  198. await ContentDialogHelper.CreateWarningDialog(
  199. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
  200. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
  201. );
  202. }
  203. [SupportedOSPlatform("linux")]
  204. private static async Task ShowVmMaxMapCountDialog()
  205. {
  206. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
  207. LinuxHelper.RecommendedVmMaxMapCount);
  208. UserResult response = await ContentDialogHelper.ShowTextDialog(
  209. $"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
  210. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
  211. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
  212. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
  213. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
  214. LocaleManager.Instance[LocaleKeys.InputDialogNo],
  215. (int)Symbol.Help
  216. );
  217. int rc;
  218. switch (response)
  219. {
  220. case UserResult.Ok:
  221. rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
  222. if (rc == 0)
  223. {
  224. Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
  225. }
  226. else
  227. {
  228. Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
  229. }
  230. break;
  231. case UserResult.No:
  232. rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
  233. if (rc == 0)
  234. {
  235. Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
  236. }
  237. else
  238. {
  239. Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
  240. }
  241. break;
  242. }
  243. }
  244. private async Task CheckLaunchState()
  245. {
  246. if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
  247. {
  248. Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
  249. if (LinuxHelper.PkExecPath is not null)
  250. {
  251. await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
  252. }
  253. else
  254. {
  255. await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
  256. }
  257. }
  258. if (!ShowKeyErrorOnLoad)
  259. {
  260. if (_deferLoad)
  261. {
  262. _deferLoad = false;
  263. if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
  264. {
  265. ApplicationData applicationData;
  266. if (_launchApplicationId != null)
  267. {
  268. applicationData = applications.Find(application => application.IdString == _launchApplicationId);
  269. if (applicationData != null)
  270. {
  271. await ViewModel.LoadApplication(applicationData, _startFullscreen);
  272. }
  273. else
  274. {
  275. Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'.");
  276. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
  277. }
  278. }
  279. else
  280. {
  281. applicationData = applications[0];
  282. await ViewModel.LoadApplication(applicationData, _startFullscreen);
  283. }
  284. }
  285. else
  286. {
  287. Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'.");
  288. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
  289. }
  290. }
  291. }
  292. else
  293. {
  294. ShowKeyErrorOnLoad = false;
  295. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
  296. }
  297. if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
  298. {
  299. await Updater.BeginParse(this, false).ContinueWith(task =>
  300. {
  301. Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
  302. }, TaskContinuationOptions.OnlyOnFaulted);
  303. }
  304. }
  305. private void Load()
  306. {
  307. StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
  308. ApplicationGrid.ApplicationOpened += Application_Opened;
  309. ApplicationGrid.DataContext = ViewModel;
  310. ApplicationList.ApplicationOpened += Application_Opened;
  311. ApplicationList.DataContext = ViewModel;
  312. }
  313. private void SetWindowSizePosition()
  314. {
  315. if (!ConfigurationState.Instance.RememberWindowState)
  316. {
  317. ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight) * Program.WindowScaleFactor;
  318. ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor;
  319. WindowState = WindowState.Normal;
  320. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  321. return;
  322. }
  323. PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
  324. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
  325. ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
  326. ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
  327. ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
  328. if (CheckScreenBounds(savedPoint))
  329. {
  330. Position = savedPoint;
  331. }
  332. else
  333. {
  334. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  335. }
  336. }
  337. private bool CheckScreenBounds(PixelPoint configPoint)
  338. {
  339. for (int i = 0; i < Screens.ScreenCount; i++)
  340. {
  341. if (Screens.All[i].Bounds.Contains(configPoint))
  342. {
  343. return true;
  344. }
  345. }
  346. Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
  347. return false;
  348. }
  349. private void SaveWindowSizePosition()
  350. {
  351. ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
  352. // Only save rectangle properties if the window is not in a maximized state.
  353. if (WindowState != WindowState.Maximized)
  354. {
  355. ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height;
  356. ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width;
  357. ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
  358. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
  359. }
  360. MainWindowViewModel.SaveConfig();
  361. }
  362. protected override void OnOpened(EventArgs e)
  363. {
  364. base.OnOpened(e);
  365. Initialize();
  366. /// <summary>
  367. /// Subscribe to the ColorValuesChanged event
  368. /// </summary>
  369. PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
  370. ViewModel.Initialize(
  371. ContentManager,
  372. StorageProvider,
  373. ApplicationLibrary,
  374. VirtualFileSystem,
  375. AccountManager,
  376. InputManager,
  377. _userChannelPersistence,
  378. LibHacHorizonManager,
  379. UiHandler,
  380. ShowLoading,
  381. SwitchToGameControl,
  382. SetMainContent,
  383. this);
  384. ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
  385. _appLibraryAppsSubscription?.Dispose();
  386. _appLibraryAppsSubscription = ApplicationLibrary.Applications
  387. .Connect()
  388. .ObserveOn(SynchronizationContext.Current)
  389. .Bind(ViewModel.Applications)
  390. .Subscribe();
  391. ViewModel.RefreshFirmwareStatus();
  392. // Load applications if no application was requested by the command line
  393. if (!_deferLoad)
  394. {
  395. LoadApplications();
  396. }
  397. #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  398. CheckLaunchState();
  399. #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  400. }
  401. private void SetMainContent(Control content = null)
  402. {
  403. content ??= GameLibrary;
  404. if (MainContent.Content != content)
  405. {
  406. // Load applications while switching to the GameLibrary if we haven't done that yet
  407. if (!_applicationsLoadedOnce && content == GameLibrary)
  408. {
  409. LoadApplications();
  410. }
  411. MainContent.Content = content;
  412. }
  413. }
  414. public static void UpdateGraphicsConfig()
  415. {
  416. #pragma warning disable IDE0055 // Disable formatting
  417. GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
  418. GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
  419. GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
  420. GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
  421. GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
  422. GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
  423. #pragma warning restore IDE0055
  424. }
  425. private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e)
  426. {
  427. var volumeSplitButton = sender as ToggleSplitButton;
  428. if (ViewModel.IsGameRunning)
  429. {
  430. if (!volumeSplitButton.IsChecked)
  431. {
  432. ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
  433. }
  434. else
  435. {
  436. ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
  437. ViewModel.AppHost.Device.SetVolume(0);
  438. }
  439. ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
  440. }
  441. }
  442. protected override void OnClosing(WindowClosingEventArgs e)
  443. {
  444. if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
  445. {
  446. e.Cancel = true;
  447. ConfirmExit();
  448. return;
  449. }
  450. ViewModel.IsClosing = true;
  451. if (ViewModel.AppHost != null)
  452. {
  453. ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
  454. ViewModel.AppHost.AppExit += (sender, e) =>
  455. {
  456. ViewModel.AppHost = null;
  457. Dispatcher.UIThread.Post(() =>
  458. {
  459. MainContent = null;
  460. Close();
  461. });
  462. };
  463. ViewModel.AppHost?.Stop();
  464. e.Cancel = true;
  465. return;
  466. }
  467. if (ConfigurationState.Instance.RememberWindowState)
  468. {
  469. SaveWindowSizePosition();
  470. }
  471. ApplicationLibrary.CancelLoading();
  472. InputManager.Dispose();
  473. _appLibraryAppsSubscription?.Dispose();
  474. Program.Exit();
  475. base.OnClosing(e);
  476. }
  477. private void ConfirmExit()
  478. {
  479. Dispatcher.UIThread.InvokeAsync(async () =>
  480. {
  481. ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
  482. if (ViewModel.IsClosing)
  483. {
  484. Close();
  485. }
  486. });
  487. }
  488. public void LoadApplications()
  489. {
  490. _applicationsLoadedOnce = true;
  491. StatusBarView.LoadProgressBar.IsVisible = true;
  492. ViewModel.StatusBarProgressMaximum = 0;
  493. ViewModel.StatusBarProgressValue = 0;
  494. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
  495. ReloadGameList();
  496. }
  497. public void ToggleFileType(string fileType)
  498. {
  499. _ = fileType switch
  500. {
  501. #pragma warning disable IDE0055 // Disable formatting
  502. "NSP" => ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSP,
  503. "PFS0" => ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.UI.ShownFileTypes.PFS0,
  504. "XCI" => ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.UI.ShownFileTypes.XCI,
  505. "NCA" => ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NCA,
  506. "NRO" => ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NRO,
  507. "NSO" => ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSO,
  508. _ => throw new ArgumentOutOfRangeException(fileType),
  509. #pragma warning restore IDE0055
  510. };
  511. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  512. LoadApplications();
  513. }
  514. private void ReloadGameList()
  515. {
  516. if (_isLoading)
  517. {
  518. return;
  519. }
  520. _isLoading = true;
  521. Thread applicationLibraryThread = new(() =>
  522. {
  523. ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
  524. ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
  525. var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
  526. if (autoloadDirs.Count > 0)
  527. {
  528. var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs);
  529. var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs);
  530. ShowNewContentAddedDialog(dlcLoaded, updatesLoaded);
  531. }
  532. _isLoading = false;
  533. })
  534. {
  535. Name = "GUI.ApplicationLibraryThread",
  536. IsBackground = true,
  537. };
  538. applicationLibraryThread.Start();
  539. }
  540. private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
  541. {
  542. var msg = "";
  543. if (numDlcAdded > 0 && numUpdatesAdded > 0)
  544. {
  545. msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded);
  546. }
  547. else if (numDlcAdded > 0)
  548. {
  549. msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded);
  550. }
  551. else if (numUpdatesAdded > 0)
  552. {
  553. msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded);
  554. }
  555. else
  556. {
  557. return Task.CompletedTask;
  558. }
  559. return Dispatcher.UIThread.InvokeAsync(async () =>
  560. {
  561. await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
  562. msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
  563. });
  564. }
  565. }
  566. }