MainWindowViewModel.cs 63 KB

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