Add signed ViGEmBus virtual-joystick support (Xbox 360)
Replace the test-signed RioGamepad driver as the default joystick output with a ViGEmBus virtual Xbox 360 controller (Nefarius.ViGEm.Client) — properly signed, so it installs with no test-signing / Secure Boot change / reboot. The coordinator now selects ViGEm first, then the RioGamepad HID feeder, then a no-op. The XInput layout caps joystick buttons at 11, so the editor's joystick picker lists the named Xbox buttons (A, B, X, Y, LB, RB, Back, Start, L3, R3, Guide); the hat maps to the D-pad and the six axes to the sticks + triggers. Bind anything beyond 11 to the keyboard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System.Runtime.Versioning;
|
||||
using Nefarius.ViGEm.Client;
|
||||
using Nefarius.ViGEm.Client.Targets;
|
||||
using Nefarius.ViGEm.Client.Targets.Xbox360;
|
||||
using RioJoy.Core.Calibration;
|
||||
using RioJoy.Core.Mapping;
|
||||
|
||||
namespace RioJoy.Core.Output;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IJoystickSink"/> backed by a <b>signed ViGEmBus</b> virtual Xbox 360
|
||||
/// controller. Properly signed — installs with no test-signing / Secure-Boot
|
||||
/// changes — at the cost of the XInput layout: 11 buttons, two sticks + two
|
||||
/// triggers, and the D-pad for the hat. RIO joystick buttons beyond
|
||||
/// <see cref="MappableButtonCount"/> are dropped (map those to the keyboard).
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed class ViGEmJoystickSink : IJoystickSink, IDisposable
|
||||
{
|
||||
// RIO joystick button (1-based) -> Xbox 360 button, in a natural order.
|
||||
private static readonly Xbox360Button[] ButtonMap =
|
||||
{
|
||||
Xbox360Button.A, Xbox360Button.B, Xbox360Button.X, Xbox360Button.Y,
|
||||
Xbox360Button.LeftShoulder, Xbox360Button.RightShoulder,
|
||||
Xbox360Button.Back, Xbox360Button.Start,
|
||||
Xbox360Button.LeftThumb, Xbox360Button.RightThumb,
|
||||
Xbox360Button.Guide,
|
||||
};
|
||||
|
||||
/// <summary>Display names for the mappable buttons, aligned with <see cref="ButtonMap"/>.</summary>
|
||||
public static IReadOnlyList<string> ButtonNames { get; } = new[]
|
||||
{
|
||||
"A", "B", "X", "Y", "LB", "RB", "Back", "Start", "L3", "R3", "Guide",
|
||||
};
|
||||
|
||||
/// <summary>How many RIO joystick buttons map onto the Xbox 360 pad (11).</summary>
|
||||
public static int MappableButtonCount => ButtonMap.Length;
|
||||
|
||||
private readonly ViGEmClient _client;
|
||||
private readonly IXbox360Controller _pad;
|
||||
private readonly object _gate = new();
|
||||
|
||||
private ViGEmJoystickSink(ViGEmClient client, IXbox360Controller pad)
|
||||
{
|
||||
_client = client;
|
||||
_pad = pad;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to connect to ViGEmBus and create a virtual Xbox 360 controller. Returns
|
||||
/// <see langword="false"/> if ViGEmBus is not installed, so callers can fall back
|
||||
/// to another joystick sink.
|
||||
/// </summary>
|
||||
public static bool TryCreate(out ViGEmJoystickSink? sink)
|
||||
{
|
||||
sink = null;
|
||||
try
|
||||
{
|
||||
var client = new ViGEmClient();
|
||||
IXbox360Controller pad = client.CreateXbox360Controller();
|
||||
pad.AutoSubmitReport = false; // submit once per logical update
|
||||
pad.Connect();
|
||||
sink = new ViGEmJoystickSink(client, pad);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// VigemBusNotFoundException (driver absent) or any connect failure.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetButton(int button, bool pressed)
|
||||
{
|
||||
int index = button - 1; // RIO joystick buttons are 1-based
|
||||
if (index < 0 || index >= ButtonMap.Length)
|
||||
return; // beyond the Xbox 360 layout — bind these to the keyboard instead
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_pad.SetButtonState(ButtonMap[index], pressed);
|
||||
_pad.SubmitReport();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHat(RioHat position)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_pad.SetButtonState(Xbox360Button.Up, position == RioHat.Up);
|
||||
_pad.SetButtonState(Xbox360Button.Right, position == RioHat.Right);
|
||||
_pad.SetButtonState(Xbox360Button.Down, position == RioHat.Down);
|
||||
_pad.SetButtonState(Xbox360Button.Left, position == RioHat.Left);
|
||||
_pad.SubmitReport();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAxis(JoyAxis axis, int value)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
case JoyAxis.X: _pad.SetAxisValue(Xbox360Axis.LeftThumbX, ToThumb(value)); break;
|
||||
case JoyAxis.Y: _pad.SetAxisValue(Xbox360Axis.LeftThumbY, ToThumb(value)); break;
|
||||
case JoyAxis.Rx: _pad.SetAxisValue(Xbox360Axis.RightThumbX, ToThumb(value)); break;
|
||||
case JoyAxis.Ry: _pad.SetAxisValue(Xbox360Axis.RightThumbY, ToThumb(value)); break;
|
||||
case JoyAxis.Z: _pad.SetSliderValue(Xbox360Slider.LeftTrigger, ToTrigger(value)); break;
|
||||
case JoyAxis.Rz: _pad.SetSliderValue(Xbox360Slider.RightTrigger, ToTrigger(value)); break;
|
||||
default: return;
|
||||
}
|
||||
_pad.SubmitReport();
|
||||
}
|
||||
}
|
||||
|
||||
// RIO axis 0..32766 (centre 16383) -> Xbox thumb short -32768..32767.
|
||||
private static short ToThumb(int value) =>
|
||||
(short)Math.Clamp((value - AxisOutputs.Center) * 2, short.MinValue, short.MaxValue);
|
||||
|
||||
// RIO axis 0..32766 -> Xbox trigger byte 0..255.
|
||||
private static byte ToTrigger(int value) =>
|
||||
(byte)Math.Clamp(value * 255 / AxisOutputs.Max, 0, 255);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { _pad.Disconnect(); } catch { /* already disconnected / bus gone */ }
|
||||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nefarius.ViGEm.Client" Version="1.21.256" />
|
||||
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user