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