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 }