MainWindowViewModel.cs 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551
  1. using ARMeilleure.Translation.PTC;
  2. using Avalonia;
  3. using Avalonia.Controls;
  4. using Avalonia.Input;
  5. using Avalonia.Media;
  6. using Avalonia.Threading;
  7. using DynamicData;
  8. using DynamicData.Binding;
  9. using LibHac.Fs;
  10. using LibHac.FsSystem;
  11. using LibHac.Ncm;
  12. using Ryujinx.Ava.Common;
  13. using Ryujinx.Ava.Common.Locale;
  14. using Ryujinx.Ava.Input;
  15. using Ryujinx.Ava.Ui.Controls;
  16. using Ryujinx.Ava.Ui.Windows;
  17. using Ryujinx.Common;
  18. using Ryujinx.Common.Configuration;
  19. using Ryujinx.Common.Logging;
  20. using Ryujinx.HLE;
  21. using Ryujinx.HLE.FileSystem;
  22. using Ryujinx.Modules;
  23. using Ryujinx.Ui.App.Common;
  24. using Ryujinx.Ui.Common;
  25. using Ryujinx.Ui.Common.Configuration;
  26. using Ryujinx.Ui.Common.Helper;
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Collections.ObjectModel;
  30. using System.Globalization;
  31. using System.IO;
  32. using System.Threading;
  33. using System.Threading.Tasks;
  34. using Path = System.IO.Path;
  35. using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
  36. namespace Ryujinx.Ava.Ui.ViewModels
  37. {
  38. internal class MainWindowViewModel : BaseModel
  39. {
  40. private const int HotKeyPressDelayMs = 500;
  41. private readonly MainWindow _owner;
  42. private ObservableCollection<ApplicationData> _applications;
  43. private string _aspectStatusText;
  44. private string _loadHeading;
  45. private string _cacheLoadStatus;
  46. private string _searchText;
  47. private string _dockedStatusText;
  48. private string _fifoStatusText;
  49. private string _gameStatusText;
  50. private string _gpuStatusText;
  51. private bool _isAmiiboRequested;
  52. private bool _isGameRunning;
  53. private bool _isLoading;
  54. private int _progressMaximum;
  55. private int _progressValue;
  56. private long _lastFullscreenToggle = Environment.TickCount64;
  57. private bool _showLoadProgress;
  58. private bool _showMenuAndStatusBar = true;
  59. private bool _showStatusSeparator;
  60. private Brush _progressBarForegroundColor;
  61. private Brush _progressBarBackgroundColor;
  62. private Brush _vsyncColor;
  63. private byte[] _selectedIcon;
  64. private bool _isAppletMenuActive;
  65. private int _statusBarProgressMaximum;
  66. private int _statusBarProgressValue;
  67. private bool _isPaused;
  68. private bool _showContent = true;
  69. private bool _isLoadingIndeterminate = true;
  70. private bool _showAll;
  71. private string _lastScannedAmiiboId;
  72. private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
  73. public string TitleName { get; internal set; }
  74. public MainWindowViewModel(MainWindow owner) : this()
  75. {
  76. _owner = owner;
  77. }
  78. public MainWindowViewModel()
  79. {
  80. Applications = new ObservableCollection<ApplicationData>();
  81. Applications.ToObservableChangeSet()
  82. .Filter(Filter)
  83. .Sort(GetComparer())
  84. .Bind(out _appsObservableList).AsObservableList();
  85. if (Program.PreviewerDetached)
  86. {
  87. LoadConfigurableHotKeys();
  88. Volume = ConfigurationState.Instance.System.AudioVolume;
  89. }
  90. }
  91. public void Initialize()
  92. {
  93. _owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
  94. _owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
  95. Ptc.PtcStateChanged -= ProgressHandler;
  96. Ptc.PtcStateChanged += ProgressHandler;
  97. }
  98. public string SearchText
  99. {
  100. get => _searchText;
  101. set
  102. {
  103. _searchText = value;
  104. RefreshView();
  105. }
  106. }
  107. public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
  108. {
  109. get => _appsObservableList;
  110. set
  111. {
  112. _appsObservableList = value;
  113. OnPropertyChanged();
  114. }
  115. }
  116. public bool IsPaused
  117. {
  118. get => _isPaused;
  119. set
  120. {
  121. _isPaused = value;
  122. OnPropertyChanged();
  123. }
  124. }
  125. public bool EnableNonGameRunningControls => !IsGameRunning;
  126. public bool ShowFirmwareStatus => !ShowLoadProgress;
  127. public bool IsGameRunning
  128. {
  129. get => _isGameRunning;
  130. set
  131. {
  132. _isGameRunning = value;
  133. if (!value)
  134. {
  135. ShowMenuAndStatusBar = false;
  136. }
  137. OnPropertyChanged();
  138. OnPropertyChanged(nameof(EnableNonGameRunningControls));
  139. OnPropertyChanged(nameof(ShowFirmwareStatus));
  140. }
  141. }
  142. public bool IsAmiiboRequested
  143. {
  144. get => _isAmiiboRequested && _isGameRunning;
  145. set
  146. {
  147. _isAmiiboRequested = value;
  148. OnPropertyChanged();
  149. }
  150. }
  151. public bool ShowLoadProgress
  152. {
  153. get => _showLoadProgress;
  154. set
  155. {
  156. _showLoadProgress = value;
  157. OnPropertyChanged();
  158. OnPropertyChanged(nameof(ShowFirmwareStatus));
  159. }
  160. }
  161. public string GameStatusText
  162. {
  163. get => _gameStatusText;
  164. set
  165. {
  166. _gameStatusText = value;
  167. OnPropertyChanged();
  168. }
  169. }
  170. private string _showUikey = "F4";
  171. private string _pauseKey = "F5";
  172. private string _screenshotkey = "F8";
  173. private float _volume;
  174. private string _backendText;
  175. public ApplicationData SelectedApplication
  176. {
  177. get
  178. {
  179. switch (Glyph)
  180. {
  181. case Glyph.List:
  182. return _owner.GameList.SelectedApplication;
  183. case Glyph.Grid:
  184. return _owner.GameGrid.SelectedApplication;
  185. default:
  186. return null;
  187. }
  188. }
  189. }
  190. public string LoadHeading
  191. {
  192. get => _loadHeading;
  193. set
  194. {
  195. _loadHeading = value;
  196. OnPropertyChanged();
  197. }
  198. }
  199. public string CacheLoadStatus
  200. {
  201. get => _cacheLoadStatus;
  202. set
  203. {
  204. _cacheLoadStatus = value;
  205. OnPropertyChanged();
  206. }
  207. }
  208. public Brush ProgressBarBackgroundColor
  209. {
  210. get => _progressBarBackgroundColor;
  211. set
  212. {
  213. _progressBarBackgroundColor = value;
  214. OnPropertyChanged();
  215. }
  216. }
  217. public Brush ProgressBarForegroundColor
  218. {
  219. get => _progressBarForegroundColor;
  220. set
  221. {
  222. _progressBarForegroundColor = value;
  223. OnPropertyChanged();
  224. }
  225. }
  226. public Brush VsyncColor
  227. {
  228. get => _vsyncColor;
  229. set
  230. {
  231. _vsyncColor = value;
  232. OnPropertyChanged();
  233. }
  234. }
  235. public byte[] SelectedIcon
  236. {
  237. get => _selectedIcon;
  238. set
  239. {
  240. _selectedIcon = value;
  241. OnPropertyChanged();
  242. }
  243. }
  244. public int ProgressMaximum
  245. {
  246. get => _progressMaximum;
  247. set
  248. {
  249. _progressMaximum = value;
  250. OnPropertyChanged();
  251. }
  252. }
  253. public int ProgressValue
  254. {
  255. get => _progressValue;
  256. set
  257. {
  258. _progressValue = value;
  259. OnPropertyChanged();
  260. }
  261. }
  262. public int StatusBarProgressMaximum
  263. {
  264. get => _statusBarProgressMaximum;
  265. set
  266. {
  267. _statusBarProgressMaximum = value;
  268. OnPropertyChanged();
  269. }
  270. }
  271. public int StatusBarProgressValue
  272. {
  273. get => _statusBarProgressValue;
  274. set
  275. {
  276. _statusBarProgressValue = value;
  277. OnPropertyChanged();
  278. }
  279. }
  280. public string FifoStatusText
  281. {
  282. get => _fifoStatusText;
  283. set
  284. {
  285. _fifoStatusText = value;
  286. OnPropertyChanged();
  287. }
  288. }
  289. public string GpuNameText
  290. {
  291. get => _gpuStatusText;
  292. set
  293. {
  294. _gpuStatusText = value;
  295. OnPropertyChanged();
  296. }
  297. }
  298. public string BackendText
  299. {
  300. get => _backendText;
  301. set
  302. {
  303. _backendText = value;
  304. OnPropertyChanged();
  305. }
  306. }
  307. public string DockedStatusText
  308. {
  309. get => _dockedStatusText;
  310. set
  311. {
  312. _dockedStatusText = value;
  313. OnPropertyChanged();
  314. }
  315. }
  316. public string AspectRatioStatusText
  317. {
  318. get => _aspectStatusText;
  319. set
  320. {
  321. _aspectStatusText = value;
  322. OnPropertyChanged();
  323. }
  324. }
  325. public string VolumeStatusText
  326. {
  327. get
  328. {
  329. string icon = Volume == 0 ? "🔇" : "🔊";
  330. return $"{icon} {(int)(Volume * 100)}%";
  331. }
  332. }
  333. public bool VolumeMuted => _volume == 0;
  334. public float Volume
  335. {
  336. get => _volume;
  337. set
  338. {
  339. _volume = value;
  340. if (_isGameRunning)
  341. {
  342. _owner.AppHost.Device.SetVolume(_volume);
  343. }
  344. OnPropertyChanged(nameof(VolumeStatusText));
  345. OnPropertyChanged(nameof(VolumeMuted));
  346. OnPropertyChanged();
  347. }
  348. }
  349. public bool ShowStatusSeparator
  350. {
  351. get => _showStatusSeparator;
  352. set
  353. {
  354. _showStatusSeparator = value;
  355. OnPropertyChanged();
  356. }
  357. }
  358. public Thickness GridItemPadding => ShowNames ? new Thickness() : new Thickness(5);
  359. public bool ShowMenuAndStatusBar
  360. {
  361. get => _showMenuAndStatusBar;
  362. set
  363. {
  364. _showMenuAndStatusBar = value;
  365. OnPropertyChanged();
  366. }
  367. }
  368. public bool IsLoadingIndeterminate
  369. {
  370. get => _isLoadingIndeterminate;
  371. set
  372. {
  373. _isLoadingIndeterminate = value;
  374. OnPropertyChanged();
  375. }
  376. }
  377. public bool ShowContent
  378. {
  379. get => _showContent;
  380. set
  381. {
  382. _showContent = value;
  383. OnPropertyChanged();
  384. }
  385. }
  386. public bool IsAppletMenuActive
  387. {
  388. get => _isAppletMenuActive && EnableNonGameRunningControls;
  389. set
  390. {
  391. _isAppletMenuActive = value;
  392. OnPropertyChanged();
  393. }
  394. }
  395. public bool IsGrid => Glyph == Glyph.Grid;
  396. public bool IsList => Glyph == Glyph.List;
  397. internal void Sort(bool isAscending)
  398. {
  399. IsAscending = isAscending;
  400. RefreshView();
  401. }
  402. internal void Sort(ApplicationSort sort)
  403. {
  404. SortMode = sort;
  405. RefreshView();
  406. }
  407. private IComparer<ApplicationData> GetComparer()
  408. {
  409. switch (SortMode)
  410. {
  411. case ApplicationSort.LastPlayed:
  412. return new Models.Generic.LastPlayedSortComparer(IsAscending);
  413. case ApplicationSort.FileSize:
  414. return new Models.Generic.FileSizeSortComparer(IsAscending);
  415. case ApplicationSort.TotalTimePlayed:
  416. return new Models.Generic.TimePlayedSortComparer(IsAscending);
  417. case ApplicationSort.Title:
  418. return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName);
  419. case ApplicationSort.Favorite:
  420. return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite);
  421. case ApplicationSort.Developer:
  422. return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer);
  423. case ApplicationSort.FileType:
  424. return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension);
  425. case ApplicationSort.Path:
  426. return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
  427. default:
  428. return null;
  429. }
  430. }
  431. private void RefreshView()
  432. {
  433. RefreshGrid();
  434. }
  435. private void RefreshGrid()
  436. {
  437. Applications.ToObservableChangeSet()
  438. .Filter(Filter)
  439. .Sort(GetComparer())
  440. .Bind(out _appsObservableList).AsObservableList();
  441. OnPropertyChanged(nameof(AppsObservableList));
  442. }
  443. public bool StartGamesInFullscreen
  444. {
  445. get => ConfigurationState.Instance.Ui.StartFullscreen;
  446. set
  447. {
  448. ConfigurationState.Instance.Ui.StartFullscreen.Value = value;
  449. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  450. OnPropertyChanged();
  451. }
  452. }
  453. public bool ShowConsole
  454. {
  455. get => ConfigurationState.Instance.Ui.ShowConsole;
  456. set
  457. {
  458. ConfigurationState.Instance.Ui.ShowConsole.Value = value;
  459. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  460. OnPropertyChanged();
  461. }
  462. }
  463. public bool ShowConsoleVisible
  464. {
  465. get => ConsoleHelper.SetConsoleWindowStateSupported;
  466. }
  467. public ObservableCollection<ApplicationData> Applications
  468. {
  469. get => _applications;
  470. set
  471. {
  472. _applications = value;
  473. OnPropertyChanged();
  474. }
  475. }
  476. public Glyph Glyph
  477. {
  478. get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value;
  479. set
  480. {
  481. ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value;
  482. OnPropertyChanged();
  483. OnPropertyChanged(nameof(IsGrid));
  484. OnPropertyChanged(nameof(IsList));
  485. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  486. }
  487. }
  488. public bool ShowNames
  489. {
  490. get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set
  491. {
  492. ConfigurationState.Instance.Ui.ShowNames.Value = value;
  493. OnPropertyChanged();
  494. OnPropertyChanged(nameof(GridItemPadding));
  495. OnPropertyChanged(nameof(GridSizeScale));
  496. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  497. }
  498. }
  499. internal ApplicationSort SortMode
  500. {
  501. get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value;
  502. private set
  503. {
  504. ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value;
  505. OnPropertyChanged();
  506. OnPropertyChanged(nameof(SortName));
  507. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  508. }
  509. }
  510. public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
  511. public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
  512. public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
  513. public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
  514. public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
  515. public bool IsSortedByType => SortMode == ApplicationSort.FileType;
  516. public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
  517. public bool IsSortedByPath => SortMode == ApplicationSort.Path;
  518. public string SortName
  519. {
  520. get
  521. {
  522. switch (SortMode)
  523. {
  524. case ApplicationSort.Title:
  525. return LocaleManager.Instance["GameListHeaderApplication"];
  526. case ApplicationSort.Developer:
  527. return LocaleManager.Instance["GameListHeaderDeveloper"];
  528. case ApplicationSort.LastPlayed:
  529. return LocaleManager.Instance["GameListHeaderLastPlayed"];
  530. case ApplicationSort.TotalTimePlayed:
  531. return LocaleManager.Instance["GameListHeaderTimePlayed"];
  532. case ApplicationSort.FileType:
  533. return LocaleManager.Instance["GameListHeaderFileExtension"];
  534. case ApplicationSort.FileSize:
  535. return LocaleManager.Instance["GameListHeaderFileSize"];
  536. case ApplicationSort.Path:
  537. return LocaleManager.Instance["GameListHeaderPath"];
  538. case ApplicationSort.Favorite:
  539. return LocaleManager.Instance["CommonFavorite"];
  540. }
  541. return string.Empty;
  542. }
  543. }
  544. public bool IsAscending
  545. {
  546. get => ConfigurationState.Instance.Ui.IsAscendingOrder;
  547. private set
  548. {
  549. ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value;
  550. OnPropertyChanged();
  551. OnPropertyChanged(nameof(SortMode));
  552. OnPropertyChanged(nameof(SortName));
  553. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  554. }
  555. }
  556. public KeyGesture ShowUiKey
  557. {
  558. get => KeyGesture.Parse(_showUikey); set
  559. {
  560. _showUikey = value.ToString();
  561. OnPropertyChanged();
  562. }
  563. }
  564. public KeyGesture ScreenshotKey
  565. {
  566. get => KeyGesture.Parse(_screenshotkey); set
  567. {
  568. _screenshotkey = value.ToString();
  569. OnPropertyChanged();
  570. }
  571. }
  572. public KeyGesture PauseKey
  573. {
  574. get => KeyGesture.Parse(_pauseKey); set
  575. {
  576. _pauseKey = value.ToString();
  577. OnPropertyChanged();
  578. }
  579. }
  580. public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
  581. public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
  582. public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
  583. public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
  584. public int GridSizeScale
  585. {
  586. get => ConfigurationState.Instance.Ui.GridSize;
  587. set
  588. {
  589. ConfigurationState.Instance.Ui.GridSize.Value = value;
  590. if (value < 2)
  591. {
  592. ShowNames = false;
  593. }
  594. OnPropertyChanged();
  595. OnPropertyChanged(nameof(IsGridSmall));
  596. OnPropertyChanged(nameof(IsGridMedium));
  597. OnPropertyChanged(nameof(IsGridLarge));
  598. OnPropertyChanged(nameof(IsGridHuge));
  599. OnPropertyChanged(nameof(ShowNames));
  600. OnPropertyChanged(nameof(GridItemPadding));
  601. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  602. }
  603. }
  604. public async void OpenAmiiboWindow()
  605. {
  606. if (!_isAmiiboRequested)
  607. {
  608. return;
  609. }
  610. if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
  611. {
  612. string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
  613. AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
  614. await window.ShowDialog(_owner);
  615. if (window.IsScanned)
  616. {
  617. _showAll = window.ViewModel.ShowAllAmiibo;
  618. _lastScannedAmiiboId = window.ScannedAmiibo.GetId();
  619. _owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
  620. }
  621. }
  622. }
  623. public void HandleShaderProgress(Switch emulationContext)
  624. {
  625. emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
  626. emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
  627. }
  628. private bool Filter(object arg)
  629. {
  630. if (arg is ApplicationData app)
  631. {
  632. return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower());
  633. }
  634. return false;
  635. }
  636. private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
  637. {
  638. AddApplication(e.AppData);
  639. }
  640. private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
  641. {
  642. StatusBarProgressValue = e.NumAppsLoaded;
  643. StatusBarProgressMaximum = e.NumAppsFound;
  644. LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
  645. Dispatcher.UIThread.Post(() =>
  646. {
  647. if (e.NumAppsFound == 0)
  648. {
  649. _owner.LoadProgressBar.IsVisible = false;
  650. }
  651. });
  652. }
  653. public void AddApplication(ApplicationData applicationData)
  654. {
  655. Dispatcher.UIThread.InvokeAsync(() =>
  656. {
  657. Applications.Add(applicationData);
  658. });
  659. }
  660. public async void LoadApplications()
  661. {
  662. await Dispatcher.UIThread.InvokeAsync(() =>
  663. {
  664. Applications.Clear();
  665. _owner.LoadProgressBar.IsVisible = true;
  666. StatusBarProgressMaximum = 0;
  667. StatusBarProgressValue = 0;
  668. LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
  669. });
  670. ReloadGameList();
  671. }
  672. private void ReloadGameList()
  673. {
  674. if (_isLoading)
  675. {
  676. return;
  677. }
  678. _isLoading = true;
  679. Thread thread = new(() =>
  680. {
  681. _owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
  682. _isLoading = false;
  683. })
  684. { Name = "GUI.AppListLoadThread", Priority = ThreadPriority.AboveNormal };
  685. thread.Start();
  686. }
  687. public async void OpenFile()
  688. {
  689. OpenFileDialog dialog = new()
  690. {
  691. Title = LocaleManager.Instance["OpenFileDialogTitle"]
  692. };
  693. dialog.Filters.Add(new FileDialogFilter
  694. {
  695. Name = LocaleManager.Instance["AllSupportedFormats"],
  696. Extensions =
  697. {
  698. "nsp",
  699. "pfs0",
  700. "xci",
  701. "nca",
  702. "nro",
  703. "nso"
  704. }
  705. });
  706. dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
  707. dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
  708. dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
  709. dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
  710. dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
  711. dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
  712. string[] files = await dialog.ShowAsync(_owner);
  713. if (files != null && files.Length > 0)
  714. {
  715. _owner.LoadApplication(files[0]);
  716. }
  717. }
  718. public async void OpenFolder()
  719. {
  720. OpenFolderDialog dialog = new()
  721. {
  722. Title = LocaleManager.Instance["OpenFolderDialogTitle"]
  723. };
  724. string folder = await dialog.ShowAsync(_owner);
  725. if (!string.IsNullOrWhiteSpace(folder) && Directory.Exists(folder))
  726. {
  727. _owner.LoadApplication(folder);
  728. }
  729. }
  730. public void LoadConfigurableHotKeys()
  731. {
  732. if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
  733. {
  734. ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
  735. }
  736. if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
  737. {
  738. ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
  739. }
  740. if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
  741. {
  742. PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
  743. }
  744. }
  745. public void TakeScreenshot()
  746. {
  747. _owner.AppHost.ScreenshotRequested = true;
  748. }
  749. public void HideUi()
  750. {
  751. ShowMenuAndStatusBar = false;
  752. }
  753. public void SetListMode()
  754. {
  755. Glyph = Glyph.List;
  756. }
  757. public void SetGridMode()
  758. {
  759. Glyph = Glyph.Grid;
  760. }
  761. public void OpenMiiApplet()
  762. {
  763. string contentPath = _owner.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
  764. if (!string.IsNullOrWhiteSpace(contentPath))
  765. {
  766. _owner.LoadApplication(contentPath, false, "Mii Applet");
  767. }
  768. }
  769. public static void OpenRyujinxFolder()
  770. {
  771. OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
  772. }
  773. public static void OpenLogsFolder()
  774. {
  775. string logPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
  776. new DirectoryInfo(logPath).Create();
  777. OpenHelper.OpenFolder(logPath);
  778. }
  779. public void ToggleFullscreen()
  780. {
  781. if (Environment.TickCount64 - _lastFullscreenToggle < HotKeyPressDelayMs)
  782. {
  783. return;
  784. }
  785. _lastFullscreenToggle = Environment.TickCount64;
  786. WindowState state = _owner.WindowState;
  787. if (state == WindowState.FullScreen)
  788. {
  789. _owner.WindowState = WindowState.Normal;
  790. if (IsGameRunning)
  791. {
  792. ShowMenuAndStatusBar = true;
  793. }
  794. }
  795. else
  796. {
  797. _owner.WindowState = WindowState.FullScreen;
  798. if (IsGameRunning)
  799. {
  800. ShowMenuAndStatusBar = false;
  801. }
  802. }
  803. OnPropertyChanged(nameof(IsFullScreen));
  804. }
  805. public bool IsFullScreen => _owner.WindowState == WindowState.FullScreen;
  806. public void ToggleDockMode()
  807. {
  808. if (IsGameRunning)
  809. {
  810. ConfigurationState.Instance.System.EnableDockedMode.Value =
  811. !ConfigurationState.Instance.System.EnableDockedMode.Value;
  812. }
  813. }
  814. public async void ExitCurrentState()
  815. {
  816. if (_owner.WindowState == WindowState.FullScreen)
  817. {
  818. ToggleFullscreen();
  819. }
  820. else if (IsGameRunning)
  821. {
  822. await Task.Delay(100);
  823. _owner.AppHost?.ShowExitPrompt();
  824. }
  825. }
  826. public async void OpenSettings()
  827. {
  828. _owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
  829. await _owner.SettingsWindow.ShowDialog(_owner);
  830. LoadConfigurableHotKeys();
  831. }
  832. public async void ManageProfiles()
  833. {
  834. await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
  835. }
  836. public async void OpenAboutWindow()
  837. {
  838. AboutWindow window = new();
  839. await window.ShowDialog(_owner);
  840. }
  841. public void ChangeLanguage(object obj)
  842. {
  843. LocaleManager.Instance.LoadDefaultLanguage();
  844. LocaleManager.Instance.LoadLanguage((string)obj);
  845. }
  846. private void ProgressHandler<T>(T state, int current, int total) where T : Enum
  847. {
  848. try
  849. {
  850. ProgressMaximum = total;
  851. ProgressValue = current;
  852. switch (state)
  853. {
  854. case PtcLoadingState ptcState:
  855. CacheLoadStatus = $"{current} / {total}";
  856. switch (ptcState)
  857. {
  858. case PtcLoadingState.Start:
  859. case PtcLoadingState.Loading:
  860. LoadHeading = LocaleManager.Instance["CompilingPPTC"];
  861. IsLoadingIndeterminate = false;
  862. break;
  863. case PtcLoadingState.Loaded:
  864. LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
  865. IsLoadingIndeterminate = true;
  866. CacheLoadStatus = "";
  867. break;
  868. }
  869. break;
  870. case ShaderCacheLoadingState shaderCacheState:
  871. CacheLoadStatus = $"{current} / {total}";
  872. switch (shaderCacheState)
  873. {
  874. case ShaderCacheLoadingState.Start:
  875. case ShaderCacheLoadingState.Loading:
  876. LoadHeading = LocaleManager.Instance["CompilingShaders"];
  877. IsLoadingIndeterminate = false;
  878. break;
  879. case ShaderCacheLoadingState.Loaded:
  880. LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
  881. IsLoadingIndeterminate = true;
  882. CacheLoadStatus = "";
  883. break;
  884. }
  885. break;
  886. default:
  887. throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
  888. }
  889. }
  890. catch (Exception) { }
  891. }
  892. public void OpenUserSaveDirectory()
  893. {
  894. var selection = SelectedApplication;
  895. if (selection != null)
  896. {
  897. Task.Run(() =>
  898. {
  899. if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
  900. out ulong titleIdNumber))
  901. {
  902. Dispatcher.UIThread.Post(async () =>
  903. {
  904. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
  905. });
  906. return;
  907. }
  908. var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
  909. var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
  910. OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
  911. });
  912. }
  913. }
  914. public void ToggleFavorite()
  915. {
  916. var selection = SelectedApplication;
  917. if (selection != null)
  918. {
  919. selection.Favorite = !selection.Favorite;
  920. _owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
  921. {
  922. appMetadata.Favorite = selection.Favorite;
  923. });
  924. RefreshView();
  925. }
  926. }
  927. public void OpenModsDirectory()
  928. {
  929. var selection = SelectedApplication;
  930. if (selection != null)
  931. {
  932. string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
  933. string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
  934. OpenHelper.OpenFolder(titleModsPath);
  935. }
  936. }
  937. public void OpenSdModsDirectory()
  938. {
  939. var selection = SelectedApplication;
  940. if (selection != null)
  941. {
  942. string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
  943. string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
  944. OpenHelper.OpenFolder(titleModsPath);
  945. }
  946. }
  947. public void OpenPtcDirectory()
  948. {
  949. var selection = SelectedApplication;
  950. if (selection != null)
  951. {
  952. string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
  953. string mainPath = Path.Combine(ptcDir, "0");
  954. string backupPath = Path.Combine(ptcDir, "1");
  955. if (!Directory.Exists(ptcDir))
  956. {
  957. Directory.CreateDirectory(ptcDir);
  958. Directory.CreateDirectory(mainPath);
  959. Directory.CreateDirectory(backupPath);
  960. }
  961. OpenHelper.OpenFolder(ptcDir);
  962. }
  963. }
  964. public async void PurgePtcCache()
  965. {
  966. var selection = SelectedApplication;
  967. if (selection != null)
  968. {
  969. DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
  970. DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
  971. // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
  972. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
  973. string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
  974. List<FileInfo> cacheFiles = new();
  975. if (mainDir.Exists)
  976. {
  977. cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
  978. }
  979. if (backupDir.Exists)
  980. {
  981. cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
  982. }
  983. if (cacheFiles.Count > 0 && result == UserResult.Yes)
  984. {
  985. foreach (FileInfo file in cacheFiles)
  986. {
  987. try
  988. {
  989. file.Delete();
  990. }
  991. catch (Exception e)
  992. {
  993. await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
  994. }
  995. }
  996. }
  997. }
  998. }
  999. public void OpenShaderCacheDirectory()
  1000. {
  1001. var selection = SelectedApplication;
  1002. if (selection != null)
  1003. {
  1004. string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
  1005. if (!Directory.Exists(shaderCacheDir))
  1006. {
  1007. Directory.CreateDirectory(shaderCacheDir);
  1008. }
  1009. OpenHelper.OpenFolder(shaderCacheDir);
  1010. }
  1011. }
  1012. public void SimulateWakeUpMessage()
  1013. {
  1014. _owner.AppHost.Device.System.SimulateWakeUpMessage();
  1015. }
  1016. public async void PurgeShaderCache()
  1017. {
  1018. var selection = SelectedApplication;
  1019. if (selection != null)
  1020. {
  1021. DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
  1022. // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
  1023. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
  1024. string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
  1025. List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
  1026. List<FileInfo> newCacheFiles = new List<FileInfo>();
  1027. if (shaderCacheDir.Exists)
  1028. {
  1029. oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
  1030. newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
  1031. newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
  1032. }
  1033. if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && result == UserResult.Yes)
  1034. {
  1035. foreach (DirectoryInfo directory in oldCacheDirectories)
  1036. {
  1037. try
  1038. {
  1039. directory.Delete(true);
  1040. }
  1041. catch (Exception e)
  1042. {
  1043. await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
  1044. }
  1045. }
  1046. }
  1047. foreach (FileInfo file in newCacheFiles)
  1048. {
  1049. try
  1050. {
  1051. file.Delete();
  1052. }
  1053. catch (Exception e)
  1054. {
  1055. await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
  1056. }
  1057. }
  1058. }
  1059. }
  1060. public async void CheckForUpdates()
  1061. {
  1062. if (Updater.CanUpdate(true, _owner))
  1063. {
  1064. await Updater.BeginParse(_owner, true);
  1065. }
  1066. }
  1067. public async void OpenTitleUpdateManager()
  1068. {
  1069. var selection = SelectedApplication;
  1070. if (selection != null)
  1071. {
  1072. TitleUpdateWindow titleUpdateManager =
  1073. new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
  1074. await titleUpdateManager.ShowDialog(_owner);
  1075. }
  1076. }
  1077. public async void OpenDownloadableContentManager()
  1078. {
  1079. var selection = SelectedApplication;
  1080. if (selection != null)
  1081. {
  1082. DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName);
  1083. await downloadableContentManager.ShowDialog(_owner);
  1084. }
  1085. }
  1086. public async void OpenCheatManager()
  1087. {
  1088. var selection = SelectedApplication;
  1089. if (selection != null)
  1090. {
  1091. CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
  1092. await cheatManager.ShowDialog(_owner);
  1093. }
  1094. }
  1095. public async void OpenCheatManagerForCurrentApp()
  1096. {
  1097. if (!IsGameRunning)
  1098. {
  1099. return;
  1100. }
  1101. var application = _owner.AppHost.Device.Application;
  1102. if (application != null)
  1103. {
  1104. CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName);
  1105. await cheatManager.ShowDialog(_owner);
  1106. _owner.AppHost.Device.EnableCheats();
  1107. }
  1108. }
  1109. public void OpenDeviceSaveDirectory()
  1110. {
  1111. var selection = SelectedApplication;
  1112. if (selection != null)
  1113. {
  1114. Task.Run(() =>
  1115. {
  1116. if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
  1117. out ulong titleIdNumber))
  1118. {
  1119. Dispatcher.UIThread.Post(async () =>
  1120. {
  1121. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
  1122. });
  1123. return;
  1124. }
  1125. var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Device, userId: default, saveDataId: default, index: default);
  1126. OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
  1127. });
  1128. }
  1129. }
  1130. public void OpenBcatSaveDirectory()
  1131. {
  1132. var selection = SelectedApplication;
  1133. if (selection != null)
  1134. {
  1135. Task.Run(() =>
  1136. {
  1137. if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
  1138. out ulong titleIdNumber))
  1139. {
  1140. Dispatcher.UIThread.Post(async () =>
  1141. {
  1142. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
  1143. });
  1144. return;
  1145. }
  1146. var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
  1147. OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
  1148. });
  1149. }
  1150. }
  1151. private void OpenSaveDirectory(in SaveDataFilter filter, ApplicationData data, ulong titleId)
  1152. {
  1153. ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName);
  1154. }
  1155. private async void ExtractLogo()
  1156. {
  1157. var selection = SelectedApplication;
  1158. if (selection != null)
  1159. {
  1160. await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
  1161. }
  1162. }
  1163. private async void ExtractRomFs()
  1164. {
  1165. var selection = SelectedApplication;
  1166. if (selection != null)
  1167. {
  1168. await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
  1169. }
  1170. }
  1171. private async void ExtractExeFs()
  1172. {
  1173. var selection = SelectedApplication;
  1174. if (selection != null)
  1175. {
  1176. await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
  1177. }
  1178. }
  1179. public void CloseWindow()
  1180. {
  1181. _owner.Close();
  1182. }
  1183. private async Task HandleFirmwareInstallation(string path)
  1184. {
  1185. try
  1186. {
  1187. string filename = path;
  1188. SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
  1189. if (firmwareVersion == null)
  1190. {
  1191. await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
  1192. return;
  1193. }
  1194. string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
  1195. SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
  1196. string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
  1197. if (currentVersion != null)
  1198. {
  1199. dialogMessage += string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSubMessage"], currentVersion.VersionString);
  1200. }
  1201. dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
  1202. UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
  1203. dialogTitle,
  1204. dialogMessage,
  1205. LocaleManager.Instance["InputDialogYes"],
  1206. LocaleManager.Instance["InputDialogNo"],
  1207. LocaleManager.Instance["RyujinxConfirm"]);
  1208. UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallWaitMessage"]);
  1209. if (result == UserResult.Yes)
  1210. {
  1211. Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
  1212. Thread thread = new(() =>
  1213. {
  1214. Dispatcher.UIThread.InvokeAsync(delegate
  1215. {
  1216. waitingDialog.Show();
  1217. });
  1218. try
  1219. {
  1220. _owner.ContentManager.InstallFirmware(filename);
  1221. Dispatcher.UIThread.InvokeAsync(async delegate
  1222. {
  1223. waitingDialog.Close();
  1224. string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
  1225. await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
  1226. Logger.Info?.Print(LogClass.Application, message);
  1227. // Purge Applet Cache.
  1228. DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
  1229. if (miiEditorCacheFolder.Exists)
  1230. {
  1231. miiEditorCacheFolder.Delete(true);
  1232. }
  1233. });
  1234. }
  1235. catch (Exception ex)
  1236. {
  1237. Dispatcher.UIThread.InvokeAsync(async () =>
  1238. {
  1239. waitingDialog.Close();
  1240. await ContentDialogHelper.CreateErrorDialog(ex.Message);
  1241. });
  1242. }
  1243. finally
  1244. {
  1245. _owner.RefreshFirmwareStatus();
  1246. }
  1247. });
  1248. thread.Name = "GUI.FirmwareInstallerThread";
  1249. thread.Start();
  1250. }
  1251. }
  1252. catch (LibHac.Common.Keys.MissingKeyException ex)
  1253. {
  1254. Logger.Error?.Print(LogClass.Application, ex.ToString());
  1255. Dispatcher.UIThread.Post(async () => await
  1256. UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
  1257. }
  1258. catch (Exception ex)
  1259. {
  1260. await ContentDialogHelper.CreateErrorDialog(ex.Message);
  1261. }
  1262. }
  1263. public async void InstallFirmwareFromFile()
  1264. {
  1265. OpenFileDialog dialog = new() { AllowMultiple = false };
  1266. dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
  1267. dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
  1268. dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
  1269. string[] file = await dialog.ShowAsync(_owner);
  1270. if (file != null && file.Length > 0)
  1271. {
  1272. await HandleFirmwareInstallation(file[0]);
  1273. }
  1274. }
  1275. public async void InstallFirmwareFromFolder()
  1276. {
  1277. OpenFolderDialog dialog = new();
  1278. string folder = await dialog.ShowAsync(_owner);
  1279. if (!string.IsNullOrWhiteSpace(folder))
  1280. {
  1281. await HandleFirmwareInstallation(folder);
  1282. }
  1283. }
  1284. }
  1285. }