/ src / Ryujinx.Gtk3 / UI / MainWindow.cs
MainWindow.cs
   1  using Gtk;
   2  using LibHac.Common;
   3  using LibHac.Common.Keys;
   4  using LibHac.Ncm;
   5  using LibHac.Ns;
   6  using LibHac.Tools.FsSystem;
   7  using LibHac.Tools.FsSystem.NcaUtils;
   8  using Ryujinx.Audio.Backends.Dummy;
   9  using Ryujinx.Audio.Backends.OpenAL;
  10  using Ryujinx.Audio.Backends.SDL2;
  11  using Ryujinx.Audio.Backends.SoundIo;
  12  using Ryujinx.Audio.Integration;
  13  using Ryujinx.Common;
  14  using Ryujinx.Common.Configuration;
  15  using Ryujinx.Common.Configuration.Multiplayer;
  16  using Ryujinx.Common.Logging;
  17  using Ryujinx.Common.SystemInterop;
  18  using Ryujinx.Cpu;
  19  using Ryujinx.Graphics.GAL;
  20  using Ryujinx.Graphics.GAL.Multithreading;
  21  using Ryujinx.HLE.FileSystem;
  22  using Ryujinx.HLE.HOS;
  23  using Ryujinx.HLE.HOS.Services.Account.Acc;
  24  using Ryujinx.HLE.HOS.SystemState;
  25  using Ryujinx.Input.GTK3;
  26  using Ryujinx.Input.HLE;
  27  using Ryujinx.Input.SDL2;
  28  using Ryujinx.Modules;
  29  using Ryujinx.UI.App.Common;
  30  using Ryujinx.UI.Applet;
  31  using Ryujinx.UI.Common;
  32  using Ryujinx.UI.Common.Configuration;
  33  using Ryujinx.UI.Common.Helper;
  34  using Ryujinx.UI.Helper;
  35  using Ryujinx.UI.Widgets;
  36  using Ryujinx.UI.Windows;
  37  using Silk.NET.Vulkan;
  38  using SPB.Graphics.Vulkan;
  39  using System;
  40  using System.Collections.Generic;
  41  using System.Diagnostics;
  42  using System.Globalization;
  43  using System.IO;
  44  using System.Reflection;
  45  using System.Threading;
  46  using System.Threading.Tasks;
  47  using GUI = Gtk.Builder.ObjectAttribute;
  48  using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
  49  
  50  namespace Ryujinx.UI
  51  {
  52      public class MainWindow : Window
  53      {
  54          private readonly VirtualFileSystem _virtualFileSystem;
  55          private readonly ContentManager _contentManager;
  56          private readonly AccountManager _accountManager;
  57          private readonly LibHacHorizonManager _libHacHorizonManager;
  58  
  59          private UserChannelPersistence _userChannelPersistence;
  60  
  61          private HLE.Switch _emulationContext;
  62  
  63          private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
  64  
  65          private readonly GtkHostUIHandler _uiHandler;
  66          private readonly AutoResetEvent _deviceExitStatus;
  67          private readonly ListStore _tableStore;
  68  
  69          private bool _updatingGameTable;
  70          private bool _gameLoaded;
  71          private bool _ending;
  72  
  73          private ApplicationData _currentApplicationData = null;
  74  
  75          private string _lastScannedAmiiboId = "";
  76          private bool _lastScannedAmiiboShowAll = false;
  77  
  78          public readonly ApplicationLibrary ApplicationLibrary;
  79          public RendererWidgetBase RendererWidget;
  80          public InputManager InputManager;
  81  
  82          public bool IsFocused;
  83  
  84  #pragma warning disable CS0169, CS0649, IDE0044, IDE0051 // Field is never assigned to, Add readonly modifier, Remove unused private member
  85  
  86          [GUI] public MenuItem ExitMenuItem;
  87          [GUI] public MenuItem UpdateMenuItem;
  88          [GUI] MenuBar _menuBar;
  89          [GUI] Box _footerBox;
  90          [GUI] Box _statusBar;
  91          [GUI] MenuItem _optionMenu;
  92          [GUI] MenuItem _manageUserProfiles;
  93          [GUI] MenuItem _fileMenu;
  94          [GUI] MenuItem _loadApplicationFile;
  95          [GUI] MenuItem _loadApplicationFolder;
  96          [GUI] MenuItem _appletMenu;
  97          [GUI] MenuItem _actionMenu;
  98          [GUI] MenuItem _pauseEmulation;
  99          [GUI] MenuItem _resumeEmulation;
 100          [GUI] MenuItem _stopEmulation;
 101          [GUI] MenuItem _simulateWakeUpMessage;
 102          [GUI] MenuItem _scanAmiibo;
 103          [GUI] MenuItem _takeScreenshot;
 104          [GUI] MenuItem _hideUI;
 105          [GUI] MenuItem _fullScreen;
 106          [GUI] CheckMenuItem _startFullScreen;
 107          [GUI] CheckMenuItem _showConsole;
 108          [GUI] CheckMenuItem _favToggle;
 109          [GUI] MenuItem _firmwareInstallDirectory;
 110          [GUI] MenuItem _firmwareInstallFile;
 111          [GUI] MenuItem _fileTypesSubMenu;
 112          [GUI] Label _fifoStatus;
 113          [GUI] CheckMenuItem _iconToggle;
 114          [GUI] CheckMenuItem _developerToggle;
 115          [GUI] CheckMenuItem _appToggle;
 116          [GUI] CheckMenuItem _timePlayedToggle;
 117          [GUI] CheckMenuItem _versionToggle;
 118          [GUI] CheckMenuItem _lastPlayedToggle;
 119          [GUI] CheckMenuItem _fileExtToggle;
 120          [GUI] CheckMenuItem _pathToggle;
 121          [GUI] CheckMenuItem _fileSizeToggle;
 122          [GUI] CheckMenuItem _nspShown;
 123          [GUI] CheckMenuItem _pfs0Shown;
 124          [GUI] CheckMenuItem _xciShown;
 125          [GUI] CheckMenuItem _ncaShown;
 126          [GUI] CheckMenuItem _nroShown;
 127          [GUI] CheckMenuItem _nsoShown;
 128          [GUI] Label _gpuBackend;
 129          [GUI] Label _dockedMode;
 130          [GUI] Label _aspectRatio;
 131          [GUI] Label _gameStatus;
 132          [GUI] TreeView _gameTable;
 133          [GUI] TreeSelection _gameTableSelection;
 134          [GUI] ScrolledWindow _gameTableWindow;
 135          [GUI] Label _gpuName;
 136          [GUI] Label _progressLabel;
 137          [GUI] Label _firmwareVersionLabel;
 138          [GUI] Gtk.ProgressBar _progressBar;
 139          [GUI] Box _viewBox;
 140          [GUI] Label _vSyncStatus;
 141          [GUI] Label _volumeStatus;
 142          [GUI] Box _listStatusBox;
 143          [GUI] Label _loadingStatusLabel;
 144          [GUI] Gtk.ProgressBar _loadingStatusBar;
 145  
 146  #pragma warning restore CS0649, IDE0044, CS0169, IDE0051
 147  
 148          public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { }
 149  
 150          private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin"))
 151          {
 152              builder.Autoconnect(this);
 153  
 154              // Apply custom theme if needed.
 155              ThemeHelper.ApplyTheme();
 156  
 157              SetWindowSizePosition();
 158  
 159              Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
 160              Title = $"Ryujinx {Program.Version}";
 161  
 162              // Hide emulation context status bar.
 163              _statusBar.Hide();
 164  
 165              // Instantiate HLE objects.
 166              _virtualFileSystem = VirtualFileSystem.CreateInstance();
 167              _libHacHorizonManager = new LibHacHorizonManager();
 168  
 169              _libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
 170              _libHacHorizonManager.InitializeArpServer();
 171              _libHacHorizonManager.InitializeBcatServer();
 172              _libHacHorizonManager.InitializeSystemClients();
 173  
 174              // Save data created before we supported extra data in directory save data will not work properly if
 175              // given empty extra data. Luckily some of that extra data can be created using the data from the
 176              // save data indexer, which should be enough to check access permissions for user saves.
 177              // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
 178              // Consider removing this at some point in the future when we don't need to worry about old saves.
 179              VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient);
 180  
 181              _contentManager = new ContentManager(_virtualFileSystem);
 182              _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
 183              _userChannelPersistence = new UserChannelPersistence();
 184  
 185              IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
 186                  ? IntegrityCheckLevel.ErrorOnInvalid
 187                  : IntegrityCheckLevel.None;
 188  
 189              // Instantiate GUI objects.
 190              ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
 191              {
 192                  DesiredLanguage = ConfigurationState.Instance.System.Language,
 193              };
 194              _uiHandler = new GtkHostUIHandler(this);
 195              _deviceExitStatus = new AutoResetEvent(false);
 196  
 197              WindowStateEvent += WindowStateEvent_Changed;
 198              DeleteEvent += Window_Close;
 199              FocusInEvent += MainWindow_FocusInEvent;
 200              FocusOutEvent += MainWindow_FocusOutEvent;
 201  
 202              ApplicationLibrary.ApplicationAdded += Application_Added;
 203              ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
 204  
 205              _fileMenu.StateChanged += FileMenu_StateChanged;
 206              _actionMenu.StateChanged += ActionMenu_StateChanged;
 207              _optionMenu.StateChanged += OptionMenu_StateChanged;
 208  
 209              _gameTable.ButtonReleaseEvent += Row_Clicked;
 210              _fullScreen.Activated += FullScreen_Toggled;
 211  
 212              RendererWidgetBase.StatusUpdatedEvent += Update_StatusBar;
 213  
 214              ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
 215              ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
 216              ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
 217              ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
 218  
 219              ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode;
 220              ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId;
 221  
 222              if (ConfigurationState.Instance.UI.StartFullscreen)
 223              {
 224                  _startFullScreen.Active = true;
 225              }
 226  
 227              _showConsole.Active = ConfigurationState.Instance.UI.ShowConsole.Value;
 228              _showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported;
 229  
 230              _actionMenu.Sensitive = false;
 231              _pauseEmulation.Sensitive = false;
 232              _resumeEmulation.Sensitive = false;
 233  
 234              _nspShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value;
 235              _pfs0Shown.Active = ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value;
 236              _xciShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value;
 237              _ncaShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value;
 238              _nroShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value;
 239              _nsoShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value;
 240  
 241              _nspShown.Toggled += NSP_Shown_Toggled;
 242              _pfs0Shown.Toggled += PFS0_Shown_Toggled;
 243              _xciShown.Toggled += XCI_Shown_Toggled;
 244              _ncaShown.Toggled += NCA_Shown_Toggled;
 245              _nroShown.Toggled += NRO_Shown_Toggled;
 246              _nsoShown.Toggled += NSO_Shown_Toggled;
 247  
 248              _fileTypesSubMenu.Visible = FileAssociationHelper.IsTypeAssociationSupported;
 249  
 250              if (ConfigurationState.Instance.UI.GuiColumns.FavColumn)
 251              {
 252                  _favToggle.Active = true;
 253              }
 254              if (ConfigurationState.Instance.UI.GuiColumns.IconColumn)
 255              {
 256                  _iconToggle.Active = true;
 257              }
 258              if (ConfigurationState.Instance.UI.GuiColumns.AppColumn)
 259              {
 260                  _appToggle.Active = true;
 261              }
 262              if (ConfigurationState.Instance.UI.GuiColumns.DevColumn)
 263              {
 264                  _developerToggle.Active = true;
 265              }
 266              if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn)
 267              {
 268                  _versionToggle.Active = true;
 269              }
 270              if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn)
 271              {
 272                  _timePlayedToggle.Active = true;
 273              }
 274              if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn)
 275              {
 276                  _lastPlayedToggle.Active = true;
 277              }
 278              if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn)
 279              {
 280                  _fileExtToggle.Active = true;
 281              }
 282              if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn)
 283              {
 284                  _fileSizeToggle.Active = true;
 285              }
 286              if (ConfigurationState.Instance.UI.GuiColumns.PathColumn)
 287              {
 288                  _pathToggle.Active = true;
 289              }
 290  
 291              _favToggle.Toggled += Fav_Toggled;
 292              _iconToggle.Toggled += Icon_Toggled;
 293              _appToggle.Toggled += App_Toggled;
 294              _developerToggle.Toggled += Developer_Toggled;
 295              _versionToggle.Toggled += Version_Toggled;
 296              _timePlayedToggle.Toggled += TimePlayed_Toggled;
 297              _lastPlayedToggle.Toggled += LastPlayed_Toggled;
 298              _fileExtToggle.Toggled += FileExt_Toggled;
 299              _fileSizeToggle.Toggled += FileSize_Toggled;
 300              _pathToggle.Toggled += Path_Toggled;
 301  
 302              _gameTable.Model = _tableStore = new ListStore(
 303                  typeof(bool),
 304                  typeof(Gdk.Pixbuf),
 305                  typeof(string),
 306                  typeof(string),
 307                  typeof(string),
 308                  typeof(string),
 309                  typeof(string),
 310                  typeof(string),
 311                  typeof(string),
 312                  typeof(string),
 313                  typeof(BlitStruct<ApplicationControlProperty>));
 314  
 315              _tableStore.SetSortFunc(5, SortHelper.TimePlayedSort);
 316              _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort);
 317              _tableStore.SetSortFunc(8, SortHelper.FileSizeSort);
 318  
 319              int columnId = ConfigurationState.Instance.UI.ColumnSort.SortColumnId;
 320              bool ascending = ConfigurationState.Instance.UI.ColumnSort.SortAscending;
 321  
 322              _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending);
 323  
 324              _gameTable.EnableSearch = true;
 325              _gameTable.SearchColumn = 2;
 326              _gameTable.SearchEqualFunc = (model, col, key, iter) => !((string)model.GetValue(iter, col)).Contains(key, StringComparison.InvariantCultureIgnoreCase);
 327  
 328              _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
 329  
 330              UpdateColumns();
 331  
 332              ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
 333              {
 334                  if (args.OldValue != args.NewValue)
 335                  {
 336                      UpdateGameTable();
 337                  }
 338              };
 339  
 340              Task.Run(RefreshFirmwareLabel);
 341  
 342              InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver());
 343          }
 344  
 345          private void UpdateMultiplayerLanInterfaceId(object sender, ReactiveEventArgs<string> args)
 346          {
 347              if (_emulationContext != null)
 348              {
 349                  _emulationContext.Configuration.MultiplayerLanInterfaceId = args.NewValue;
 350              }
 351          }
 352  
 353          private void UpdateMultiplayerMode(object sender, ReactiveEventArgs<MultiplayerMode> args)
 354          {
 355              if (_emulationContext != null)
 356              {
 357                  _emulationContext.Configuration.MultiplayerMode = args.NewValue;
 358              }
 359          }
 360  
 361          private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
 362          {
 363              if (_emulationContext != null)
 364              {
 365                  _emulationContext.Configuration.IgnoreMissingServices = args.NewValue;
 366              }
 367          }
 368  
 369          private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args)
 370          {
 371              if (_emulationContext != null)
 372              {
 373                  _emulationContext.Configuration.AspectRatio = args.NewValue;
 374              }
 375          }
 376  
 377          private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
 378          {
 379              _emulationContext?.System.ChangeDockedModeState(e.NewValue);
 380          }
 381  
 382          private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
 383          {
 384              _emulationContext?.SetVolume(e.NewValue);
 385          }
 386  
 387          private void WindowStateEvent_Changed(object o, WindowStateEventArgs args)
 388          {
 389              _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen";
 390          }
 391  
 392          private void MainWindow_FocusOutEvent(object o, FocusOutEventArgs args)
 393          {
 394              IsFocused = false;
 395          }
 396  
 397          private void MainWindow_FocusInEvent(object o, FocusInEventArgs args)
 398          {
 399              IsFocused = true;
 400          }
 401  
 402          private void UpdateColumns()
 403          {
 404              foreach (TreeViewColumn column in _gameTable.Columns)
 405              {
 406                  _gameTable.RemoveColumn(column);
 407              }
 408  
 409              CellRendererToggle favToggle = new();
 410              favToggle.Toggled += FavToggle_Toggled;
 411  
 412              if (ConfigurationState.Instance.UI.GuiColumns.FavColumn)
 413              {
 414                  _gameTable.AppendColumn("Fav", favToggle, "active", 0);
 415              }
 416              if (ConfigurationState.Instance.UI.GuiColumns.IconColumn)
 417              {
 418                  _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1);
 419              }
 420              if (ConfigurationState.Instance.UI.GuiColumns.AppColumn)
 421              {
 422                  _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2);
 423              }
 424              if (ConfigurationState.Instance.UI.GuiColumns.DevColumn)
 425              {
 426                  _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3);
 427              }
 428              if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn)
 429              {
 430                  _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4);
 431              }
 432              if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn)
 433              {
 434                  _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5);
 435              }
 436              if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn)
 437              {
 438                  _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6);
 439              }
 440              if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn)
 441              {
 442                  _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7);
 443              }
 444              if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn)
 445              {
 446                  _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8);
 447              }
 448              if (ConfigurationState.Instance.UI.GuiColumns.PathColumn)
 449              {
 450                  _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9);
 451              }
 452  
 453              foreach (TreeViewColumn column in _gameTable.Columns)
 454              {
 455                  switch (column.Title)
 456                  {
 457                      case "Fav":
 458                          column.SortColumnId = 0;
 459                          column.Clicked += Column_Clicked;
 460                          break;
 461                      case "Application":
 462                          column.SortColumnId = 2;
 463                          column.Clicked += Column_Clicked;
 464                          break;
 465                      case "Developer":
 466                          column.SortColumnId = 3;
 467                          column.Clicked += Column_Clicked;
 468                          break;
 469                      case "Version":
 470                          column.SortColumnId = 4;
 471                          column.Clicked += Column_Clicked;
 472                          break;
 473                      case "Time Played":
 474                          column.SortColumnId = 5;
 475                          column.Clicked += Column_Clicked;
 476                          break;
 477                      case "Last Played":
 478                          column.SortColumnId = 6;
 479                          column.Clicked += Column_Clicked;
 480                          break;
 481                      case "File Ext":
 482                          column.SortColumnId = 7;
 483                          column.Clicked += Column_Clicked;
 484                          break;
 485                      case "File Size":
 486                          column.SortColumnId = 8;
 487                          column.Clicked += Column_Clicked;
 488                          break;
 489                      case "Path":
 490                          column.SortColumnId = 9;
 491                          column.Clicked += Column_Clicked;
 492                          break;
 493                  }
 494              }
 495          }
 496  
 497          protected override void OnDestroyed()
 498          {
 499              InputManager.Dispose();
 500          }
 501  
 502          private void InitializeSwitchInstance()
 503          {
 504              _virtualFileSystem.ReloadKeySet();
 505  
 506              IRenderer renderer;
 507  
 508              if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan)
 509              {
 510                  string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
 511                  renderer = new Graphics.Vulkan.VulkanRenderer(Vk.GetApi(), CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
 512              }
 513              else
 514              {
 515                  renderer = new Graphics.OpenGL.OpenGLRenderer();
 516              }
 517  
 518              BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
 519  
 520              bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
 521  
 522              if (threadedGAL)
 523              {
 524                  renderer = new ThreadedRenderer(renderer);
 525              }
 526  
 527              Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {threadedGAL}");
 528  
 529              IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
 530  
 531              if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2)
 532              {
 533                  if (SDL2HardwareDeviceDriver.IsSupported)
 534                  {
 535                      deviceDriver = new SDL2HardwareDeviceDriver();
 536                  }
 537                  else
 538                  {
 539                      Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
 540  
 541                      if (OpenALHardwareDeviceDriver.IsSupported)
 542                      {
 543                          Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
 544  
 545                          ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
 546                          SaveConfig();
 547  
 548                          deviceDriver = new OpenALHardwareDeviceDriver();
 549                      }
 550                      else
 551                      {
 552                          Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO.");
 553  
 554                          if (SoundIoHardwareDeviceDriver.IsSupported)
 555                          {
 556                              Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
 557  
 558                              ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
 559                              SaveConfig();
 560  
 561                              deviceDriver = new SoundIoHardwareDeviceDriver();
 562                          }
 563                          else
 564                          {
 565                              Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
 566                          }
 567                      }
 568                  }
 569              }
 570              else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
 571              {
 572                  if (SoundIoHardwareDeviceDriver.IsSupported)
 573                  {
 574                      deviceDriver = new SoundIoHardwareDeviceDriver();
 575                  }
 576                  else
 577                  {
 578                      Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2.");
 579  
 580                      if (SDL2HardwareDeviceDriver.IsSupported)
 581                      {
 582                          Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
 583  
 584                          ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
 585                          SaveConfig();
 586  
 587                          deviceDriver = new SDL2HardwareDeviceDriver();
 588                      }
 589                      else
 590                      {
 591                          Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
 592  
 593                          if (OpenALHardwareDeviceDriver.IsSupported)
 594                          {
 595                              Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
 596  
 597                              ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
 598                              SaveConfig();
 599  
 600                              deviceDriver = new OpenALHardwareDeviceDriver();
 601                          }
 602                          else
 603                          {
 604                              Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out.");
 605                          }
 606                      }
 607                  }
 608              }
 609              else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl)
 610              {
 611                  if (OpenALHardwareDeviceDriver.IsSupported)
 612                  {
 613                      deviceDriver = new OpenALHardwareDeviceDriver();
 614                  }
 615                  else
 616                  {
 617                      Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2.");
 618  
 619                      if (SDL2HardwareDeviceDriver.IsSupported)
 620                      {
 621                          Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
 622  
 623                          ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
 624                          SaveConfig();
 625  
 626                          deviceDriver = new SDL2HardwareDeviceDriver();
 627                      }
 628                      else
 629                      {
 630                          Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO.");
 631  
 632                          if (SoundIoHardwareDeviceDriver.IsSupported)
 633                          {
 634                              Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
 635  
 636                              ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
 637                              SaveConfig();
 638  
 639                              deviceDriver = new SoundIoHardwareDeviceDriver();
 640                          }
 641                          else
 642                          {
 643                              Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
 644                          }
 645                      }
 646                  }
 647              }
 648  
 649              var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value
 650                  ? HLE.MemoryConfiguration.MemoryConfiguration8GiB
 651                  : HLE.MemoryConfiguration.MemoryConfiguration4GiB;
 652  
 653              IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
 654  
 655              HLE.HLEConfiguration configuration = new(_virtualFileSystem,
 656                  _libHacHorizonManager,
 657                  _contentManager,
 658                  _accountManager,
 659                  _userChannelPersistence,
 660                  renderer,
 661                  deviceDriver,
 662                  memoryConfiguration,
 663                  _uiHandler,
 664                  (SystemLanguage)ConfigurationState.Instance.System.Language.Value,
 665                  (RegionCode)ConfigurationState.Instance.System.Region.Value,
 666                  ConfigurationState.Instance.Graphics.EnableVsync,
 667                  ConfigurationState.Instance.System.EnableDockedMode,
 668                  ConfigurationState.Instance.System.EnablePtc,
 669                  ConfigurationState.Instance.System.EnableInternetAccess,
 670                  fsIntegrityCheckLevel,
 671                  ConfigurationState.Instance.System.FsGlobalAccessLogMode,
 672                  ConfigurationState.Instance.System.SystemTimeOffset,
 673                  ConfigurationState.Instance.System.TimeZone,
 674                  ConfigurationState.Instance.System.MemoryManagerMode,
 675                  ConfigurationState.Instance.System.IgnoreMissingServices,
 676                  ConfigurationState.Instance.Graphics.AspectRatio,
 677                  ConfigurationState.Instance.System.AudioVolume,
 678                  ConfigurationState.Instance.System.UseHypervisor,
 679                  ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
 680                  ConfigurationState.Instance.Multiplayer.Mode);
 681  
 682              _emulationContext = new HLE.Switch(configuration);
 683          }
 684  
 685          private SurfaceKHR CreateVulkanSurface(Instance instance, Vk vk)
 686          {
 687              return new SurfaceKHR((ulong)((VulkanRenderer)RendererWidget).CreateWindowSurface(instance.Handle));
 688          }
 689  
 690          private void SetupProgressUIHandlers()
 691          {
 692              if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
 693              {
 694                  _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
 695                  _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
 696              }
 697  
 698              _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
 699              _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
 700          }
 701  
 702          private void ProgressHandler<T>(T state, int current, int total) where T : Enum
 703          {
 704              bool visible;
 705              string label;
 706  
 707              switch (state)
 708              {
 709                  case LoadState ptcState:
 710                      visible = ptcState != LoadState.Loaded;
 711                      label = $"PTC : {current}/{total}";
 712                      break;
 713                  case ShaderCacheLoadingState shaderCacheState:
 714                      visible = shaderCacheState != ShaderCacheLoadingState.Loaded;
 715                      label = $"Shaders : {current}/{total}";
 716                      break;
 717                  default:
 718                      throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
 719              }
 720  
 721              Application.Invoke(delegate
 722              {
 723                  _loadingStatusLabel.Text = label;
 724                  _loadingStatusBar.Fraction = total > 0 ? (double)current / total : 0;
 725                  _loadingStatusBar.Visible = visible;
 726                  _loadingStatusLabel.Visible = visible;
 727              });
 728          }
 729  
 730          public void UpdateGameTable()
 731          {
 732              if (_updatingGameTable || _gameLoaded)
 733              {
 734                  return;
 735              }
 736  
 737              _updatingGameTable = true;
 738  
 739              _tableStore.Clear();
 740  
 741              Thread applicationLibraryThread = new(() =>
 742              {
 743                  ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
 744                  ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
 745  
 746                  _updatingGameTable = false;
 747              })
 748              {
 749                  Name = "GUI.ApplicationLibraryThread",
 750                  IsBackground = true,
 751              };
 752              applicationLibraryThread.Start();
 753          }
 754  
 755          [Conditional("RELEASE")]
 756          public void PerformanceCheck()
 757          {
 758              if (ConfigurationState.Instance.Logger.EnableTrace.Value)
 759              {
 760                  MessageDialog debugWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
 761                  {
 762                      Title = "Ryujinx - Warning",
 763                      Text = "You have trace logging enabled, which is designed to be used by developers only.",
 764                      SecondaryText = "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?",
 765                  };
 766  
 767                  if (debugWarningDialog.Run() == (int)ResponseType.Yes)
 768                  {
 769                      ConfigurationState.Instance.Logger.EnableTrace.Value = false;
 770                      SaveConfig();
 771                  }
 772  
 773                  debugWarningDialog.Dispose();
 774              }
 775  
 776              if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
 777              {
 778                  MessageDialog shadersDumpWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
 779                  {
 780                      Title = "Ryujinx - Warning",
 781                      Text = "You have shader dumping enabled, which is designed to be used by developers only.",
 782                      SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?",
 783                  };
 784  
 785                  if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes)
 786                  {
 787                      ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
 788                      SaveConfig();
 789                  }
 790  
 791                  shadersDumpWarningDialog.Dispose();
 792              }
 793          }
 794  
 795          private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
 796          {
 797              SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
 798  
 799              if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
 800              {
 801                  if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
 802                  {
 803                      string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})";
 804  
 805                      ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run();
 806  
 807                      if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _))
 808                      {
 809                          UserErrorDialog.CreateUserErrorDialog(userError);
 810  
 811                          return false;
 812                      }
 813  
 814                      // Tell the user that we installed a firmware for them.
 815  
 816                      firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
 817  
 818                      RefreshFirmwareLabel();
 819  
 820                      message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start.";
 821  
 822                      GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message);
 823                  }
 824                  else
 825                  {
 826                      UserErrorDialog.CreateUserErrorDialog(userError);
 827  
 828                      return false;
 829                  }
 830              }
 831  
 832              Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
 833  
 834              if (isFirmwareTitle)
 835              {
 836                  Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
 837  
 838                  return _emulationContext.LoadNca(path);
 839              }
 840  
 841              if (Directory.Exists(path))
 842              {
 843                  string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
 844  
 845                  if (romFsFiles.Length == 0)
 846                  {
 847                      romFsFiles = Directory.GetFiles(path, "*.romfs");
 848                  }
 849  
 850                  if (romFsFiles.Length > 0)
 851                  {
 852                      Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
 853  
 854                      return _emulationContext.LoadCart(path, romFsFiles[0]);
 855                  }
 856  
 857                  Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
 858  
 859                  return _emulationContext.LoadCart(path);
 860              }
 861  
 862              if (File.Exists(path))
 863              {
 864                  switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
 865                  {
 866                      case ".xci":
 867                          Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
 868  
 869                          return _emulationContext.LoadXci(path, applicationId);
 870                      case ".nca":
 871                          Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
 872  
 873                          return _emulationContext.LoadNca(path);
 874                      case ".nsp":
 875                      case ".pfs0":
 876                          Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
 877  
 878                          return _emulationContext.LoadNsp(path, applicationId);
 879                      default:
 880                          Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
 881                          try
 882                          {
 883                              return _emulationContext.LoadProgram(path);
 884                          }
 885                          catch (ArgumentOutOfRangeException)
 886                          {
 887                              Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
 888  
 889                              return false;
 890                          }
 891                  }
 892              }
 893  
 894              Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
 895  
 896              return false;
 897          }
 898  
 899          public void RunApplication(ApplicationData application, bool startFullscreen = false)
 900          {
 901              if (_gameLoaded)
 902              {
 903                  GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game.");
 904              }
 905              else
 906              {
 907                  PerformanceCheck();
 908  
 909                  Logger.RestartTime();
 910  
 911                  RendererWidget = CreateRendererWidget();
 912  
 913                  SwitchToRenderWidget(startFullscreen);
 914  
 915                  InitializeSwitchInstance();
 916  
 917                  UpdateGraphicsConfig();
 918  
 919                  bool isFirmwareTitle = false;
 920  
 921                  if (application.Path.StartsWith("@SystemContent"))
 922                  {
 923                      application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
 924  
 925                      isFirmwareTitle = true;
 926                  }
 927  
 928                  if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
 929                  {
 930                      _emulationContext.Dispose();
 931                      SwitchToGameTable();
 932  
 933                      return;
 934                  }
 935  
 936                  SetupProgressUIHandlers();
 937  
 938                  _currentApplicationData = application;
 939  
 940                  _deviceExitStatus.Reset();
 941  
 942                  Thread windowThread = new(CreateGameWindow)
 943                  {
 944                      Name = "GUI.WindowThread",
 945                  };
 946  
 947                  windowThread.Start();
 948  
 949                  _gameLoaded = true;
 950                  _actionMenu.Sensitive = true;
 951                  UpdateMenuItem.Sensitive = false;
 952  
 953                  _lastScannedAmiiboId = "";
 954  
 955                  _firmwareInstallFile.Sensitive = false;
 956                  _firmwareInstallDirectory.Sensitive = false;
 957  
 958                  DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText,
 959                                                                _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
 960  
 961                  ApplicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
 962                  {
 963                      appMetadata.UpdatePreGame();
 964                  });
 965              }
 966          }
 967  
 968          private RendererWidgetBase CreateRendererWidget()
 969          {
 970              if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan)
 971              {
 972                  return new VulkanRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
 973              }
 974              else
 975              {
 976                  return new OpenGLRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
 977              }
 978          }
 979  
 980          private void SwitchToRenderWidget(bool startFullscreen = false)
 981          {
 982              _viewBox.Remove(_gameTableWindow);
 983              RendererWidget.Expand = true;
 984              _viewBox.Child = RendererWidget;
 985  
 986              RendererWidget.ShowAll();
 987              EditFooterForGameRenderer();
 988  
 989              if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
 990              {
 991                  ToggleExtraWidgets(false);
 992              }
 993              else if (startFullscreen || ConfigurationState.Instance.UI.StartFullscreen.Value)
 994              {
 995                  FullScreen_Toggled(null, null);
 996              }
 997          }
 998  
 999          private void SwitchToGameTable()
1000          {
1001              if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
1002              {
1003                  ToggleExtraWidgets(true);
1004              }
1005  
1006              RendererWidget.Exit();
1007  
1008              if (RendererWidget.Window != Window && RendererWidget.Window != null)
1009              {
1010                  RendererWidget.Window.Dispose();
1011              }
1012  
1013              RendererWidget.Dispose();
1014  
1015              if (OperatingSystem.IsWindows())
1016              {
1017                  _windowsMultimediaTimerResolution?.Dispose();
1018                  _windowsMultimediaTimerResolution = null;
1019              }
1020  
1021              DisplaySleep.Restore();
1022  
1023              _viewBox.Remove(RendererWidget);
1024              _viewBox.Add(_gameTableWindow);
1025  
1026              _gameTableWindow.Expand = true;
1027  
1028              Window.Title = $"Ryujinx {Program.Version}";
1029  
1030              _emulationContext = null;
1031              _gameLoaded = false;
1032              RendererWidget = null;
1033  
1034              DiscordIntegrationModule.SwitchToMainMenu();
1035  
1036              RecreateFooterForMenu();
1037  
1038              UpdateColumns();
1039              UpdateGameTable();
1040  
1041              RefreshFirmwareLabel();
1042              HandleRelaunch();
1043          }
1044  
1045          private void CreateGameWindow()
1046          {
1047              if (OperatingSystem.IsWindows())
1048              {
1049                  _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
1050              }
1051  
1052              DisplaySleep.Prevent();
1053  
1054              RendererWidget.Initialize(_emulationContext);
1055  
1056              RendererWidget.WaitEvent.WaitOne();
1057  
1058              RendererWidget.Start();
1059  
1060              _emulationContext.Dispose();
1061              _deviceExitStatus.Set();
1062  
1063              // NOTE: Everything that is here will not be executed when you close the UI.
1064              Application.Invoke(delegate
1065              {
1066                  SwitchToGameTable();
1067              });
1068          }
1069  
1070          private void RecreateFooterForMenu()
1071          {
1072              _listStatusBox.Show();
1073              _statusBar.Hide();
1074          }
1075  
1076          private void EditFooterForGameRenderer()
1077          {
1078              _listStatusBox.Hide();
1079              _statusBar.Show();
1080          }
1081  
1082          public void ToggleExtraWidgets(bool show)
1083          {
1084              if (RendererWidget != null)
1085              {
1086                  if (show)
1087                  {
1088                      _menuBar.ShowAll();
1089                      _footerBox.Show();
1090                      _statusBar.Show();
1091                  }
1092                  else
1093                  {
1094                      _menuBar.Hide();
1095                      _footerBox.Hide();
1096                  }
1097              }
1098          }
1099  
1100          private void UpdateGameMetadata(string titleId)
1101          {
1102              if (_gameLoaded)
1103              {
1104                  ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
1105                  {
1106                      appMetadata.UpdatePostGame();
1107                  });
1108              }
1109          }
1110  
1111          public static void UpdateGraphicsConfig()
1112          {
1113              int resScale = ConfigurationState.Instance.Graphics.ResScale;
1114              float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
1115  
1116              Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale;
1117              Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
1118              Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
1119              Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
1120              Graphics.Gpu.GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
1121              Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
1122          }
1123  
1124          public void UpdateInternetAccess()
1125          {
1126              if (_gameLoaded)
1127              {
1128                  _emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value;
1129              }
1130          }
1131  
1132          public static void SaveConfig()
1133          {
1134              ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
1135          }
1136  
1137          private void End()
1138          {
1139              if (_ending)
1140              {
1141                  return;
1142              }
1143  
1144              _ending = true;
1145  
1146              if (_emulationContext != null)
1147              {
1148                  UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
1149  
1150                  if (RendererWidget != null)
1151                  {
1152                      // We tell the widget that we are exiting.
1153                      RendererWidget.Exit();
1154  
1155                      // Wait for the other thread to dispose the HLE context before exiting.
1156                      _deviceExitStatus.WaitOne();
1157                      RendererWidget.Dispose();
1158                  }
1159              }
1160  
1161              Dispose();
1162  
1163              Program.Exit();
1164              Application.Quit();
1165          }
1166  
1167          //
1168          // Events
1169          //
1170          private void Application_Added(object sender, ApplicationAddedEventArgs args)
1171          {
1172              Application.Invoke(delegate
1173              {
1174                  _tableStore.AppendValues(
1175                      args.AppData.Favorite,
1176                      new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
1177                      $"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
1178                      args.AppData.Developer,
1179                      args.AppData.Version,
1180                      args.AppData.TimePlayedString,
1181                      args.AppData.LastPlayedString,
1182                      args.AppData.FileExtension,
1183                      args.AppData.FileSizeString,
1184                      args.AppData.Path,
1185                      args.AppData.ControlHolder);
1186              });
1187          }
1188  
1189          private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args)
1190          {
1191              Application.Invoke(delegate
1192              {
1193                  _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded";
1194                  float barValue = 0;
1195  
1196                  if (args.NumAppsFound != 0)
1197                  {
1198                      barValue = (float)args.NumAppsLoaded / args.NumAppsFound;
1199                  }
1200  
1201                  _progressBar.Fraction = barValue;
1202  
1203                  // Reset the vertical scrollbar to the top when titles finish loading
1204                  if (args.NumAppsLoaded == args.NumAppsFound)
1205                  {
1206                      _gameTableWindow.Vadjustment.Value = 0;
1207                  }
1208              });
1209          }
1210  
1211          private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
1212          {
1213              Application.Invoke(delegate
1214              {
1215                  _gameStatus.Text = args.GameStatus;
1216                  _fifoStatus.Text = args.FifoStatus;
1217                  _gpuName.Text = args.GpuName;
1218                  _dockedMode.Text = args.DockedMode;
1219                  _aspectRatio.Text = args.AspectRatio;
1220                  _gpuBackend.Text = args.GpuBackend;
1221                  _volumeStatus.Text = GetVolumeLabelText(args.Volume);
1222  
1223                  if (args.VSyncEnabled)
1224                  {
1225                      _vSyncStatus.Attributes = new Pango.AttrList();
1226                      _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(11822, 60138, 51657));
1227                  }
1228                  else
1229                  {
1230                      _vSyncStatus.Attributes = new Pango.AttrList();
1231                      _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(ushort.MaxValue, 17733, 21588));
1232                  }
1233              });
1234          }
1235  
1236          private void FavToggle_Toggled(object sender, ToggledArgs args)
1237          {
1238              _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path));
1239  
1240              string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
1241              bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0);
1242  
1243              _tableStore.SetValue(treeIter, 0, newToggleValue);
1244  
1245              ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
1246              {
1247                  appMetadata.Favorite = newToggleValue;
1248              });
1249          }
1250  
1251          private void Column_Clicked(object sender, EventArgs args)
1252          {
1253              TreeViewColumn column = (TreeViewColumn)sender;
1254  
1255              ConfigurationState.Instance.UI.ColumnSort.SortColumnId.Value = column.SortColumnId;
1256              ConfigurationState.Instance.UI.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending;
1257  
1258              SaveConfig();
1259          }
1260  
1261          private void Row_Activated(object sender, RowActivatedArgs args)
1262          {
1263              _gameTableSelection.GetSelected(out TreeIter treeIter);
1264  
1265              ApplicationData application = new()
1266              {
1267                  Favorite = (bool)_tableStore.GetValue(treeIter, 0),
1268                  Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
1269                  Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
1270                  Developer = (string)_tableStore.GetValue(treeIter, 3),
1271                  Version = (string)_tableStore.GetValue(treeIter, 4),
1272                  TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
1273                  LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
1274                  FileExtension = (string)_tableStore.GetValue(treeIter, 7),
1275                  FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
1276                  Path = (string)_tableStore.GetValue(treeIter, 9),
1277                  ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
1278              };
1279  
1280              RunApplication(application);
1281          }
1282  
1283          private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
1284          {
1285              _emulationContext.EnableDeviceVsync = !_emulationContext.EnableDeviceVsync;
1286  
1287              Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {_emulationContext.EnableDeviceVsync}");
1288          }
1289  
1290          private void DockedMode_Clicked(object sender, ButtonReleaseEventArgs args)
1291          {
1292              ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
1293          }
1294  
1295          private static string GetVolumeLabelText(float volume)
1296          {
1297              string icon = volume == 0 ? "🔇" : "🔊";
1298  
1299              return $"{icon} {(int)(volume * 100)}%";
1300          }
1301  
1302          private void VolumeStatus_Clicked(object sender, ButtonReleaseEventArgs args)
1303          {
1304              if (_emulationContext != null)
1305              {
1306                  if (_emulationContext.IsAudioMuted())
1307                  {
1308                      _emulationContext.SetVolume(ConfigurationState.Instance.System.AudioVolume);
1309                  }
1310                  else
1311                  {
1312                      _emulationContext.SetVolume(0);
1313                  }
1314              }
1315          }
1316  
1317          private void AspectRatio_Clicked(object sender, ButtonReleaseEventArgs args)
1318          {
1319              AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
1320  
1321              ConfigurationState.Instance.Graphics.AspectRatio.Value = ((int)aspectRatio + 1) > Enum.GetNames<AspectRatio>().Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
1322          }
1323  
1324          private void Row_Clicked(object sender, ButtonReleaseEventArgs args)
1325          {
1326              if (args.Event.Button != 3 /* Right Click */)
1327              {
1328                  return;
1329              }
1330  
1331              _gameTableSelection.GetSelected(out TreeIter treeIter);
1332  
1333              if (treeIter.UserData == IntPtr.Zero)
1334              {
1335                  return;
1336              }
1337  
1338              ApplicationData application = new()
1339              {
1340                  Favorite = (bool)_tableStore.GetValue(treeIter, 0),
1341                  Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
1342                  Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
1343                  Developer = (string)_tableStore.GetValue(treeIter, 3),
1344                  Version = (string)_tableStore.GetValue(treeIter, 4),
1345                  TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
1346                  LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
1347                  FileExtension = (string)_tableStore.GetValue(treeIter, 7),
1348                  FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
1349                  Path = (string)_tableStore.GetValue(treeIter, 9),
1350                  ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
1351              };
1352  
1353              _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
1354          }
1355  
1356          private void Load_Application_File(object sender, EventArgs args)
1357          {
1358              using FileChooserNative fileChooser = new("Choose the file to open", this, FileChooserAction.Open, "Open", "Cancel");
1359  
1360              FileFilter filter = new()
1361              {
1362                  Name = "Switch Executables",
1363              };
1364              filter.AddPattern("*.xci");
1365              filter.AddPattern("*.nsp");
1366              filter.AddPattern("*.pfs0");
1367              filter.AddPattern("*.nca");
1368              filter.AddPattern("*.nro");
1369              filter.AddPattern("*.nso");
1370  
1371              fileChooser.AddFilter(filter);
1372  
1373              if (fileChooser.Run() == (int)ResponseType.Accept)
1374              {
1375                  if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename,
1376                          out List<ApplicationData> applications))
1377                  {
1378                      RunApplication(applications[0]);
1379                  }
1380                  else
1381                  {
1382                      GtkDialog.CreateErrorDialog("No applications found in selected file.");
1383                  }
1384              }
1385          }
1386  
1387          private void Load_Application_Folder(object sender, EventArgs args)
1388          {
1389              using FileChooserNative fileChooser = new("Choose the folder to open", this, FileChooserAction.SelectFolder, "Open", "Cancel");
1390  
1391              if (fileChooser.Run() == (int)ResponseType.Accept)
1392              {
1393                  ApplicationData applicationData = new()
1394                  {
1395                      Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
1396                      Path = fileChooser.Filename,
1397                  };
1398  
1399                  RunApplication(applicationData);
1400              }
1401          }
1402  
1403          private void FileMenu_StateChanged(object o, StateChangedArgs args)
1404          {
1405              _appletMenu.Sensitive = _emulationContext == null && _contentManager.GetCurrentFirmwareVersion() != null && _contentManager.GetCurrentFirmwareVersion().Major > 3;
1406              _loadApplicationFile.Sensitive = _emulationContext == null;
1407              _loadApplicationFolder.Sensitive = _emulationContext == null;
1408          }
1409  
1410          private void Load_Mii_Edit_Applet(object sender, EventArgs args)
1411          {
1412              string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
1413  
1414              ApplicationData applicationData = new()
1415              {
1416                  Name = "miiEdit",
1417                  Id = 0x0100000000001009ul,
1418                  Path = contentPath,
1419              };
1420  
1421              RunApplication(applicationData);
1422          }
1423  
1424          private void Open_Ryu_Folder(object sender, EventArgs args)
1425          {
1426              OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
1427          }
1428  
1429          private void OpenLogsFolder_Pressed(object sender, EventArgs args)
1430          {
1431              string logPath = AppDataManager.GetOrCreateLogsDir();
1432              if (!string.IsNullOrEmpty(logPath))
1433              {
1434                  OpenHelper.OpenFolder(logPath);
1435              }
1436          }
1437  
1438          private void Exit_Pressed(object sender, EventArgs args)
1439          {
1440              if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
1441              {
1442                  SaveWindowSizePosition();
1443                  End();
1444              }
1445          }
1446  
1447          private void Window_Close(object sender, DeleteEventArgs args)
1448          {
1449              if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
1450              {
1451                  SaveWindowSizePosition();
1452                  End();
1453              }
1454              else
1455              {
1456                  args.RetVal = true;
1457              }
1458          }
1459  
1460          private void SetWindowSizePosition()
1461          {
1462              DefaultWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth;
1463              DefaultHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight;
1464  
1465              Move(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
1466  
1467              if (ConfigurationState.Instance.UI.WindowStartup.WindowMaximized)
1468              {
1469                  Maximize();
1470              }
1471          }
1472  
1473          private void SaveWindowSizePosition()
1474          {
1475              GetSize(out int windowWidth, out int windowHeight);
1476              GetPosition(out int windowXPos, out int windowYPos);
1477  
1478              ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = IsMaximized;
1479              ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = windowWidth;
1480              ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = windowHeight;
1481              ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = windowXPos;
1482              ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = windowYPos;
1483  
1484              SaveConfig();
1485          }
1486  
1487          private void StopEmulation_Pressed(object sender, EventArgs args)
1488          {
1489              if (_emulationContext != null)
1490              {
1491                  UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
1492              }
1493  
1494              _pauseEmulation.Sensitive = false;
1495              _resumeEmulation.Sensitive = false;
1496              UpdateMenuItem.Sensitive = true;
1497              RendererWidget?.Exit();
1498          }
1499  
1500          private void PauseEmulation_Pressed(object sender, EventArgs args)
1501          {
1502              _pauseEmulation.Sensitive = false;
1503              _resumeEmulation.Sensitive = true;
1504              _emulationContext.System.TogglePauseEmulation(true);
1505              Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version, "Paused");
1506              Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
1507          }
1508  
1509          private void ResumeEmulation_Pressed(object sender, EventArgs args)
1510          {
1511              _pauseEmulation.Sensitive = true;
1512              _resumeEmulation.Sensitive = false;
1513              _emulationContext.System.TogglePauseEmulation(false);
1514              Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version);
1515              Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
1516          }
1517  
1518          public void ActivatePauseMenu()
1519          {
1520              _pauseEmulation.Sensitive = true;
1521              _resumeEmulation.Sensitive = false;
1522          }
1523  
1524          public void TogglePause()
1525          {
1526              _pauseEmulation.Sensitive ^= true;
1527              _resumeEmulation.Sensitive ^= true;
1528              _emulationContext.System.TogglePauseEmulation(_resumeEmulation.Sensitive);
1529          }
1530  
1531          private void Installer_File_Pressed(object o, EventArgs args)
1532          {
1533              FileChooserNative fileChooser = new("Choose the firmware file to open", this, FileChooserAction.Open, "Open", "Cancel");
1534  
1535              FileFilter filter = new()
1536              {
1537                  Name = "Switch Firmware Files",
1538              };
1539              filter.AddPattern("*.zip");
1540              filter.AddPattern("*.xci");
1541  
1542              fileChooser.AddFilter(filter);
1543  
1544              HandleInstallerDialog(fileChooser);
1545          }
1546  
1547          private void Installer_Directory_Pressed(object o, EventArgs args)
1548          {
1549              FileChooserNative directoryChooser = new("Choose the firmware directory to open", this, FileChooserAction.SelectFolder, "Open", "Cancel");
1550  
1551              HandleInstallerDialog(directoryChooser);
1552          }
1553  
1554          private void HandleInstallerDialog(FileChooserNative fileChooser)
1555          {
1556              if (fileChooser.Run() == (int)ResponseType.Accept)
1557              {
1558                  try
1559                  {
1560                      string filename = fileChooser.Filename;
1561  
1562                      fileChooser.Dispose();
1563  
1564                      SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
1565  
1566                      if (firmwareVersion is null)
1567                      {
1568                          GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}.");
1569  
1570                          return;
1571                      }
1572  
1573                      string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}";
1574  
1575                      SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion();
1576  
1577                      string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";
1578  
1579                      if (currentVersion != null)
1580                      {
1581                          dialogMessage += $"\n\nThis will replace the current system version {currentVersion.VersionString}. ";
1582                      }
1583  
1584                      dialogMessage += "\n\nDo you want to continue?";
1585  
1586                      ResponseType responseInstallDialog = (ResponseType)GtkDialog.CreateConfirmationDialog(dialogTitle, dialogMessage).Run();
1587  
1588                      MessageDialog waitingDialog = GtkDialog.CreateWaitingDialog(dialogTitle, "Installing firmware...");
1589  
1590                      if (responseInstallDialog == ResponseType.Yes)
1591                      {
1592                          Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
1593  
1594                          Thread thread = new(() =>
1595                          {
1596                              Application.Invoke(delegate
1597                              {
1598                                  waitingDialog.Run();
1599  
1600                              });
1601  
1602                              try
1603                              {
1604                                  _contentManager.InstallFirmware(filename);
1605  
1606                                  Application.Invoke(delegate
1607                                  {
1608                                      waitingDialog.Dispose();
1609  
1610                                      string message = $"System version {firmwareVersion.VersionString} successfully installed.";
1611  
1612                                      GtkDialog.CreateInfoDialog(dialogTitle, message);
1613                                      Logger.Info?.Print(LogClass.Application, message);
1614  
1615                                      // Purge Applet Cache.
1616  
1617                                      DirectoryInfo miiEditorCacheFolder = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
1618  
1619                                      if (miiEditorCacheFolder.Exists)
1620                                      {
1621                                          miiEditorCacheFolder.Delete(true);
1622                                      }
1623                                  });
1624                              }
1625                              catch (Exception ex)
1626                              {
1627                                  Application.Invoke(delegate
1628                                  {
1629                                      waitingDialog.Dispose();
1630  
1631                                      GtkDialog.CreateErrorDialog(ex.Message);
1632                                  });
1633                              }
1634                              finally
1635                              {
1636                                  RefreshFirmwareLabel();
1637                              }
1638                          })
1639                          {
1640                              Name = "GUI.FirmwareInstallerThread",
1641                          };
1642                          thread.Start();
1643                      }
1644                  }
1645                  catch (MissingKeyException ex)
1646                  {
1647                      Logger.Error?.Print(LogClass.Application, ex.ToString());
1648                      UserErrorDialog.CreateUserErrorDialog(UserError.FirmwareParsingFailed);
1649                  }
1650                  catch (Exception ex)
1651                  {
1652                      GtkDialog.CreateErrorDialog(ex.Message);
1653                  }
1654              }
1655              else
1656              {
1657                  fileChooser.Dispose();
1658              }
1659          }
1660  
1661          private void RefreshFirmwareLabel()
1662          {
1663              SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion();
1664  
1665              Application.Invoke(delegate
1666              {
1667                  _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0";
1668              });
1669          }
1670  
1671          private void InstallFileTypes_Pressed(object sender, EventArgs e)
1672          {
1673              if (FileAssociationHelper.Install())
1674              {
1675                  GtkDialog.CreateInfoDialog("Install file types", "File types successfully installed!");
1676              }
1677              else
1678              {
1679                  GtkDialog.CreateErrorDialog("Failed to install file types.");
1680              }
1681          }
1682  
1683          private void UninstallFileTypes_Pressed(object sender, EventArgs e)
1684          {
1685              if (FileAssociationHelper.Uninstall())
1686              {
1687                  GtkDialog.CreateInfoDialog("Uninstall file types", "File types successfully uninstalled!");
1688              }
1689              else
1690              {
1691                  GtkDialog.CreateErrorDialog("Failed to uninstall file types.");
1692              }
1693          }
1694  
1695          private void HandleRelaunch()
1696          {
1697              if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart)
1698              {
1699                  _userChannelPersistence.ShouldRestart = false;
1700  
1701                  RunApplication(_currentApplicationData);
1702              }
1703              else
1704              {
1705                  // otherwise, clear state.
1706                  _userChannelPersistence = new UserChannelPersistence();
1707                  _currentApplicationData = null;
1708                  _actionMenu.Sensitive = false;
1709                  _firmwareInstallFile.Sensitive = true;
1710                  _firmwareInstallDirectory.Sensitive = true;
1711              }
1712          }
1713  
1714          private void FullScreen_Toggled(object sender, EventArgs args)
1715          {
1716              if (!Window.State.HasFlag(Gdk.WindowState.Fullscreen))
1717              {
1718                  Fullscreen();
1719  
1720                  ToggleExtraWidgets(false);
1721              }
1722              else
1723              {
1724                  Unfullscreen();
1725  
1726                  ToggleExtraWidgets(true);
1727              }
1728          }
1729  
1730          private void StartFullScreen_Toggled(object sender, EventArgs args)
1731          {
1732              ConfigurationState.Instance.UI.StartFullscreen.Value = _startFullScreen.Active;
1733  
1734              SaveConfig();
1735          }
1736  
1737          private void ShowConsole_Toggled(object sender, EventArgs args)
1738          {
1739              ConfigurationState.Instance.UI.ShowConsole.Value = _showConsole.Active;
1740  
1741              SaveConfig();
1742          }
1743  
1744          private void OptionMenu_StateChanged(object o, StateChangedArgs args)
1745          {
1746              _manageUserProfiles.Sensitive = _emulationContext == null;
1747          }
1748  
1749          private void Settings_Pressed(object sender, EventArgs args)
1750          {
1751              SettingsWindow settingsWindow = new(this, _virtualFileSystem, _contentManager);
1752  
1753              settingsWindow.SetSizeRequest((int)(settingsWindow.DefaultWidth * Program.WindowScaleFactor), (int)(settingsWindow.DefaultHeight * Program.WindowScaleFactor));
1754              settingsWindow.Show();
1755          }
1756  
1757          private void HideUI_Pressed(object sender, EventArgs args)
1758          {
1759              ToggleExtraWidgets(false);
1760          }
1761  
1762          private void ManageCheats_Pressed(object sender, EventArgs args)
1763          {
1764              var window = new CheatWindow(
1765                  _virtualFileSystem,
1766                  _emulationContext.Processes.ActiveApplication.ProgramId,
1767                  _emulationContext.Processes.ActiveApplication.ApplicationControlProperties
1768                      .Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
1769                  _currentApplicationData.Path);
1770  
1771              window.Destroyed += CheatWindow_Destroyed;
1772              window.Show();
1773          }
1774  
1775          private void CheatWindow_Destroyed(object sender, EventArgs e)
1776          {
1777              _emulationContext.EnableCheats();
1778              (sender as CheatWindow).Destroyed -= CheatWindow_Destroyed;
1779          }
1780  
1781          private void ManageUserProfiles_Pressed(object sender, EventArgs args)
1782          {
1783              UserProfilesManagerWindow userProfilesManagerWindow = new(_accountManager, _contentManager, _virtualFileSystem);
1784  
1785              userProfilesManagerWindow.SetSizeRequest((int)(userProfilesManagerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(userProfilesManagerWindow.DefaultHeight * Program.WindowScaleFactor));
1786              userProfilesManagerWindow.Show();
1787          }
1788  
1789          private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args)
1790          {
1791              _emulationContext?.System.SimulateWakeUpMessage();
1792          }
1793  
1794          private void ActionMenu_StateChanged(object o, StateChangedArgs args)
1795          {
1796              _scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _);
1797              _takeScreenshot.Sensitive = _emulationContext != null;
1798          }
1799  
1800          private void Scan_Amiibo(object sender, EventArgs args)
1801          {
1802              if (_emulationContext.System.SearchingForAmiibo(out int deviceId))
1803              {
1804                  AmiiboWindow amiiboWindow = new()
1805                  {
1806                      LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll,
1807                      LastScannedAmiiboId = _lastScannedAmiiboId,
1808                      DeviceId = deviceId,
1809                      TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper(),
1810                  };
1811  
1812                  amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent;
1813  
1814                  amiiboWindow.Show();
1815              }
1816              else
1817              {
1818                  GtkDialog.CreateInfoDialog($"Amiibo", "The game is currently not ready to receive Amiibo scan data. Ensure that you have an Amiibo-compatible game open and ready to receive Amiibo scan data.");
1819              }
1820          }
1821  
1822          private void Take_Screenshot(object sender, EventArgs args)
1823          {
1824              if (_emulationContext != null && RendererWidget != null)
1825              {
1826                  RendererWidget.ScreenshotRequested = true;
1827              }
1828          }
1829  
1830          private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args)
1831          {
1832              if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok)
1833              {
1834                  _lastScannedAmiiboId = ((AmiiboWindow)sender).AmiiboId;
1835                  _lastScannedAmiiboShowAll = ((AmiiboWindow)sender).LastScannedAmiiboShowAll;
1836  
1837                  _emulationContext.System.ScanAmiibo(((AmiiboWindow)sender).DeviceId, ((AmiiboWindow)sender).AmiiboId, ((AmiiboWindow)sender).UseRandomUuid);
1838              }
1839          }
1840  
1841          private void Update_Pressed(object sender, EventArgs args)
1842          {
1843              if (Updater.CanUpdate(true))
1844              {
1845                  Updater.BeginParse(this, true).ContinueWith(task =>
1846                  {
1847                      Logger.Error?.Print(LogClass.Application, $"Updater error: {task.Exception}");
1848                  }, TaskContinuationOptions.OnlyOnFaulted);
1849              }
1850          }
1851  
1852          private void About_Pressed(object sender, EventArgs args)
1853          {
1854              AboutWindow aboutWindow = new();
1855  
1856              aboutWindow.SetSizeRequest((int)(aboutWindow.DefaultWidth * Program.WindowScaleFactor), (int)(aboutWindow.DefaultHeight * Program.WindowScaleFactor));
1857              aboutWindow.Show();
1858          }
1859  
1860          private void Fav_Toggled(object sender, EventArgs args)
1861          {
1862              ConfigurationState.Instance.UI.GuiColumns.FavColumn.Value = _favToggle.Active;
1863  
1864              SaveConfig();
1865              UpdateColumns();
1866          }
1867  
1868          private void Icon_Toggled(object sender, EventArgs args)
1869          {
1870              ConfigurationState.Instance.UI.GuiColumns.IconColumn.Value = _iconToggle.Active;
1871  
1872              SaveConfig();
1873              UpdateColumns();
1874          }
1875  
1876          private void App_Toggled(object sender, EventArgs args)
1877          {
1878              ConfigurationState.Instance.UI.GuiColumns.AppColumn.Value = _appToggle.Active;
1879  
1880              SaveConfig();
1881              UpdateColumns();
1882          }
1883  
1884          private void Developer_Toggled(object sender, EventArgs args)
1885          {
1886              ConfigurationState.Instance.UI.GuiColumns.DevColumn.Value = _developerToggle.Active;
1887  
1888              SaveConfig();
1889              UpdateColumns();
1890          }
1891  
1892          private void Version_Toggled(object sender, EventArgs args)
1893          {
1894              ConfigurationState.Instance.UI.GuiColumns.VersionColumn.Value = _versionToggle.Active;
1895  
1896              SaveConfig();
1897              UpdateColumns();
1898          }
1899  
1900          private void TimePlayed_Toggled(object sender, EventArgs args)
1901          {
1902              ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active;
1903  
1904              SaveConfig();
1905              UpdateColumns();
1906          }
1907  
1908          private void LastPlayed_Toggled(object sender, EventArgs args)
1909          {
1910              ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active;
1911  
1912              SaveConfig();
1913              UpdateColumns();
1914          }
1915  
1916          private void FileExt_Toggled(object sender, EventArgs args)
1917          {
1918              ConfigurationState.Instance.UI.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active;
1919  
1920              SaveConfig();
1921              UpdateColumns();
1922          }
1923  
1924          private void FileSize_Toggled(object sender, EventArgs args)
1925          {
1926              ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active;
1927  
1928              SaveConfig();
1929              UpdateColumns();
1930          }
1931  
1932          private void Path_Toggled(object sender, EventArgs args)
1933          {
1934              ConfigurationState.Instance.UI.GuiColumns.PathColumn.Value = _pathToggle.Active;
1935  
1936              SaveConfig();
1937              UpdateColumns();
1938          }
1939  
1940          private void NSP_Shown_Toggled(object sender, EventArgs args)
1941          {
1942              ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = _nspShown.Active;
1943  
1944              SaveConfig();
1945              UpdateGameTable();
1946          }
1947  
1948          private void PFS0_Shown_Toggled(object sender, EventArgs args)
1949          {
1950              ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = _pfs0Shown.Active;
1951  
1952              SaveConfig();
1953              UpdateGameTable();
1954          }
1955  
1956          private void XCI_Shown_Toggled(object sender, EventArgs args)
1957          {
1958              ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = _xciShown.Active;
1959  
1960              SaveConfig();
1961              UpdateGameTable();
1962          }
1963  
1964          private void NCA_Shown_Toggled(object sender, EventArgs args)
1965          {
1966              ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = _ncaShown.Active;
1967  
1968              SaveConfig();
1969              UpdateGameTable();
1970          }
1971  
1972          private void NRO_Shown_Toggled(object sender, EventArgs args)
1973          {
1974              ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = _nroShown.Active;
1975  
1976              SaveConfig();
1977              UpdateGameTable();
1978          }
1979  
1980          private void NSO_Shown_Toggled(object sender, EventArgs args)
1981          {
1982              ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = _nsoShown.Active;
1983  
1984              SaveConfig();
1985              UpdateGameTable();
1986          }
1987  
1988          private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
1989          {
1990              UpdateGameTable();
1991          }
1992      }
1993  }