MainWindowViewModel.cs 69 KB

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