Program.cs
1 using CommandLine; 2 using LibHac.Tools.FsSystem; 3 using Ryujinx.Audio.Backends.SDL2; 4 using Ryujinx.Common; 5 using Ryujinx.Common.Configuration; 6 using Ryujinx.Common.Configuration.Hid; 7 using Ryujinx.Common.Configuration.Hid.Controller; 8 using Ryujinx.Common.Configuration.Hid.Controller.Motion; 9 using Ryujinx.Common.Configuration.Hid.Keyboard; 10 using Ryujinx.Common.GraphicsDriver; 11 using Ryujinx.Common.Logging; 12 using Ryujinx.Common.Logging.Targets; 13 using Ryujinx.Common.SystemInterop; 14 using Ryujinx.Common.Utilities; 15 using Ryujinx.Cpu; 16 using Ryujinx.Graphics.GAL; 17 using Ryujinx.Graphics.GAL.Multithreading; 18 using Ryujinx.Graphics.Gpu; 19 using Ryujinx.Graphics.Gpu.Shader; 20 using Ryujinx.Graphics.OpenGL; 21 using Ryujinx.Graphics.Vulkan; 22 using Ryujinx.Graphics.Vulkan.MoltenVK; 23 using Ryujinx.Headless.SDL2.OpenGL; 24 using Ryujinx.Headless.SDL2.Vulkan; 25 using Ryujinx.HLE; 26 using Ryujinx.HLE.FileSystem; 27 using Ryujinx.HLE.HOS; 28 using Ryujinx.HLE.HOS.Services.Account.Acc; 29 using Ryujinx.Input; 30 using Ryujinx.Input.HLE; 31 using Ryujinx.Input.SDL2; 32 using Ryujinx.SDL2.Common; 33 using Silk.NET.Vulkan; 34 using System; 35 using System.Collections.Generic; 36 using System.IO; 37 using System.Text.Json; 38 using System.Threading; 39 using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; 40 using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; 41 using Key = Ryujinx.Common.Configuration.Hid.Key; 42 43 namespace Ryujinx.Headless.SDL2 44 { 45 class Program 46 { 47 public static string Version { get; private set; } 48 49 private static VirtualFileSystem _virtualFileSystem; 50 private static ContentManager _contentManager; 51 private static AccountManager _accountManager; 52 private static LibHacHorizonManager _libHacHorizonManager; 53 private static UserChannelPersistence _userChannelPersistence; 54 private static InputManager _inputManager; 55 private static Switch _emulationContext; 56 private static WindowBase _window; 57 private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; 58 private static List<InputConfig> _inputConfiguration; 59 private static bool _enableKeyboard; 60 private static bool _enableMouse; 61 62 private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); 63 64 static void Main(string[] args) 65 { 66 Version = ReleaseInformation.Version; 67 68 // Make process DPI aware for proper window sizing on high-res screens. 69 ForceDpiAware.Windows(); 70 71 Console.Title = $"Ryujinx Console {Version} (Headless SDL2)"; 72 73 if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) 74 { 75 AutoResetEvent invoked = new(false); 76 77 // MacOS must perform SDL polls from the main thread. 78 SDL2Driver.MainThreadDispatcher = action => 79 { 80 invoked.Reset(); 81 82 WindowBase.QueueMainThreadAction(() => 83 { 84 action(); 85 86 invoked.Set(); 87 }); 88 89 invoked.WaitOne(); 90 }; 91 } 92 93 if (OperatingSystem.IsMacOS()) 94 { 95 MVKInitialization.InitializeResolver(); 96 } 97 98 Parser.Default.ParseArguments<Options>(args) 99 .WithParsed(Load) 100 .WithNotParsed(errors => errors.Output()); 101 } 102 103 private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) 104 { 105 if (inputId == null) 106 { 107 if (index == PlayerIndex.Player1) 108 { 109 Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard."); 110 111 // Default to keyboard 112 inputId = "0"; 113 } 114 else 115 { 116 Logger.Info?.Print(LogClass.Application, $"{index} not configured"); 117 118 return null; 119 } 120 } 121 122 IGamepad gamepad; 123 124 bool isKeyboard = true; 125 126 gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId); 127 128 if (gamepad == null) 129 { 130 gamepad = _inputManager.GamepadDriver.GetGamepad(inputId); 131 isKeyboard = false; 132 133 if (gamepad == null) 134 { 135 Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")"); 136 137 return null; 138 } 139 } 140 141 string gamepadName = gamepad.Name; 142 143 gamepad.Dispose(); 144 145 InputConfig config; 146 147 if (inputProfileName == null || inputProfileName.Equals("default")) 148 { 149 if (isKeyboard) 150 { 151 config = new StandardKeyboardInputConfig 152 { 153 Version = InputConfig.CurrentVersion, 154 Backend = InputBackendType.WindowKeyboard, 155 Id = null, 156 ControllerType = ControllerType.JoyconPair, 157 LeftJoycon = new LeftJoyconCommonConfig<Key> 158 { 159 DpadUp = Key.Up, 160 DpadDown = Key.Down, 161 DpadLeft = Key.Left, 162 DpadRight = Key.Right, 163 ButtonMinus = Key.Minus, 164 ButtonL = Key.E, 165 ButtonZl = Key.Q, 166 ButtonSl = Key.Unbound, 167 ButtonSr = Key.Unbound, 168 }, 169 170 LeftJoyconStick = new JoyconConfigKeyboardStick<Key> 171 { 172 StickUp = Key.W, 173 StickDown = Key.S, 174 StickLeft = Key.A, 175 StickRight = Key.D, 176 StickButton = Key.F, 177 }, 178 179 RightJoycon = new RightJoyconCommonConfig<Key> 180 { 181 ButtonA = Key.Z, 182 ButtonB = Key.X, 183 ButtonX = Key.C, 184 ButtonY = Key.V, 185 ButtonPlus = Key.Plus, 186 ButtonR = Key.U, 187 ButtonZr = Key.O, 188 ButtonSl = Key.Unbound, 189 ButtonSr = Key.Unbound, 190 }, 191 192 RightJoyconStick = new JoyconConfigKeyboardStick<Key> 193 { 194 StickUp = Key.I, 195 StickDown = Key.K, 196 StickLeft = Key.J, 197 StickRight = Key.L, 198 StickButton = Key.H, 199 }, 200 }; 201 } 202 else 203 { 204 bool isNintendoStyle = gamepadName.Contains("Nintendo"); 205 206 config = new StandardControllerInputConfig 207 { 208 Version = InputConfig.CurrentVersion, 209 Backend = InputBackendType.GamepadSDL2, 210 Id = null, 211 ControllerType = ControllerType.JoyconPair, 212 DeadzoneLeft = 0.1f, 213 DeadzoneRight = 0.1f, 214 RangeLeft = 1.0f, 215 RangeRight = 1.0f, 216 TriggerThreshold = 0.5f, 217 LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> 218 { 219 DpadUp = ConfigGamepadInputId.DpadUp, 220 DpadDown = ConfigGamepadInputId.DpadDown, 221 DpadLeft = ConfigGamepadInputId.DpadLeft, 222 DpadRight = ConfigGamepadInputId.DpadRight, 223 ButtonMinus = ConfigGamepadInputId.Minus, 224 ButtonL = ConfigGamepadInputId.LeftShoulder, 225 ButtonZl = ConfigGamepadInputId.LeftTrigger, 226 ButtonSl = ConfigGamepadInputId.Unbound, 227 ButtonSr = ConfigGamepadInputId.Unbound, 228 }, 229 230 LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> 231 { 232 Joystick = ConfigStickInputId.Left, 233 StickButton = ConfigGamepadInputId.LeftStick, 234 InvertStickX = false, 235 InvertStickY = false, 236 Rotate90CW = false, 237 }, 238 239 RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> 240 { 241 ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, 242 ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, 243 ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, 244 ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, 245 ButtonPlus = ConfigGamepadInputId.Plus, 246 ButtonR = ConfigGamepadInputId.RightShoulder, 247 ButtonZr = ConfigGamepadInputId.RightTrigger, 248 ButtonSl = ConfigGamepadInputId.Unbound, 249 ButtonSr = ConfigGamepadInputId.Unbound, 250 }, 251 252 RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> 253 { 254 Joystick = ConfigStickInputId.Right, 255 StickButton = ConfigGamepadInputId.RightStick, 256 InvertStickX = false, 257 InvertStickY = false, 258 Rotate90CW = false, 259 }, 260 261 Motion = new StandardMotionConfigController 262 { 263 MotionBackend = MotionInputBackendType.GamepadDriver, 264 EnableMotion = true, 265 Sensitivity = 100, 266 GyroDeadzone = 1, 267 }, 268 Rumble = new RumbleConfigController 269 { 270 StrongRumble = 1f, 271 WeakRumble = 1f, 272 EnableRumble = false, 273 }, 274 }; 275 } 276 } 277 else 278 { 279 string profileBasePath; 280 281 if (isKeyboard) 282 { 283 profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); 284 } 285 else 286 { 287 profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller"); 288 } 289 290 string path = Path.Combine(profileBasePath, inputProfileName + ".json"); 291 292 if (!File.Exists(path)) 293 { 294 Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\""); 295 296 return null; 297 } 298 299 try 300 { 301 config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); 302 } 303 catch (JsonException) 304 { 305 Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\""); 306 307 return null; 308 } 309 } 310 311 config.Id = inputId; 312 config.PlayerIndex = index; 313 314 string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad"; 315 316 Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\""); 317 318 // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. 319 if (config is StandardControllerInputConfig controllerConfig) 320 { 321 if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f) 322 { 323 controllerConfig.RangeLeft = 1.0f; 324 controllerConfig.RangeRight = 1.0f; 325 326 Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); 327 } 328 } 329 330 return config; 331 } 332 333 static void Load(Options option) 334 { 335 AppDataManager.Initialize(option.BaseDataDir); 336 337 _virtualFileSystem = VirtualFileSystem.CreateInstance(); 338 _libHacHorizonManager = new LibHacHorizonManager(); 339 340 _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); 341 _libHacHorizonManager.InitializeArpServer(); 342 _libHacHorizonManager.InitializeBcatServer(); 343 _libHacHorizonManager.InitializeSystemClients(); 344 345 _contentManager = new ContentManager(_virtualFileSystem); 346 _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile); 347 _userChannelPersistence = new UserChannelPersistence(); 348 349 _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); 350 351 GraphicsConfig.EnableShaderCache = true; 352 353 if (OperatingSystem.IsMacOS()) 354 { 355 if (option.GraphicsBackend == GraphicsBackend.OpenGl) 356 { 357 option.GraphicsBackend = GraphicsBackend.Vulkan; 358 Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!"); 359 } 360 } 361 362 IGamepad gamepad; 363 364 if (option.ListInputIds) 365 { 366 Logger.Info?.Print(LogClass.Application, "Input Ids:"); 367 368 foreach (string id in _inputManager.KeyboardDriver.GamepadsIds) 369 { 370 gamepad = _inputManager.KeyboardDriver.GetGamepad(id); 371 372 Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")"); 373 374 gamepad.Dispose(); 375 } 376 377 foreach (string id in _inputManager.GamepadDriver.GamepadsIds) 378 { 379 gamepad = _inputManager.GamepadDriver.GetGamepad(id); 380 381 Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")"); 382 383 gamepad.Dispose(); 384 } 385 386 return; 387 } 388 389 if (option.InputPath == null) 390 { 391 Logger.Error?.Print(LogClass.Application, "Please provide a file to load"); 392 393 return; 394 } 395 396 _inputConfiguration = new List<InputConfig>(); 397 _enableKeyboard = option.EnableKeyboard; 398 _enableMouse = option.EnableMouse; 399 400 static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) 401 { 402 InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index); 403 404 if (inputConfig != null) 405 { 406 _inputConfiguration.Add(inputConfig); 407 } 408 } 409 410 LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); 411 LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); 412 LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3); 413 LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4); 414 LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5); 415 LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6); 416 LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7); 417 LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); 418 LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); 419 420 if (_inputConfiguration.Count == 0) 421 { 422 return; 423 } 424 425 // Setup logging level 426 Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug); 427 Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub); 428 Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo); 429 Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning); 430 Logger.SetEnable(LogLevel.Error, option.LoggingEnableError); 431 Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace); 432 Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest); 433 Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog); 434 435 if (!option.DisableFileLog) 436 { 437 string logDir = AppDataManager.LogsDirPath; 438 FileStream logFile = null; 439 440 if (!string.IsNullOrEmpty(logDir)) 441 { 442 logFile = FileLogTarget.PrepareLogFile(logDir); 443 } 444 445 if (logFile != null) 446 { 447 Logger.AddTarget(new AsyncLogTargetWrapper( 448 new FileLogTarget("file", logFile), 449 1000, 450 AsyncLogTargetOverflowAction.Block 451 )); 452 } 453 else 454 { 455 Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable."); 456 } 457 } 458 459 // Setup graphics configuration 460 GraphicsConfig.EnableShaderCache = !option.DisableShaderCache; 461 GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression; 462 GraphicsConfig.ResScale = option.ResScale; 463 GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy; 464 GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath; 465 GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE; 466 467 DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off); 468 469 while (true) 470 { 471 LoadApplication(option); 472 473 if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart) 474 { 475 break; 476 } 477 478 _userChannelPersistence.ShouldRestart = false; 479 } 480 481 _inputManager.Dispose(); 482 } 483 484 private static void SetupProgressHandler() 485 { 486 if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) 487 { 488 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; 489 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; 490 } 491 492 _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; 493 _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; 494 } 495 496 private static void ProgressHandler<T>(T state, int current, int total) where T : Enum 497 { 498 string label = state switch 499 { 500 LoadState => $"PTC : {current}/{total}", 501 ShaderCacheState => $"Shaders : {current}/{total}", 502 _ => throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"), 503 }; 504 505 Logger.Info?.Print(LogClass.Application, label); 506 } 507 508 private static WindowBase CreateWindow(Options options) 509 { 510 return options.GraphicsBackend == GraphicsBackend.Vulkan 511 ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode) 512 : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode); 513 } 514 515 private static IRenderer CreateRenderer(Options options, WindowBase window) 516 { 517 if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow) 518 { 519 string preferredGpuId = string.Empty; 520 Vk api = Vk.GetApi(); 521 522 if (!string.IsNullOrEmpty(options.PreferredGPUVendor)) 523 { 524 string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant(); 525 var devices = VulkanRenderer.GetPhysicalDevices(api); 526 527 foreach (var device in devices) 528 { 529 if (device.Vendor.ToLowerInvariant() == preferredGpuVendor) 530 { 531 preferredGpuId = device.Id; 532 break; 533 } 534 } 535 } 536 537 return new VulkanRenderer( 538 api, 539 (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), 540 vulkanWindow.GetRequiredInstanceExtensions, 541 preferredGpuId); 542 } 543 544 return new OpenGLRenderer(); 545 } 546 547 private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) 548 { 549 BackendThreading threadingMode = options.BackendThreading; 550 551 bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); 552 553 if (threadedGAL) 554 { 555 renderer = new ThreadedRenderer(renderer); 556 } 557 558 HLEConfiguration configuration = new(_virtualFileSystem, 559 _libHacHorizonManager, 560 _contentManager, 561 _accountManager, 562 _userChannelPersistence, 563 renderer, 564 new SDL2HardwareDeviceDriver(), 565 options.ExpandRAM ? MemoryConfiguration.MemoryConfiguration8GiB : MemoryConfiguration.MemoryConfiguration4GiB, 566 window, 567 options.SystemLanguage, 568 options.SystemRegion, 569 !options.DisableVSync, 570 !options.DisableDockedMode, 571 !options.DisablePTC, 572 options.EnableInternetAccess, 573 !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, 574 options.FsGlobalAccessLogMode, 575 options.SystemTimeOffset, 576 options.SystemTimeZone, 577 options.MemoryManagerMode, 578 options.IgnoreMissingServices, 579 options.AspectRatio, 580 options.AudioVolume, 581 options.UseHypervisor ?? true, 582 options.MultiplayerLanInterfaceId, 583 Common.Configuration.Multiplayer.MultiplayerMode.Disabled); 584 585 return new Switch(configuration); 586 } 587 588 private static void ExecutionEntrypoint() 589 { 590 if (OperatingSystem.IsWindows()) 591 { 592 _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); 593 } 594 595 DisplaySleep.Prevent(); 596 597 _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse); 598 599 _window.Execute(); 600 601 _emulationContext.Dispose(); 602 _window.Dispose(); 603 604 if (OperatingSystem.IsWindows()) 605 { 606 _windowsMultimediaTimerResolution?.Dispose(); 607 _windowsMultimediaTimerResolution = null; 608 } 609 } 610 611 private static bool LoadApplication(Options options) 612 { 613 string path = options.InputPath; 614 615 Logger.RestartTime(); 616 617 WindowBase window = CreateWindow(options); 618 IRenderer renderer = CreateRenderer(options, window); 619 620 _window = window; 621 622 _window.IsFullscreen = options.IsFullscreen; 623 _window.DisplayId = options.DisplayId; 624 _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; 625 _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; 626 _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; 627 _window.AntiAliasing = options.AntiAliasing; 628 _window.ScalingFilter = options.ScalingFilter; 629 _window.ScalingFilterLevel = options.ScalingFilterLevel; 630 631 _emulationContext = InitializeEmulationContext(window, renderer, options); 632 633 SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); 634 635 Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); 636 637 if (Directory.Exists(path)) 638 { 639 string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); 640 641 if (romFsFiles.Length == 0) 642 { 643 romFsFiles = Directory.GetFiles(path, "*.romfs"); 644 } 645 646 if (romFsFiles.Length > 0) 647 { 648 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); 649 650 if (!_emulationContext.LoadCart(path, romFsFiles[0])) 651 { 652 _emulationContext.Dispose(); 653 654 return false; 655 } 656 } 657 else 658 { 659 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); 660 661 if (!_emulationContext.LoadCart(path)) 662 { 663 _emulationContext.Dispose(); 664 665 return false; 666 } 667 } 668 } 669 else if (File.Exists(path)) 670 { 671 switch (Path.GetExtension(path).ToLowerInvariant()) 672 { 673 case ".xci": 674 Logger.Info?.Print(LogClass.Application, "Loading as XCI."); 675 676 if (!_emulationContext.LoadXci(path)) 677 { 678 _emulationContext.Dispose(); 679 680 return false; 681 } 682 break; 683 case ".nca": 684 Logger.Info?.Print(LogClass.Application, "Loading as NCA."); 685 686 if (!_emulationContext.LoadNca(path)) 687 { 688 _emulationContext.Dispose(); 689 690 return false; 691 } 692 break; 693 case ".nsp": 694 case ".pfs0": 695 Logger.Info?.Print(LogClass.Application, "Loading as NSP."); 696 697 if (!_emulationContext.LoadNsp(path)) 698 { 699 _emulationContext.Dispose(); 700 701 return false; 702 } 703 break; 704 default: 705 Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); 706 try 707 { 708 if (!_emulationContext.LoadProgram(path)) 709 { 710 _emulationContext.Dispose(); 711 712 return false; 713 } 714 } 715 catch (ArgumentOutOfRangeException) 716 { 717 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); 718 719 _emulationContext.Dispose(); 720 721 return false; 722 } 723 break; 724 } 725 } 726 else 727 { 728 Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); 729 730 _emulationContext.Dispose(); 731 732 return false; 733 } 734 735 SetupProgressHandler(); 736 ExecutionEntrypoint(); 737 738 return true; 739 } 740 } 741 }