|
|
@@ -0,0 +1,561 @@
|
|
|
+using ARMeilleure.Translation;
|
|
|
+using ARMeilleure.Translation.PTC;
|
|
|
+using CommandLine;
|
|
|
+using Ryujinx.Audio.Backends.SDL2;
|
|
|
+using Ryujinx.Common.Configuration;
|
|
|
+using Ryujinx.Common.Configuration.Hid;
|
|
|
+using Ryujinx.Common.Configuration.Hid.Controller;
|
|
|
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
|
|
+using Ryujinx.Common.Configuration.Hid.Keyboard;
|
|
|
+using Ryujinx.Common.Logging;
|
|
|
+using Ryujinx.Common.System;
|
|
|
+using Ryujinx.Common.Utilities;
|
|
|
+using Ryujinx.Graphics.Gpu;
|
|
|
+using Ryujinx.Graphics.Gpu.Shader;
|
|
|
+using Ryujinx.Graphics.OpenGL;
|
|
|
+using Ryujinx.Headless.SDL2.OpenGL;
|
|
|
+using Ryujinx.HLE;
|
|
|
+using Ryujinx.HLE.FileSystem;
|
|
|
+using Ryujinx.HLE.FileSystem.Content;
|
|
|
+using Ryujinx.HLE.HOS;
|
|
|
+using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
|
+using Ryujinx.Input;
|
|
|
+using Ryujinx.Input.HLE;
|
|
|
+using Ryujinx.Input.SDL2;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.IO;
|
|
|
+using System.Reflection;
|
|
|
+using System.Runtime.InteropServices;
|
|
|
+using System.Text.Json;
|
|
|
+using System.Threading;
|
|
|
+
|
|
|
+using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
|
|
+using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
|
|
+using Key = Ryujinx.Common.Configuration.Hid.Key;
|
|
|
+
|
|
|
+namespace Ryujinx.Headless.SDL2
|
|
|
+{
|
|
|
+ class Program
|
|
|
+ {
|
|
|
+ public static string Version { get; private set; }
|
|
|
+
|
|
|
+ private static VirtualFileSystem _virtualFileSystem;
|
|
|
+ private static ContentManager _contentManager;
|
|
|
+ private static AccountManager _accountManager;
|
|
|
+ private static UserChannelPersistence _userChannelPersistence;
|
|
|
+ private static InputManager _inputManager;
|
|
|
+ private static Switch _emulationContext;
|
|
|
+ private static WindowBase _window;
|
|
|
+ private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
|
|
+ private static List<InputConfig> _inputConfiguration;
|
|
|
+ private static bool _enableKeyboard;
|
|
|
+ private static bool _enableMouse;
|
|
|
+
|
|
|
+ static void Main(string[] args)
|
|
|
+ {
|
|
|
+ Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
|
|
+
|
|
|
+ Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
|
|
|
+
|
|
|
+ AppDataManager.Initialize(null);
|
|
|
+
|
|
|
+ _virtualFileSystem = VirtualFileSystem.CreateInstance();
|
|
|
+ _contentManager = new ContentManager(_virtualFileSystem);
|
|
|
+ _accountManager = new AccountManager(_virtualFileSystem);
|
|
|
+ _userChannelPersistence = new UserChannelPersistence();
|
|
|
+
|
|
|
+ _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
|
|
+
|
|
|
+ GraphicsConfig.EnableShaderCache = true;
|
|
|
+
|
|
|
+ Parser.Default.ParseArguments<Options>(args)
|
|
|
+ .WithParsed(options => Load(options))
|
|
|
+ .WithNotParsed(errors => errors.Output());
|
|
|
+
|
|
|
+ _inputManager.Dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
|
|
|
+ {
|
|
|
+ if (inputId == null)
|
|
|
+ {
|
|
|
+ if (index == PlayerIndex.Player1)
|
|
|
+ {
|
|
|
+ Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
|
|
|
+
|
|
|
+ // Default to keyboard
|
|
|
+ inputId = "0";
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.Info?.Print(LogClass.Application, $"{index} not configured");
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ IGamepad gamepad;
|
|
|
+
|
|
|
+ bool isKeyboard = true;
|
|
|
+
|
|
|
+ gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
|
|
|
+
|
|
|
+ if (gamepad == null)
|
|
|
+ {
|
|
|
+ gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
|
|
|
+ isKeyboard = false;
|
|
|
+
|
|
|
+ if (gamepad == null)
|
|
|
+ {
|
|
|
+ Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ string gamepadName = gamepad.Name;
|
|
|
+
|
|
|
+ gamepad.Dispose();
|
|
|
+
|
|
|
+ InputConfig config;
|
|
|
+
|
|
|
+ if (inputProfileName == null || inputProfileName.Equals("default"))
|
|
|
+ {
|
|
|
+ if (isKeyboard)
|
|
|
+ {
|
|
|
+ config = new StandardKeyboardInputConfig
|
|
|
+ {
|
|
|
+ Version = InputConfig.CurrentVersion,
|
|
|
+ Backend = InputBackendType.WindowKeyboard,
|
|
|
+ Id = null,
|
|
|
+ ControllerType = ControllerType.JoyconPair,
|
|
|
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
|
|
|
+ {
|
|
|
+ DpadUp = Key.Up,
|
|
|
+ DpadDown = Key.Down,
|
|
|
+ DpadLeft = Key.Left,
|
|
|
+ DpadRight = Key.Right,
|
|
|
+ ButtonMinus = Key.Minus,
|
|
|
+ ButtonL = Key.E,
|
|
|
+ ButtonZl = Key.Q,
|
|
|
+ ButtonSl = Key.Unbound,
|
|
|
+ ButtonSr = Key.Unbound
|
|
|
+ },
|
|
|
+
|
|
|
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
|
|
+ {
|
|
|
+ StickUp = Key.W,
|
|
|
+ StickDown = Key.S,
|
|
|
+ StickLeft = Key.A,
|
|
|
+ StickRight = Key.D,
|
|
|
+ StickButton = Key.F,
|
|
|
+ },
|
|
|
+
|
|
|
+ RightJoycon = new RightJoyconCommonConfig<Key>
|
|
|
+ {
|
|
|
+ ButtonA = Key.Z,
|
|
|
+ ButtonB = Key.X,
|
|
|
+ ButtonX = Key.C,
|
|
|
+ ButtonY = Key.V,
|
|
|
+ ButtonPlus = Key.Plus,
|
|
|
+ ButtonR = Key.U,
|
|
|
+ ButtonZr = Key.O,
|
|
|
+ ButtonSl = Key.Unbound,
|
|
|
+ ButtonSr = Key.Unbound
|
|
|
+ },
|
|
|
+
|
|
|
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
|
|
+ {
|
|
|
+ StickUp = Key.I,
|
|
|
+ StickDown = Key.K,
|
|
|
+ StickLeft = Key.J,
|
|
|
+ StickRight = Key.L,
|
|
|
+ StickButton = Key.H,
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ bool isNintendoStyle = gamepadName.Contains("Nintendo");
|
|
|
+
|
|
|
+ config = new StandardControllerInputConfig
|
|
|
+ {
|
|
|
+ Version = InputConfig.CurrentVersion,
|
|
|
+ Backend = InputBackendType.GamepadSDL2,
|
|
|
+ Id = null,
|
|
|
+ ControllerType = ControllerType.JoyconPair,
|
|
|
+ DeadzoneLeft = 0.1f,
|
|
|
+ DeadzoneRight = 0.1f,
|
|
|
+ TriggerThreshold = 0.5f,
|
|
|
+ LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
|
|
+ {
|
|
|
+ DpadUp = ConfigGamepadInputId.DpadUp,
|
|
|
+ DpadDown = ConfigGamepadInputId.DpadDown,
|
|
|
+ DpadLeft = ConfigGamepadInputId.DpadLeft,
|
|
|
+ DpadRight = ConfigGamepadInputId.DpadRight,
|
|
|
+ ButtonMinus = ConfigGamepadInputId.Minus,
|
|
|
+ ButtonL = ConfigGamepadInputId.LeftShoulder,
|
|
|
+ ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
|
|
+ ButtonSl = ConfigGamepadInputId.Unbound,
|
|
|
+ ButtonSr = ConfigGamepadInputId.Unbound,
|
|
|
+ },
|
|
|
+
|
|
|
+ LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
|
|
+ {
|
|
|
+ Joystick = ConfigStickInputId.Left,
|
|
|
+ StickButton = ConfigGamepadInputId.LeftStick,
|
|
|
+ InvertStickX = false,
|
|
|
+ InvertStickY = false,
|
|
|
+ },
|
|
|
+
|
|
|
+ RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
|
|
+ {
|
|
|
+ ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
|
|
|
+ ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
|
|
|
+ ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
|
|
|
+ ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
|
|
|
+ ButtonPlus = ConfigGamepadInputId.Plus,
|
|
|
+ ButtonR = ConfigGamepadInputId.RightShoulder,
|
|
|
+ ButtonZr = ConfigGamepadInputId.RightTrigger,
|
|
|
+ ButtonSl = ConfigGamepadInputId.Unbound,
|
|
|
+ ButtonSr = ConfigGamepadInputId.Unbound,
|
|
|
+ },
|
|
|
+
|
|
|
+ RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
|
|
+ {
|
|
|
+ Joystick = ConfigStickInputId.Right,
|
|
|
+ StickButton = ConfigGamepadInputId.RightStick,
|
|
|
+ InvertStickX = false,
|
|
|
+ InvertStickY = false,
|
|
|
+ },
|
|
|
+
|
|
|
+ Motion = new StandardMotionConfigController
|
|
|
+ {
|
|
|
+ MotionBackend = MotionInputBackendType.GamepadDriver,
|
|
|
+ EnableMotion = true,
|
|
|
+ Sensitivity = 100,
|
|
|
+ GyroDeadzone = 1,
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ string profileBasePath;
|
|
|
+
|
|
|
+ if (isKeyboard)
|
|
|
+ {
|
|
|
+ profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
|
|
|
+ }
|
|
|
+
|
|
|
+ string path = Path.Combine(profileBasePath, inputProfileName + ".json");
|
|
|
+
|
|
|
+ if (!File.Exists(path))
|
|
|
+ {
|
|
|
+ Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using (Stream stream = File.OpenRead(path))
|
|
|
+ {
|
|
|
+ config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (JsonException)
|
|
|
+ {
|
|
|
+ Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ config.Id = inputId;
|
|
|
+ config.PlayerIndex = index;
|
|
|
+
|
|
|
+ string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
|
|
|
+
|
|
|
+ Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
|
|
|
+
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ static void Load(Options option)
|
|
|
+ {
|
|
|
+ IGamepad gamepad;
|
|
|
+
|
|
|
+ if (option.ListInputIds)
|
|
|
+ {
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Input Ids:");
|
|
|
+
|
|
|
+ foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
|
|
|
+ {
|
|
|
+ gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
|
|
|
+
|
|
|
+ Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
|
|
|
+
|
|
|
+ gamepad.Dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
|
|
|
+ {
|
|
|
+ gamepad = _inputManager.GamepadDriver.GetGamepad(id);
|
|
|
+
|
|
|
+ Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
|
|
|
+
|
|
|
+ gamepad.Dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (option.InputPath == null)
|
|
|
+ {
|
|
|
+ Logger.Error?.Print(LogClass.Application, "Please provide a file to load");
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _inputConfiguration = new List<InputConfig>();
|
|
|
+ _enableKeyboard = (bool)option.EnableKeyboard;
|
|
|
+ _enableMouse = (bool)option.EnableMouse;
|
|
|
+
|
|
|
+ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
|
|
|
+ {
|
|
|
+ InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index);
|
|
|
+
|
|
|
+ if (inputConfig != null)
|
|
|
+ {
|
|
|
+ _inputConfiguration.Add(inputConfig);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7);
|
|
|
+ LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
|
|
|
+ LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
|
|
|
+
|
|
|
+ if (_inputConfiguration.Count == 0)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Setup logging level
|
|
|
+ Logger.SetEnable(LogLevel.Debug, (bool)option.LoggingEnableDebug);
|
|
|
+ Logger.SetEnable(LogLevel.Stub, (bool)option.LoggingEnableStub);
|
|
|
+ Logger.SetEnable(LogLevel.Info, (bool)option.LoggingEnableInfo);
|
|
|
+ Logger.SetEnable(LogLevel.Warning, (bool)option.LoggingEnableWarning);
|
|
|
+ Logger.SetEnable(LogLevel.Error, (bool)option.LoggingEnableError);
|
|
|
+ Logger.SetEnable(LogLevel.Guest, (bool)option.LoggingEnableGuest);
|
|
|
+ Logger.SetEnable(LogLevel.AccessLog, (bool)option.LoggingEnableFsAccessLog);
|
|
|
+
|
|
|
+ if ((bool)option.EnableFileLog)
|
|
|
+ {
|
|
|
+ Logger.AddTarget(new AsyncLogTargetWrapper(
|
|
|
+ new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
|
|
|
+ 1000,
|
|
|
+ AsyncLogTargetOverflowAction.Block
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Setup graphics configuration
|
|
|
+ GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache;
|
|
|
+ GraphicsConfig.ResScale = option.ResScale;
|
|
|
+ GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
|
|
|
+ GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ LoadApplication(option);
|
|
|
+
|
|
|
+ if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ _userChannelPersistence.ShouldRestart = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void SetupProgressHandler()
|
|
|
+ {
|
|
|
+ Ptc.PtcStateChanged -= ProgressHandler;
|
|
|
+ Ptc.PtcStateChanged += ProgressHandler;
|
|
|
+
|
|
|
+ _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
|
|
+ _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ProgressHandler<T>(T state, int current, int total) where T : Enum
|
|
|
+ {
|
|
|
+ string label;
|
|
|
+
|
|
|
+ switch (state)
|
|
|
+ {
|
|
|
+ case PtcLoadingState ptcState:
|
|
|
+ label = $"PTC : {current}/{total}";
|
|
|
+ break;
|
|
|
+ case ShaderCacheState shaderCacheState:
|
|
|
+ label = $"Shaders : {current}/{total}";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.Info?.Print(LogClass.Application, label);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Switch InitializeEmulationContext(WindowBase window, Options options)
|
|
|
+ {
|
|
|
+ HLEConfiguration configuration = new HLEConfiguration(_virtualFileSystem,
|
|
|
+ _contentManager,
|
|
|
+ _accountManager,
|
|
|
+ _userChannelPersistence,
|
|
|
+ new Renderer(),
|
|
|
+ new SDL2HardwareDeviceDriver(),
|
|
|
+ (bool)options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GB : MemoryConfiguration.MemoryConfiguration4GB,
|
|
|
+ window,
|
|
|
+ options.SystemLanguage,
|
|
|
+ options.SystemRegion,
|
|
|
+ (bool)options.EnableVsync,
|
|
|
+ (bool)options.EnableDockedMode,
|
|
|
+ (bool)options.EnablePtc,
|
|
|
+ (bool)options.EnableFsIntegrityChecks ? LibHac.FsSystem.IntegrityCheckLevel.ErrorOnInvalid : LibHac.FsSystem.IntegrityCheckLevel.None,
|
|
|
+ options.FsGlobalAccessLogMode,
|
|
|
+ options.SystemTimeOffset,
|
|
|
+ options.SystemTimeZone,
|
|
|
+ options.MemoryManagerMode,
|
|
|
+ (bool)options.IgnoreMissingServices,
|
|
|
+ options.AspectRatio);
|
|
|
+
|
|
|
+ return new Switch(configuration);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ExecutionEntrypoint()
|
|
|
+ {
|
|
|
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
+ {
|
|
|
+ _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ DisplaySleep.Prevent();
|
|
|
+
|
|
|
+ _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
|
|
|
+
|
|
|
+ _window.Execute();
|
|
|
+
|
|
|
+ Ptc.Close();
|
|
|
+ PtcProfiler.Stop();
|
|
|
+
|
|
|
+ _emulationContext.Dispose();
|
|
|
+ _window.Dispose();
|
|
|
+
|
|
|
+ _windowsMultimediaTimerResolution?.Dispose();
|
|
|
+ _windowsMultimediaTimerResolution = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool LoadApplication(Options options)
|
|
|
+ {
|
|
|
+ string path = options.InputPath;
|
|
|
+
|
|
|
+ Logger.RestartTime();
|
|
|
+
|
|
|
+ _window = new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse);
|
|
|
+ _emulationContext = InitializeEmulationContext(_window, options);
|
|
|
+
|
|
|
+ SetupProgressHandler();
|
|
|
+
|
|
|
+ SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
|
|
+
|
|
|
+ Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
|
|
|
+
|
|
|
+ if (Directory.Exists(path))
|
|
|
+ {
|
|
|
+ string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
|
|
|
+
|
|
|
+ if (romFsFiles.Length == 0)
|
|
|
+ {
|
|
|
+ romFsFiles = Directory.GetFiles(path, "*.romfs");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (romFsFiles.Length > 0)
|
|
|
+ {
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
|
|
+ _emulationContext.LoadCart(path, romFsFiles[0]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
|
|
+ _emulationContext.LoadCart(path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (File.Exists(path))
|
|
|
+ {
|
|
|
+ switch (Path.GetExtension(path).ToLowerInvariant())
|
|
|
+ {
|
|
|
+ case ".xci":
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
|
|
+ _emulationContext.LoadXci(path);
|
|
|
+ break;
|
|
|
+ case ".nca":
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
|
|
+ _emulationContext.LoadNca(path);
|
|
|
+ break;
|
|
|
+ case ".nsp":
|
|
|
+ case ".pfs0":
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
|
|
+ _emulationContext.LoadNsp(path);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _emulationContext.LoadProgram(path);
|
|
|
+ }
|
|
|
+ catch (ArgumentOutOfRangeException)
|
|
|
+ {
|
|
|
+ Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
|
|
+
|
|
|
+ _emulationContext.Dispose();
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Translator.IsReadyForTranslation.Reset();
|
|
|
+
|
|
|
+ Thread windowThread = new Thread(() =>
|
|
|
+ {
|
|
|
+ ExecutionEntrypoint();
|
|
|
+ })
|
|
|
+ {
|
|
|
+ Name = "GUI.WindowThread"
|
|
|
+ };
|
|
|
+
|
|
|
+ windowThread.Start();
|
|
|
+ windowThread.Join();
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|