MainWindowViewModel.cs 75 KB

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