mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-04-21 03:03:15 +02:00
359 lines
15 KiB
C#
359 lines
15 KiB
C#
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<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,
|
|
RangeLeft = 1.0f,
|
|
RangeRight = 1.0f,
|
|
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,
|
|
Rotate90CW = 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,
|
|
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
|
|
)
|
|
);
|
|
}
|
|
}
|