MainWindow.axaml.cs 29 KB

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