MainWindowViewModel.cs 54 KB

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