using DiscordRPC; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Ava; using Ryujinx.Ava.Systems; using Ryujinx.Ava.Systems.Configuration; 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.Utilities; using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE; using Ryujinx.Input; using Silk.NET.Vulkan; using System; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; 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 { public partial class HeadlessRyujinx { public static void Initialize() { // Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now; // Delete backup files after updating. Task.Run(Updater.CleanupUpdate); // Hook unhandled exception and process exit events. AppDomain.CurrentDomain.UnhandledException += (sender, e) => Program.ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating); AppDomain.CurrentDomain.ProcessExit += (_, _) => Program.Exit(); // Initialize the configuration. ConfigurationState.Initialize(); // Initialize Discord integration. DiscordIntegrationModule.Initialize(); // Logging system information. Program.PrintSystemInfo(); } 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; } } if (index != PlayerIndex.Handheld && _inputConfiguration.Any(x => x.PlayerIndex == PlayerIndex.Handheld)) return null; IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId); bool isKeyboard = true; 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 { 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 { StickUp = Key.W, StickDown = Key.S, StickLeft = Key.A, StickRight = Key.D, StickButton = Key.F, }, RightJoycon = new RightJoyconCommonConfig { 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 { 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, RangeLeft = 1.0f, RangeRight = 1.0f, TriggerThreshold = 0.5f, LeftJoycon = new LeftJoyconCommonConfig { 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 { Joystick = ConfigStickInputId.Left, StickButton = ConfigGamepadInputId.LeftStick, InvertStickX = false, InvertStickY = false, Rotate90CW = false, }, RightJoycon = new RightJoyconCommonConfig { 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 { Joystick = ConfigStickInputId.Right, StickButton = ConfigGamepadInputId.RightStick, InvertStickX = false, InvertStickY = false, Rotate90CW = false, }, Motion = new StandardMotionConfigController { MotionBackend = MotionInputBackendType.GamepadDriver, EnableMotion = true, Sensitivity = 100, GyroDeadzone = 1, }, Rumble = new RumbleConfigController { StrongRumble = 1f, WeakRumble = 1f, EnableRumble = false, }, }; } } 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 { config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); } 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}\""); // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. if (config is StandardControllerInputConfig controllerConfig) { if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f) { controllerConfig.RangeLeft = 1.0f; controllerConfig.RangeRight = 1.0f; Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); } } return config; } private static IRenderer CreateRenderer(Options options, WindowBase window) { if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow) { string preferredGpuId = string.Empty; Vk api = Vk.GetApi(); if (!string.IsNullOrEmpty(options.PreferredGPUVendor)) { string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant(); DeviceInfo[] devices = VulkanRenderer.GetPhysicalDevices(api); foreach (DeviceInfo device in devices) { if (device.Vendor.ToLowerInvariant() == preferredGpuVendor) { preferredGpuId = device.Id; break; } } } return new VulkanRenderer( api, (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), vulkanWindow.GetRequiredInstanceExtensions, preferredGpuId); } return new OpenGLRenderer(); } private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) => new( new HleConfiguration( options.DramSize, options.SystemLanguage, options.SystemRegion, options.VSyncMode, !options.DisableDockedMode, !options.DisablePTC, ITickSource.RealityTickScalar, options.EnableInternetAccess, !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, options.FsGlobalAccessLogMode, options.SystemTimeOffset, options.SystemTimeZone, options.MemoryManagerMode, options.IgnoreMissingServices, options.AspectRatio, options.AudioVolume, options.UseHypervisor ?? true, options.MultiplayerLanInterfaceId, Common.Configuration.Multiplayer.MultiplayerMode.Disabled, false, string.Empty, string.Empty, options.CustomVSyncInterval ) .Configure( _virtualFileSystem, _libHacHorizonManager, _contentManager, _accountManager, _userChannelPersistence, renderer.TryMakeThreaded(options.BackendThreading), new SDL2HardwareDeviceDriver(), window ) ); } }