MainWindowViewModel.cs 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.ApplicationLifetimes;
  4. using Avalonia.Input;
  5. using Avalonia.Media;
  6. using Avalonia.Platform.Storage;
  7. using Avalonia.Threading;
  8. using DynamicData;
  9. using DynamicData.Binding;
  10. using LibHac.Common;
  11. using Ryujinx.Ava.Common;
  12. using Ryujinx.Ava.Common.Locale;
  13. using Ryujinx.Ava.Input;
  14. using Ryujinx.Ava.UI.Controls;
  15. using Ryujinx.Ava.UI.Helpers;
  16. using Ryujinx.Ava.UI.Models;
  17. using Ryujinx.Ava.UI.Models.Generic;
  18. using Ryujinx.Ava.UI.Renderer;
  19. using Ryujinx.Ava.UI.Windows;
  20. using Ryujinx.Common;
  21. using Ryujinx.Common.Configuration;
  22. using Ryujinx.Common.Logging;
  23. using Ryujinx.Cpu;
  24. using Ryujinx.HLE;
  25. using Ryujinx.HLE.FileSystem;
  26. using Ryujinx.HLE.HOS;
  27. using Ryujinx.HLE.HOS.Services.Account.Acc;
  28. using Ryujinx.HLE.Ui;
  29. using Ryujinx.Input.HLE;
  30. using Ryujinx.Modules;
  31. using Ryujinx.Ui.App.Common;
  32. using Ryujinx.Ui.Common;
  33. using Ryujinx.Ui.Common.Configuration;
  34. using Ryujinx.Ui.Common.Helper;
  35. using SixLabors.ImageSharp.PixelFormats;
  36. using System;
  37. using System.Collections.Generic;
  38. using System.Collections.ObjectModel;
  39. using System.IO;
  40. using System.Threading;
  41. using System.Threading.Tasks;
  42. using Image = SixLabors.ImageSharp.Image;
  43. using Key = Ryujinx.Input.Key;
  44. using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
  45. using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
  46. namespace Ryujinx.Ava.UI.ViewModels
  47. {
  48. public class MainWindowViewModel : BaseModel
  49. {
  50. private const int HotKeyPressDelayMs = 500;
  51. private ObservableCollection<ApplicationData> _applications;
  52. private string _aspectStatusText;
  53. private string _loadHeading;
  54. private string _cacheLoadStatus;
  55. private string _searchText;
  56. private Timer _searchTimer;
  57. private string _dockedStatusText;
  58. private string _fifoStatusText;
  59. private string _gameStatusText;
  60. private string _volumeStatusText;
  61. private string _gpuStatusText;
  62. private bool _isAmiiboRequested;
  63. private bool _isGameRunning;
  64. private bool _isFullScreen;
  65. private int _progressMaximum;
  66. private int _progressValue;
  67. private long _lastFullscreenToggle = Environment.TickCount64;
  68. private bool _showLoadProgress;
  69. private bool _showMenuAndStatusBar = true;
  70. private bool _showStatusSeparator;
  71. private Brush _progressBarForegroundColor;
  72. private Brush _progressBarBackgroundColor;
  73. private Brush _vsyncColor;
  74. private byte[] _selectedIcon;
  75. private bool _isAppletMenuActive;
  76. private int _statusBarProgressMaximum;
  77. private int _statusBarProgressValue;
  78. private bool _isPaused;
  79. private bool _showContent = true;
  80. private bool _isLoadingIndeterminate = true;
  81. private bool _showAll;
  82. private string _lastScannedAmiiboId;
  83. private bool _statusBarVisible;
  84. private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
  85. private string _showUiKey = "F4";
  86. private string _pauseKey = "F5";
  87. private string _screenshotKey = "F8";
  88. private float _volume;
  89. private float _volumeBeforeMute;
  90. private string _backendText;
  91. private bool _canUpdate = true;
  92. private Cursor _cursor;
  93. private string _title;
  94. private string _currentEmulatedGamePath;
  95. private readonly AutoResetEvent _rendererWaitEvent;
  96. private WindowState _windowState;
  97. private double _windowWidth;
  98. private double _windowHeight;
  99. private bool _isActive;
  100. public ApplicationData ListSelectedApplication;
  101. public ApplicationData GridSelectedApplication;
  102. private string TitleName { get; set; }
  103. internal AppHost AppHost { get; set; }
  104. public MainWindowViewModel()
  105. {
  106. Applications = new ObservableCollection<ApplicationData>();
  107. Applications.ToObservableChangeSet()
  108. .Filter(Filter)
  109. .Sort(GetComparer())
  110. .Bind(out _appsObservableList).AsObservableList();
  111. _rendererWaitEvent = new AutoResetEvent(false);
  112. if (Program.PreviewerDetached)
  113. {
  114. LoadConfigurableHotKeys();
  115. Volume = ConfigurationState.Instance.System.AudioVolume;
  116. }
  117. }
  118. public void Initialize(
  119. ContentManager contentManager,
  120. IStorageProvider storageProvider,
  121. ApplicationLibrary applicationLibrary,
  122. VirtualFileSystem virtualFileSystem,
  123. AccountManager accountManager,
  124. InputManager inputManager,
  125. UserChannelPersistence userChannelPersistence,
  126. LibHacHorizonManager libHacHorizonManager,
  127. IHostUiHandler uiHandler,
  128. Action<bool> showLoading,
  129. Action<bool> switchToGameControl,
  130. Action<Control> setMainContent,
  131. TopLevel topLevel)
  132. {
  133. ContentManager = contentManager;
  134. StorageProvider = storageProvider;
  135. ApplicationLibrary = applicationLibrary;
  136. VirtualFileSystem = virtualFileSystem;
  137. AccountManager = accountManager;
  138. InputManager = inputManager;
  139. UserChannelPersistence = userChannelPersistence;
  140. LibHacHorizonManager = libHacHorizonManager;
  141. UiHandler = uiHandler;
  142. ShowLoading = showLoading;
  143. SwitchToGameControl = switchToGameControl;
  144. SetMainContent = setMainContent;
  145. TopLevel = topLevel;
  146. }
  147. #region Properties
  148. public string SearchText
  149. {
  150. get => _searchText;
  151. set
  152. {
  153. _searchText = value;
  154. _searchTimer?.Dispose();
  155. _searchTimer = new Timer(TimerCallback, null, 1000, 0);
  156. }
  157. }
  158. private void TimerCallback(object obj)
  159. {
  160. RefreshView();
  161. _searchTimer.Dispose();
  162. _searchTimer = null;
  163. }
  164. public bool CanUpdate
  165. {
  166. get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
  167. set
  168. {
  169. _canUpdate = value;
  170. OnPropertyChanged();
  171. }
  172. }
  173. public Cursor Cursor
  174. {
  175. get => _cursor;
  176. set
  177. {
  178. _cursor = value;
  179. OnPropertyChanged();
  180. }
  181. }
  182. public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
  183. {
  184. get => _appsObservableList;
  185. set
  186. {
  187. _appsObservableList = value;
  188. OnPropertyChanged();
  189. }
  190. }
  191. public bool IsPaused
  192. {
  193. get => _isPaused;
  194. set
  195. {
  196. _isPaused = value;
  197. OnPropertyChanged();
  198. }
  199. }
  200. public long LastFullscreenToggle
  201. {
  202. get => _lastFullscreenToggle;
  203. set
  204. {
  205. _lastFullscreenToggle = value;
  206. OnPropertyChanged();
  207. }
  208. }
  209. public bool StatusBarVisible
  210. {
  211. get => _statusBarVisible && EnableNonGameRunningControls;
  212. set
  213. {
  214. _statusBarVisible = value;
  215. OnPropertyChanged();
  216. }
  217. }
  218. public bool EnableNonGameRunningControls => !IsGameRunning;
  219. public bool ShowFirmwareStatus => !ShowLoadProgress;
  220. public bool IsGameRunning
  221. {
  222. get => _isGameRunning;
  223. set
  224. {
  225. _isGameRunning = value;
  226. if (!value)
  227. {
  228. ShowMenuAndStatusBar = false;
  229. }
  230. OnPropertyChanged();
  231. OnPropertyChanged(nameof(EnableNonGameRunningControls));
  232. OnPropertyChanged(nameof(IsAppletMenuActive));
  233. OnPropertyChanged(nameof(StatusBarVisible));
  234. OnPropertyChanged(nameof(ShowFirmwareStatus));
  235. }
  236. }
  237. public bool IsAmiiboRequested
  238. {
  239. get => _isAmiiboRequested && _isGameRunning;
  240. set
  241. {
  242. _isAmiiboRequested = value;
  243. OnPropertyChanged();
  244. }
  245. }
  246. public bool ShowLoadProgress
  247. {
  248. get => _showLoadProgress;
  249. set
  250. {
  251. _showLoadProgress = value;
  252. OnPropertyChanged();
  253. OnPropertyChanged(nameof(ShowFirmwareStatus));
  254. }
  255. }
  256. public string GameStatusText
  257. {
  258. get => _gameStatusText;
  259. set
  260. {
  261. _gameStatusText = value;
  262. OnPropertyChanged();
  263. }
  264. }
  265. public bool IsFullScreen
  266. {
  267. get => _isFullScreen;
  268. set
  269. {
  270. _isFullScreen = value;
  271. OnPropertyChanged();
  272. }
  273. }
  274. public bool ShowAll
  275. {
  276. get => _showAll;
  277. set
  278. {
  279. _showAll = value;
  280. OnPropertyChanged();
  281. }
  282. }
  283. public string LastScannedAmiiboId
  284. {
  285. get => _lastScannedAmiiboId;
  286. set
  287. {
  288. _lastScannedAmiiboId = value;
  289. OnPropertyChanged();
  290. }
  291. }
  292. public ApplicationData SelectedApplication
  293. {
  294. get
  295. {
  296. return Glyph switch
  297. {
  298. Glyph.List => ListSelectedApplication,
  299. Glyph.Grid => GridSelectedApplication,
  300. _ => null,
  301. };
  302. }
  303. }
  304. public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
  305. public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
  306. public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
  307. public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
  308. public string LoadHeading
  309. {
  310. get => _loadHeading;
  311. set
  312. {
  313. _loadHeading = value;
  314. OnPropertyChanged();
  315. }
  316. }
  317. public string CacheLoadStatus
  318. {
  319. get => _cacheLoadStatus;
  320. set
  321. {
  322. _cacheLoadStatus = value;
  323. OnPropertyChanged();
  324. }
  325. }
  326. public Brush ProgressBarBackgroundColor
  327. {
  328. get => _progressBarBackgroundColor;
  329. set
  330. {
  331. _progressBarBackgroundColor = value;
  332. OnPropertyChanged();
  333. }
  334. }
  335. public Brush ProgressBarForegroundColor
  336. {
  337. get => _progressBarForegroundColor;
  338. set
  339. {
  340. _progressBarForegroundColor = value;
  341. OnPropertyChanged();
  342. }
  343. }
  344. public Brush VsyncColor
  345. {
  346. get => _vsyncColor;
  347. set
  348. {
  349. _vsyncColor = value;
  350. OnPropertyChanged();
  351. }
  352. }
  353. public byte[] SelectedIcon
  354. {
  355. get => _selectedIcon;
  356. set
  357. {
  358. _selectedIcon = value;
  359. OnPropertyChanged();
  360. }
  361. }
  362. public int ProgressMaximum
  363. {
  364. get => _progressMaximum;
  365. set
  366. {
  367. _progressMaximum = value;
  368. OnPropertyChanged();
  369. }
  370. }
  371. public int ProgressValue
  372. {
  373. get => _progressValue;
  374. set
  375. {
  376. _progressValue = value;
  377. OnPropertyChanged();
  378. }
  379. }
  380. public int StatusBarProgressMaximum
  381. {
  382. get => _statusBarProgressMaximum;
  383. set
  384. {
  385. _statusBarProgressMaximum = value;
  386. OnPropertyChanged();
  387. }
  388. }
  389. public int StatusBarProgressValue
  390. {
  391. get => _statusBarProgressValue;
  392. set
  393. {
  394. _statusBarProgressValue = value;
  395. OnPropertyChanged();
  396. }
  397. }
  398. public string FifoStatusText
  399. {
  400. get => _fifoStatusText;
  401. set
  402. {
  403. _fifoStatusText = value;
  404. OnPropertyChanged();
  405. }
  406. }
  407. public string GpuNameText
  408. {
  409. get => _gpuStatusText;
  410. set
  411. {
  412. _gpuStatusText = value;
  413. OnPropertyChanged();
  414. }
  415. }
  416. public string BackendText
  417. {
  418. get => _backendText;
  419. set
  420. {
  421. _backendText = value;
  422. OnPropertyChanged();
  423. }
  424. }
  425. public string DockedStatusText
  426. {
  427. get => _dockedStatusText;
  428. set
  429. {
  430. _dockedStatusText = value;
  431. OnPropertyChanged();
  432. }
  433. }
  434. public string AspectRatioStatusText
  435. {
  436. get => _aspectStatusText;
  437. set
  438. {
  439. _aspectStatusText = value;
  440. OnPropertyChanged();
  441. }
  442. }
  443. public string VolumeStatusText
  444. {
  445. get => _volumeStatusText;
  446. set
  447. {
  448. _volumeStatusText = value;
  449. OnPropertyChanged();
  450. }
  451. }
  452. public bool VolumeMuted => _volume == 0;
  453. public float Volume
  454. {
  455. get => _volume;
  456. set
  457. {
  458. _volume = value;
  459. if (_isGameRunning)
  460. {
  461. AppHost.Device.SetVolume(_volume);
  462. }
  463. OnPropertyChanged(nameof(VolumeStatusText));
  464. OnPropertyChanged(nameof(VolumeMuted));
  465. OnPropertyChanged();
  466. }
  467. }
  468. public float VolumeBeforeMute
  469. {
  470. get => _volumeBeforeMute;
  471. set
  472. {
  473. _volumeBeforeMute = value;
  474. OnPropertyChanged();
  475. }
  476. }
  477. public bool ShowStatusSeparator
  478. {
  479. get => _showStatusSeparator;
  480. set
  481. {
  482. _showStatusSeparator = value;
  483. OnPropertyChanged();
  484. }
  485. }
  486. public bool ShowMenuAndStatusBar
  487. {
  488. get => _showMenuAndStatusBar;
  489. set
  490. {
  491. _showMenuAndStatusBar = value;
  492. OnPropertyChanged();
  493. }
  494. }
  495. public bool IsLoadingIndeterminate
  496. {
  497. get => _isLoadingIndeterminate;
  498. set
  499. {
  500. _isLoadingIndeterminate = value;
  501. OnPropertyChanged();
  502. }
  503. }
  504. public bool IsActive
  505. {
  506. get => _isActive;
  507. set
  508. {
  509. _isActive = value;
  510. OnPropertyChanged();
  511. }
  512. }
  513. public bool ShowContent
  514. {
  515. get => _showContent;
  516. set
  517. {
  518. _showContent = value;
  519. OnPropertyChanged();
  520. }
  521. }
  522. public bool IsAppletMenuActive
  523. {
  524. get => _isAppletMenuActive && EnableNonGameRunningControls;
  525. set
  526. {
  527. _isAppletMenuActive = value;
  528. OnPropertyChanged();
  529. }
  530. }
  531. public WindowState WindowState
  532. {
  533. get => _windowState;
  534. internal set
  535. {
  536. _windowState = value;
  537. OnPropertyChanged();
  538. }
  539. }
  540. public double WindowWidth
  541. {
  542. get => _windowWidth;
  543. set
  544. {
  545. _windowWidth = value;
  546. OnPropertyChanged();
  547. }
  548. }
  549. public double WindowHeight
  550. {
  551. get => _windowHeight;
  552. set
  553. {
  554. _windowHeight = value;
  555. OnPropertyChanged();
  556. }
  557. }
  558. public bool IsGrid => Glyph == Glyph.Grid;
  559. public bool IsList => Glyph == Glyph.List;
  560. internal void Sort(bool isAscending)
  561. {
  562. IsAscending = isAscending;
  563. RefreshView();
  564. }
  565. internal void Sort(ApplicationSort sort)
  566. {
  567. SortMode = sort;
  568. RefreshView();
  569. }
  570. public bool StartGamesInFullscreen
  571. {
  572. get => ConfigurationState.Instance.Ui.StartFullscreen;
  573. set
  574. {
  575. ConfigurationState.Instance.Ui.StartFullscreen.Value = value;
  576. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  577. OnPropertyChanged();
  578. }
  579. }
  580. public bool ShowConsole
  581. {
  582. get => ConfigurationState.Instance.Ui.ShowConsole;
  583. set
  584. {
  585. ConfigurationState.Instance.Ui.ShowConsole.Value = value;
  586. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  587. OnPropertyChanged();
  588. }
  589. }
  590. public string Title
  591. {
  592. get => _title;
  593. set
  594. {
  595. _title = value;
  596. OnPropertyChanged();
  597. }
  598. }
  599. public bool ShowConsoleVisible
  600. {
  601. get => ConsoleHelper.SetConsoleWindowStateSupported;
  602. }
  603. public bool ManageFileTypesVisible
  604. {
  605. get => FileAssociationHelper.IsTypeAssociationSupported;
  606. }
  607. public ObservableCollection<ApplicationData> Applications
  608. {
  609. get => _applications;
  610. set
  611. {
  612. _applications = value;
  613. OnPropertyChanged();
  614. }
  615. }
  616. public Glyph Glyph
  617. {
  618. get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value;
  619. set
  620. {
  621. ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value;
  622. OnPropertyChanged();
  623. OnPropertyChanged(nameof(IsGrid));
  624. OnPropertyChanged(nameof(IsList));
  625. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  626. }
  627. }
  628. public bool ShowNames
  629. {
  630. get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set
  631. {
  632. ConfigurationState.Instance.Ui.ShowNames.Value = value;
  633. OnPropertyChanged();
  634. OnPropertyChanged(nameof(GridSizeScale));
  635. OnPropertyChanged(nameof(GridItemSelectorSize));
  636. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  637. }
  638. }
  639. internal ApplicationSort SortMode
  640. {
  641. get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value;
  642. private set
  643. {
  644. ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value;
  645. OnPropertyChanged();
  646. OnPropertyChanged(nameof(SortName));
  647. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  648. }
  649. }
  650. public int ListItemSelectorSize
  651. {
  652. get
  653. {
  654. return ConfigurationState.Instance.Ui.GridSize.Value switch
  655. {
  656. 1 => 78,
  657. 2 => 100,
  658. 3 => 120,
  659. 4 => 140,
  660. _ => 16,
  661. };
  662. }
  663. }
  664. public int GridItemSelectorSize
  665. {
  666. get
  667. {
  668. return ConfigurationState.Instance.Ui.GridSize.Value switch
  669. {
  670. 1 => 120,
  671. 2 => ShowNames ? 210 : 150,
  672. 3 => ShowNames ? 240 : 180,
  673. 4 => ShowNames ? 280 : 220,
  674. _ => 16,
  675. };
  676. }
  677. }
  678. public int GridSizeScale
  679. {
  680. get => ConfigurationState.Instance.Ui.GridSize;
  681. set
  682. {
  683. ConfigurationState.Instance.Ui.GridSize.Value = value;
  684. if (value < 2)
  685. {
  686. ShowNames = false;
  687. }
  688. OnPropertyChanged();
  689. OnPropertyChanged(nameof(IsGridSmall));
  690. OnPropertyChanged(nameof(IsGridMedium));
  691. OnPropertyChanged(nameof(IsGridLarge));
  692. OnPropertyChanged(nameof(IsGridHuge));
  693. OnPropertyChanged(nameof(ListItemSelectorSize));
  694. OnPropertyChanged(nameof(GridItemSelectorSize));
  695. OnPropertyChanged(nameof(ShowNames));
  696. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  697. }
  698. }
  699. public string SortName
  700. {
  701. get
  702. {
  703. return SortMode switch
  704. {
  705. ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication],
  706. ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper],
  707. ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed],
  708. ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed],
  709. ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension],
  710. ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize],
  711. ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath],
  712. ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite],
  713. _ => string.Empty,
  714. };
  715. }
  716. }
  717. public bool IsAscending
  718. {
  719. get => ConfigurationState.Instance.Ui.IsAscendingOrder;
  720. private set
  721. {
  722. ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value;
  723. OnPropertyChanged();
  724. OnPropertyChanged(nameof(SortMode));
  725. OnPropertyChanged(nameof(SortName));
  726. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  727. }
  728. }
  729. public KeyGesture ShowUiKey
  730. {
  731. get => KeyGesture.Parse(_showUiKey);
  732. set
  733. {
  734. _showUiKey = value.ToString();
  735. OnPropertyChanged();
  736. }
  737. }
  738. public KeyGesture ScreenshotKey
  739. {
  740. get => KeyGesture.Parse(_screenshotKey);
  741. set
  742. {
  743. _screenshotKey = value.ToString();
  744. OnPropertyChanged();
  745. }
  746. }
  747. public KeyGesture PauseKey
  748. {
  749. get => KeyGesture.Parse(_pauseKey); set
  750. {
  751. _pauseKey = value.ToString();
  752. OnPropertyChanged();
  753. }
  754. }
  755. public ContentManager ContentManager { get; private set; }
  756. public IStorageProvider StorageProvider { get; private set; }
  757. public ApplicationLibrary ApplicationLibrary { get; private set; }
  758. public VirtualFileSystem VirtualFileSystem { get; private set; }
  759. public AccountManager AccountManager { get; private set; }
  760. public InputManager InputManager { get; private set; }
  761. public UserChannelPersistence UserChannelPersistence { get; private set; }
  762. public Action<bool> ShowLoading { get; private set; }
  763. public Action<bool> SwitchToGameControl { get; private set; }
  764. public Action<Control> SetMainContent { get; private set; }
  765. public TopLevel TopLevel { get; private set; }
  766. public RendererHost RendererHostControl { get; private set; }
  767. public bool IsClosing { get; set; }
  768. public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
  769. public IHostUiHandler UiHandler { get; internal set; }
  770. public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
  771. public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
  772. public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
  773. public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
  774. public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
  775. public bool IsSortedByType => SortMode == ApplicationSort.FileType;
  776. public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
  777. public bool IsSortedByPath => SortMode == ApplicationSort.Path;
  778. public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
  779. public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
  780. public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
  781. public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
  782. #endregion
  783. #region PrivateMethods
  784. private IComparer<ApplicationData> GetComparer()
  785. {
  786. return SortMode switch
  787. {
  788. #pragma warning disable IDE0055 // Disable formatting
  789. ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
  790. : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
  791. ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
  792. : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
  793. ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
  794. ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
  795. ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
  796. : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
  797. ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize)
  798. : SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
  799. ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
  800. : SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
  801. ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
  802. : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
  803. _ => null,
  804. #pragma warning restore IDE0055
  805. };
  806. }
  807. public void RefreshView()
  808. {
  809. RefreshGrid();
  810. }
  811. private void RefreshGrid()
  812. {
  813. Applications.ToObservableChangeSet()
  814. .Filter(Filter)
  815. .Sort(GetComparer())
  816. .Bind(out _appsObservableList).AsObservableList();
  817. OnPropertyChanged(nameof(AppsObservableList));
  818. }
  819. private bool Filter(object arg)
  820. {
  821. if (arg is ApplicationData app)
  822. {
  823. return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower());
  824. }
  825. return false;
  826. }
  827. private async Task HandleFirmwareInstallation(string filename)
  828. {
  829. try
  830. {
  831. SystemVersion firmwareVersion = ContentManager.VerifyFirmwarePackage(filename);
  832. if (firmwareVersion == null)
  833. {
  834. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename));
  835. return;
  836. }
  837. string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString);
  838. string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString);
  839. SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion();
  840. if (currentVersion != null)
  841. {
  842. dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString);
  843. }
  844. dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage];
  845. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
  846. dialogTitle,
  847. dialogMessage,
  848. LocaleManager.Instance[LocaleKeys.InputDialogYes],
  849. LocaleManager.Instance[LocaleKeys.InputDialogNo],
  850. LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
  851. UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
  852. if (result == UserResult.Yes)
  853. {
  854. Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
  855. Thread thread = new(() =>
  856. {
  857. Dispatcher.UIThread.InvokeAsync(delegate
  858. {
  859. waitingDialog.Show();
  860. });
  861. try
  862. {
  863. ContentManager.InstallFirmware(filename);
  864. Dispatcher.UIThread.InvokeAsync(async delegate
  865. {
  866. waitingDialog.Close();
  867. string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString);
  868. await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
  869. Logger.Info?.Print(LogClass.Application, message);
  870. // Purge Applet Cache.
  871. DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
  872. if (miiEditorCacheFolder.Exists)
  873. {
  874. miiEditorCacheFolder.Delete(true);
  875. }
  876. });
  877. }
  878. catch (Exception ex)
  879. {
  880. Dispatcher.UIThread.InvokeAsync(async () =>
  881. {
  882. waitingDialog.Close();
  883. await ContentDialogHelper.CreateErrorDialog(ex.Message);
  884. });
  885. }
  886. finally
  887. {
  888. RefreshFirmwareStatus();
  889. }
  890. })
  891. {
  892. Name = "GUI.FirmwareInstallerThread",
  893. };
  894. thread.Start();
  895. }
  896. }
  897. catch (MissingKeyException ex)
  898. {
  899. if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  900. {
  901. Logger.Error?.Print(LogClass.Application, ex.ToString());
  902. await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
  903. }
  904. }
  905. catch (Exception ex)
  906. {
  907. await ContentDialogHelper.CreateErrorDialog(ex.Message);
  908. }
  909. }
  910. private void ProgressHandler<T>(T state, int current, int total) where T : Enum
  911. {
  912. Dispatcher.UIThread.Post((() =>
  913. {
  914. ProgressMaximum = total;
  915. ProgressValue = current;
  916. switch (state)
  917. {
  918. case LoadState ptcState:
  919. CacheLoadStatus = $"{current} / {total}";
  920. switch (ptcState)
  921. {
  922. case LoadState.Unloaded:
  923. case LoadState.Loading:
  924. LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingPPTC];
  925. IsLoadingIndeterminate = false;
  926. break;
  927. case LoadState.Loaded:
  928. LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
  929. IsLoadingIndeterminate = true;
  930. CacheLoadStatus = "";
  931. break;
  932. }
  933. break;
  934. case ShaderCacheLoadingState shaderCacheState:
  935. CacheLoadStatus = $"{current} / {total}";
  936. switch (shaderCacheState)
  937. {
  938. case ShaderCacheLoadingState.Start:
  939. case ShaderCacheLoadingState.Loading:
  940. LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders];
  941. IsLoadingIndeterminate = false;
  942. break;
  943. case ShaderCacheLoadingState.Packaging:
  944. LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders];
  945. IsLoadingIndeterminate = false;
  946. break;
  947. case ShaderCacheLoadingState.Loaded:
  948. LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
  949. IsLoadingIndeterminate = true;
  950. CacheLoadStatus = "";
  951. break;
  952. }
  953. break;
  954. default:
  955. throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
  956. }
  957. }));
  958. }
  959. private void PrepareLoadScreen()
  960. {
  961. using MemoryStream stream = new(SelectedIcon);
  962. using var gameIconBmp = Image.Load<Bgra32>(stream);
  963. var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
  964. const float ColorMultiple = 0.5f;
  965. Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
  966. Color progressBgColor = Color.FromRgb(
  967. (byte)(dominantColor.R * ColorMultiple),
  968. (byte)(dominantColor.G * ColorMultiple),
  969. (byte)(dominantColor.B * ColorMultiple));
  970. ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
  971. ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
  972. }
  973. private void InitializeGame()
  974. {
  975. RendererHostControl.WindowCreated += RendererHost_Created;
  976. AppHost.StatusUpdatedEvent += Update_StatusBar;
  977. AppHost.AppExit += AppHost_AppExit;
  978. _rendererWaitEvent.WaitOne();
  979. AppHost?.Start();
  980. AppHost?.DisposeContext();
  981. }
  982. private async Task HandleRelaunch()
  983. {
  984. if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart)
  985. {
  986. UserChannelPersistence.ShouldRestart = false;
  987. await LoadApplication(_currentEmulatedGamePath);
  988. }
  989. else
  990. {
  991. // Otherwise, clear state.
  992. UserChannelPersistence = new UserChannelPersistence();
  993. _currentEmulatedGamePath = null;
  994. }
  995. }
  996. private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
  997. {
  998. if (ShowMenuAndStatusBar && !ShowLoadProgress)
  999. {
  1000. Dispatcher.UIThread.InvokeAsync(() =>
  1001. {
  1002. Application.Current.Styles.TryGetResource(args.VSyncEnabled
  1003. ? "VsyncEnabled"
  1004. : "VsyncDisabled",
  1005. Application.Current.ActualThemeVariant,
  1006. out object color);
  1007. if (color is not null)
  1008. {
  1009. VsyncColor = new SolidColorBrush((Color)color);
  1010. }
  1011. DockedStatusText = args.DockedMode;
  1012. AspectRatioStatusText = args.AspectRatio;
  1013. GameStatusText = args.GameStatus;
  1014. VolumeStatusText = args.VolumeStatus;
  1015. FifoStatusText = args.FifoStatus;
  1016. GpuNameText = args.GpuName;
  1017. BackendText = args.GpuBackend;
  1018. ShowStatusSeparator = true;
  1019. });
  1020. }
  1021. }
  1022. private void RendererHost_Created(object sender, EventArgs e)
  1023. {
  1024. ShowLoading(false);
  1025. _rendererWaitEvent.Set();
  1026. }
  1027. #endregion
  1028. #region PublicMethods
  1029. public void SetUiProgressHandlers(Switch emulationContext)
  1030. {
  1031. if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
  1032. {
  1033. emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
  1034. emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
  1035. }
  1036. emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
  1037. emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
  1038. }
  1039. public void LoadConfigurableHotKeys()
  1040. {
  1041. if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
  1042. {
  1043. ShowUiKey = new KeyGesture(showUiKey);
  1044. }
  1045. if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
  1046. {
  1047. ScreenshotKey = new KeyGesture(screenshotKey);
  1048. }
  1049. if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
  1050. {
  1051. PauseKey = new KeyGesture(pauseKey);
  1052. }
  1053. }
  1054. public void TakeScreenshot()
  1055. {
  1056. AppHost.ScreenshotRequested = true;
  1057. }
  1058. public void HideUi()
  1059. {
  1060. ShowMenuAndStatusBar = false;
  1061. }
  1062. public void ToggleStartGamesInFullscreen()
  1063. {
  1064. StartGamesInFullscreen = !StartGamesInFullscreen;
  1065. }
  1066. public void ToggleShowConsole()
  1067. {
  1068. ShowConsole = !ShowConsole;
  1069. }
  1070. public void SetListMode()
  1071. {
  1072. Glyph = Glyph.List;
  1073. }
  1074. public void SetGridMode()
  1075. {
  1076. Glyph = Glyph.Grid;
  1077. }
  1078. public void SetAspectRatio(AspectRatio aspectRatio)
  1079. {
  1080. ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
  1081. }
  1082. public async Task InstallFirmwareFromFile()
  1083. {
  1084. var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
  1085. {
  1086. AllowMultiple = false,
  1087. FileTypeFilter = new List<FilePickerFileType>
  1088. {
  1089. new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
  1090. {
  1091. Patterns = new[] { "*.xci", "*.zip" },
  1092. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" },
  1093. MimeTypes = new[] { "application/x-nx-xci", "application/zip" },
  1094. },
  1095. new("XCI")
  1096. {
  1097. Patterns = new[] { "*.xci" },
  1098. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
  1099. MimeTypes = new[] { "application/x-nx-xci" },
  1100. },
  1101. new("ZIP")
  1102. {
  1103. Patterns = new[] { "*.zip" },
  1104. AppleUniformTypeIdentifiers = new[] { "public.zip-archive" },
  1105. MimeTypes = new[] { "application/zip" },
  1106. },
  1107. },
  1108. });
  1109. if (result.Count > 0)
  1110. {
  1111. await HandleFirmwareInstallation(result[0].Path.LocalPath);
  1112. }
  1113. }
  1114. public async Task InstallFirmwareFromFolder()
  1115. {
  1116. var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
  1117. {
  1118. AllowMultiple = false,
  1119. });
  1120. if (result.Count > 0)
  1121. {
  1122. await HandleFirmwareInstallation(result[0].Path.LocalPath);
  1123. }
  1124. }
  1125. public void OpenRyujinxFolder()
  1126. {
  1127. OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
  1128. }
  1129. public void OpenLogsFolder()
  1130. {
  1131. string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
  1132. if (LoggerModule.LogDirectoryPath != null)
  1133. {
  1134. logPath = LoggerModule.LogDirectoryPath;
  1135. }
  1136. new DirectoryInfo(logPath).Create();
  1137. OpenHelper.OpenFolder(logPath);
  1138. }
  1139. public void ToggleDockMode()
  1140. {
  1141. if (IsGameRunning)
  1142. {
  1143. ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
  1144. }
  1145. }
  1146. public async Task ExitCurrentState()
  1147. {
  1148. if (WindowState == WindowState.FullScreen)
  1149. {
  1150. ToggleFullscreen();
  1151. }
  1152. else if (IsGameRunning)
  1153. {
  1154. await Task.Delay(100);
  1155. AppHost?.ShowExitPrompt();
  1156. }
  1157. }
  1158. public static void ChangeLanguage(object languageCode)
  1159. {
  1160. LocaleManager.Instance.LoadLanguage((string)languageCode);
  1161. if (Program.PreviewerDetached)
  1162. {
  1163. ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode;
  1164. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  1165. }
  1166. }
  1167. public async Task ManageProfiles()
  1168. {
  1169. await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
  1170. }
  1171. public void SimulateWakeUpMessage()
  1172. {
  1173. AppHost.Device.System.SimulateWakeUpMessage();
  1174. }
  1175. public async Task OpenFile()
  1176. {
  1177. var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
  1178. {
  1179. Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
  1180. AllowMultiple = false,
  1181. FileTypeFilter = new List<FilePickerFileType>
  1182. {
  1183. new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
  1184. {
  1185. Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" },
  1186. AppleUniformTypeIdentifiers = new[]
  1187. {
  1188. "com.ryujinx.nsp",
  1189. "com.ryujinx.xci",
  1190. "com.ryujinx.nca",
  1191. "com.ryujinx.nro",
  1192. "com.ryujinx.nso",
  1193. },
  1194. MimeTypes = new[]
  1195. {
  1196. "application/x-nx-nsp",
  1197. "application/x-nx-xci",
  1198. "application/x-nx-nca",
  1199. "application/x-nx-nro",
  1200. "application/x-nx-nso",
  1201. },
  1202. },
  1203. new("NSP")
  1204. {
  1205. Patterns = new[] { "*.nsp" },
  1206. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
  1207. MimeTypes = new[] { "application/x-nx-nsp" },
  1208. },
  1209. new("XCI")
  1210. {
  1211. Patterns = new[] { "*.xci" },
  1212. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
  1213. MimeTypes = new[] { "application/x-nx-xci" },
  1214. },
  1215. new("NCA")
  1216. {
  1217. Patterns = new[] { "*.nca" },
  1218. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" },
  1219. MimeTypes = new[] { "application/x-nx-nca" },
  1220. },
  1221. new("NRO")
  1222. {
  1223. Patterns = new[] { "*.nro" },
  1224. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" },
  1225. MimeTypes = new[] { "application/x-nx-nro" },
  1226. },
  1227. new("NSO")
  1228. {
  1229. Patterns = new[] { "*.nso" },
  1230. AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" },
  1231. MimeTypes = new[] { "application/x-nx-nso" },
  1232. },
  1233. },
  1234. });
  1235. if (result.Count > 0)
  1236. {
  1237. await LoadApplication(result[0].Path.LocalPath);
  1238. }
  1239. }
  1240. public async Task OpenFolder()
  1241. {
  1242. var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
  1243. {
  1244. Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
  1245. AllowMultiple = false,
  1246. });
  1247. if (result.Count > 0)
  1248. {
  1249. await LoadApplication(result[0].Path.LocalPath);
  1250. }
  1251. }
  1252. public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
  1253. {
  1254. if (AppHost != null)
  1255. {
  1256. await ContentDialogHelper.CreateInfoDialog(
  1257. LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage],
  1258. LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage],
  1259. LocaleManager.Instance[LocaleKeys.InputDialogOk],
  1260. "",
  1261. LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
  1262. return;
  1263. }
  1264. #if RELEASE
  1265. await PerformanceCheck();
  1266. #endif
  1267. Logger.RestartTime();
  1268. SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
  1269. PrepareLoadScreen();
  1270. RendererHostControl = new RendererHost();
  1271. AppHost = new AppHost(
  1272. RendererHostControl,
  1273. InputManager,
  1274. path,
  1275. VirtualFileSystem,
  1276. ContentManager,
  1277. AccountManager,
  1278. UserChannelPersistence,
  1279. this,
  1280. TopLevel);
  1281. if (!await AppHost.LoadGuestApplication())
  1282. {
  1283. AppHost.DisposeContext();
  1284. AppHost = null;
  1285. return;
  1286. }
  1287. CanUpdate = false;
  1288. LoadHeading = TitleName = titleName;
  1289. if (string.IsNullOrWhiteSpace(titleName))
  1290. {
  1291. LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
  1292. TitleName = AppHost.Device.Processes.ActiveApplication.Name;
  1293. }
  1294. SwitchToRenderer(startFullscreen);
  1295. _currentEmulatedGamePath = path;
  1296. Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
  1297. gameThread.Start();
  1298. }
  1299. public void SwitchToRenderer(bool startFullscreen)
  1300. {
  1301. Dispatcher.UIThread.Post(() =>
  1302. {
  1303. SwitchToGameControl(startFullscreen);
  1304. SetMainContent(RendererHostControl);
  1305. RendererHostControl.Focus();
  1306. });
  1307. }
  1308. public static void UpdateGameMetadata(string titleId)
  1309. {
  1310. ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
  1311. {
  1312. appMetadata.UpdatePostGame();
  1313. });
  1314. }
  1315. public void RefreshFirmwareStatus()
  1316. {
  1317. SystemVersion version = null;
  1318. try
  1319. {
  1320. version = ContentManager.GetCurrentFirmwareVersion();
  1321. }
  1322. catch (Exception) { }
  1323. bool hasApplet = false;
  1324. if (version != null)
  1325. {
  1326. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString);
  1327. hasApplet = version.Major > 3;
  1328. }
  1329. else
  1330. {
  1331. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
  1332. }
  1333. IsAppletMenuActive = hasApplet;
  1334. }
  1335. public void AppHost_AppExit(object sender, EventArgs e)
  1336. {
  1337. if (IsClosing)
  1338. {
  1339. return;
  1340. }
  1341. IsGameRunning = false;
  1342. Dispatcher.UIThread.InvokeAsync(async () =>
  1343. {
  1344. ShowMenuAndStatusBar = true;
  1345. ShowContent = true;
  1346. ShowLoadProgress = false;
  1347. IsLoadingIndeterminate = false;
  1348. CanUpdate = true;
  1349. Cursor = Cursor.Default;
  1350. SetMainContent(null);
  1351. AppHost = null;
  1352. await HandleRelaunch();
  1353. });
  1354. RendererHostControl.WindowCreated -= RendererHost_Created;
  1355. RendererHostControl = null;
  1356. SelectedIcon = null;
  1357. Dispatcher.UIThread.InvokeAsync(() =>
  1358. {
  1359. Title = $"Ryujinx {Program.Version}";
  1360. });
  1361. }
  1362. public void ToggleFullscreen()
  1363. {
  1364. if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs)
  1365. {
  1366. return;
  1367. }
  1368. LastFullscreenToggle = Environment.TickCount64;
  1369. if (WindowState == WindowState.FullScreen)
  1370. {
  1371. WindowState = WindowState.Normal;
  1372. if (IsGameRunning)
  1373. {
  1374. ShowMenuAndStatusBar = true;
  1375. }
  1376. }
  1377. else
  1378. {
  1379. WindowState = WindowState.FullScreen;
  1380. if (IsGameRunning)
  1381. {
  1382. ShowMenuAndStatusBar = false;
  1383. }
  1384. }
  1385. IsFullScreen = WindowState == WindowState.FullScreen;
  1386. }
  1387. public static void SaveConfig()
  1388. {
  1389. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  1390. }
  1391. public static async Task PerformanceCheck()
  1392. {
  1393. if (ConfigurationState.Instance.Logger.EnableTrace.Value)
  1394. {
  1395. string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage];
  1396. string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage];
  1397. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
  1398. mainMessage,
  1399. secondaryMessage,
  1400. LocaleManager.Instance[LocaleKeys.InputDialogYes],
  1401. LocaleManager.Instance[LocaleKeys.InputDialogNo],
  1402. LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
  1403. if (result == UserResult.Yes)
  1404. {
  1405. ConfigurationState.Instance.Logger.EnableTrace.Value = false;
  1406. SaveConfig();
  1407. }
  1408. }
  1409. if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
  1410. {
  1411. string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage];
  1412. string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage];
  1413. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
  1414. mainMessage,
  1415. secondaryMessage,
  1416. LocaleManager.Instance[LocaleKeys.InputDialogYes],
  1417. LocaleManager.Instance[LocaleKeys.InputDialogNo],
  1418. LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
  1419. if (result == UserResult.Yes)
  1420. {
  1421. ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
  1422. SaveConfig();
  1423. }
  1424. }
  1425. }
  1426. #endregion
  1427. }
  1428. }