MainWindowViewModel.cs 54 KB

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