Added XInput Support using ViGEm.

- Rumble support
 - Ability to rebind keys
 - No longer need to use "Also use for axes/buttons"
 - System-wide compatability (use your joycons with Steam, or something)
 - Requires ViGEm driver (provided in release)
This commit is contained in:
David Khachaturov 2018-03-15 17:14:54 +00:00
parent 90139b3aa4
commit 854aa02c2f
6 changed files with 139 additions and 20 deletions

View file

@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27130.2036
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterJoyForCemu", "BetterJoyForCemu\BetterJoyForCemu.csproj", "{1BF709E9-C133-41DF-933A-C9FF3F664C7B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterJoyForCemu", "BetterJoyForCemu\BetterJoyForCemu.csproj", "{1BF709E9-C133-41DF-933A-C9FF3F664C7B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViGEmClient", "..\..\..\Downloads\ViGEm-master\ViGEm-master\NET\ViGEmClient\ViGEmClient.csproj", "{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,18 @@ Global
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x64.Build.0 = Release|x64 {1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x64.Build.0 = Release|x64
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.ActiveCfg = Release|x86 {1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.ActiveCfg = Release|x86
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.Build.0 = Release|x86 {1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.Build.0 = Release|x86
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x64.ActiveCfg = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x64.Build.0 = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x86.ActiveCfg = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x86.Build.0 = Debug|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|Any CPU.Build.0 = Release|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x64.ActiveCfg = Release|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x64.Build.0 = Release|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x86.ActiveCfg = Release|Any CPU
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -3,4 +3,23 @@
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup> </startup>
<appSettings>
<!--Motion Server IP: the default is localhost; you can change it to 0.0.0.0 (all interfaces) or a specific LAN IP
which is **useful if you want to access the server from another computer in a network.** Default: 127.0.0.1-->
<add key="IP" value="127.0.0.1" />
<!--Motion Server port: the default is 26760; if it conflicts with another server set it to anything valid
but in that case also change the port in PadTest and cemuhook.ini accordingly. Default: 26760 -->
<add key="Port" value="26760" />
<!--Rumble motor period in millisec. Lower means more granular vibration, higher is more stable.-->
<!--The response of rumble does not only depend on this setting and it's always high. Default: 100 -->
<add key="RumblePeriod" value="100" />
<!--The controller's HD rumble settings for the low/high frequency rumble. Change to change the pitch of the rumble.-->
<!--Don't set above ~1200. Default: 160 and 320 -->
<add key="LowFreqRumble" value="160" />
<add key="HighFreqRumble" value="320" />
</appSettings>
</configuration> </configuration>

View file

@ -101,5 +101,11 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Downloads\ViGEm-master\ViGEm-master\NET\ViGEmClient\ViGEmClient.csproj">
<Project>{aa18ebcf-7e9d-4bc5-8760-e8c6e9a773e5}</Project>
<Name>ViGEmClient</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -4,10 +4,14 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Numerics; using System.Numerics;
using System.Text; using System.Configuration;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.Xbox360;
namespace BetterJoyForCemu { namespace BetterJoyForCemu {
public class Joycon { public class Joycon {
float timing = 120.0f; float timing = 120.0f;
@ -197,7 +201,13 @@ namespace BetterJoyForCemu {
public PhysicalAddress PadMacAddress = new PhysicalAddress(new byte[] { 01, 02, 03, 04, 05, 06 }); public PhysicalAddress PadMacAddress = new PhysicalAddress(new byte[] { 01, 02, 03, 04, 05, 06 });
public ulong Timestamp = 0; public ulong Timestamp = 0;
public int packetCounter = 0; public int packetCounter = 0;
// // For XInput
public Xbox360Controller xin;
Xbox360Report report;
int rumblePeriod = Int32.Parse(ConfigurationSettings.AppSettings["RumblePeriod"]);
int lowFreq = Int32.Parse(ConfigurationSettings.AppSettings["LowFreqRumble"]);
int highFreq = Int32.Parse(ConfigurationSettings.AppSettings["HighFreqRumble"]);
public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, int id = 0, bool isPro=false, bool usb = false) { public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, int id = 0, bool isPro=false, bool usb = false) {
handle = handle_; handle = handle_;
@ -210,7 +220,21 @@ namespace BetterJoyForCemu {
PadId = id; PadId = id;
this.isPro = isPro; this.isPro = isPro;
isUSB = usb; isUSB = usb;
if (isLeft || isPro) {
xin = new Xbox360Controller(Program.emClient);
xin.FeedbackReceived += ReceiveRumble;
report = new Xbox360Report();
} }
}
public void ReceiveRumble(object sender, Nefarius.ViGEm.Client.Targets.Xbox360.Xbox360FeedbackReceivedEventArgs e) {
SetRumble(lowFreq, highFreq, (float) e.LargeMotor / (float) 255, rumblePeriod);
if (other != null)
other.SetRumble(lowFreq, highFreq, (float)e.LargeMotor / (float)255, rumblePeriod);
}
public void DebugPrint(String s, DebugType d) { public void DebugPrint(String s, DebugType d) {
if (debug_type == DebugType.NONE) return; if (debug_type == DebugType.NONE) return;
if (d == DebugType.ALL || d == debug_type || debug_type == DebugType.ALL) { if (d == DebugType.ALL || d == debug_type || debug_type == DebugType.ALL) {
@ -343,6 +367,9 @@ namespace BetterJoyForCemu {
packetCounter++; packetCounter++;
if (Program.server != null) if (Program.server != null)
Program.server.NewReportIncoming(this); Program.server.NewReportIncoming(this);
if (xin != null)
xin.SendReport(report);
} }
if (ts_en == raw_buf[1]) { if (ts_en == raw_buf[1]) {
@ -457,9 +484,25 @@ namespace BetterJoyForCemu {
buttons[(int)Button.STICK2] = ((report_buf[4] & (!isLeft ? 0x08 : 0x04)) != 0); buttons[(int)Button.STICK2] = ((report_buf[4] & (!isLeft ? 0x08 : 0x04)) != 0);
buttons[(int)Button.SHOULDER2_1] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x40) != 0; buttons[(int)Button.SHOULDER2_1] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x40) != 0;
buttons[(int)Button.SHOULDER2_2] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x80) != 0; buttons[(int)Button.SHOULDER2_2] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x80) != 0;
report.SetButtonState(Xbox360Buttons.A, buttons[(int)Button.B]);
report.SetButtonState(Xbox360Buttons.B, buttons[(int)Button.A]);
report.SetButtonState(Xbox360Buttons.Y, buttons[(int)Button.X]);
report.SetButtonState(Xbox360Buttons.X, buttons[(int)Button.Y]);
report.SetButtonState(Xbox360Buttons.Up, buttons[(int)Button.DPAD_UP]);
report.SetButtonState(Xbox360Buttons.Down, buttons[(int)Button.DPAD_DOWN]);
report.SetButtonState(Xbox360Buttons.Left, buttons[(int)Button.DPAD_LEFT]);
report.SetButtonState(Xbox360Buttons.Right, buttons[(int)Button.DPAD_RIGHT]);
report.SetButtonState(Xbox360Buttons.Back, buttons[(int)Button.MINUS]);
report.SetButtonState(Xbox360Buttons.Start, buttons[(int)Button.PLUS]);
report.SetButtonState(Xbox360Buttons.Guide, buttons[(int)Button.HOME]);
report.SetButtonState(Xbox360Buttons.LeftShoulder, buttons[(int)Button.SHOULDER_1]);
report.SetButtonState(Xbox360Buttons.RightShoulder, buttons[(int)Button.SHOULDER2_1]);
report.SetButtonState(Xbox360Buttons.LeftThumb, buttons[(int)Button.STICK]);
report.SetButtonState(Xbox360Buttons.RightThumb, buttons[(int)Button.STICK2]);
} }
if (isLeft && other != null) { if (other != null) {
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN]; buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT]; buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP]; buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
@ -468,24 +511,35 @@ namespace BetterJoyForCemu {
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK]; buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1]; buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2]; buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
}
if (isLeft && other != null) {
buttons[(int)Button.HOME] = other.buttons[(int)Button.HOME]; buttons[(int)Button.HOME] = other.buttons[(int)Button.HOME];
buttons[(int)Button.PLUS] = other.buttons[(int)Button.PLUS]; buttons[(int)Button.PLUS] = other.buttons[(int)Button.PLUS];
} }
if (!isLeft && other != null) { if (!isLeft && other != null) {
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
buttons[(int)Button.Y] = other.buttons[(int)Button.DPAD_LEFT];
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
buttons[(int)Button.MINUS] = other.buttons[(int)Button.MINUS]; buttons[(int)Button.MINUS] = other.buttons[(int)Button.MINUS];
} }
if (!isPro && other != null && xin != null) {
report.SetButtonState(Xbox360Buttons.A, buttons[(int)(isLeft ? Button.B : Button.DPAD_DOWN)]);
report.SetButtonState(Xbox360Buttons.B, buttons[(int)(isLeft ? Button.A : Button.DPAD_RIGHT)]);
report.SetButtonState(Xbox360Buttons.Y, buttons[(int)(isLeft ? Button.X : Button.DPAD_UP)]);
report.SetButtonState(Xbox360Buttons.X, buttons[(int)(isLeft ? Button.Y : Button.DPAD_LEFT)]);
report.SetButtonState(Xbox360Buttons.Up, buttons[(int)(isLeft ? Button.DPAD_UP : Button.X)]);
report.SetButtonState(Xbox360Buttons.Down, buttons[(int)(isLeft ? Button.DPAD_DOWN : Button.B)]);
report.SetButtonState(Xbox360Buttons.Left, buttons[(int)(isLeft ? Button.DPAD_LEFT : Button.Y)]);
report.SetButtonState(Xbox360Buttons.Right, buttons[(int)(isLeft ? Button.DPAD_RIGHT : Button.A)]);
report.SetButtonState(Xbox360Buttons.Back, buttons[(int)Button.MINUS]);
report.SetButtonState(Xbox360Buttons.Start, buttons[(int)Button.PLUS]);
report.SetButtonState(Xbox360Buttons.Guide, buttons[(int)Button.HOME]);
report.SetButtonState(Xbox360Buttons.LeftShoulder, buttons[(int)(isLeft ? Button.SHOULDER_1 : Button.SHOULDER2_1)]);
report.SetButtonState(Xbox360Buttons.RightShoulder, buttons[(int)(isLeft ? Button.SHOULDER2_1 : Button.SHOULDER_1)]);
report.SetButtonState(Xbox360Buttons.LeftThumb, buttons[(int)(isLeft ? Button.STICK : Button.STICK2)]);
report.SetButtonState(Xbox360Buttons.RightThumb, buttons[(int)(isLeft ? Button.STICK2 : Button.STICK)]);
}
lock (buttons_up) { lock (buttons_up) {
lock (buttons_down) { lock (buttons_down) {
for (int i = 0; i < buttons.Length; ++i) { for (int i = 0; i < buttons.Length; ++i) {
@ -495,6 +549,16 @@ namespace BetterJoyForCemu {
} }
} }
} }
if (xin != null) {
report.SetAxis(Xbox360Axes.LeftThumbX, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick[0] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
report.SetAxis(Xbox360Axes.LeftThumbY, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick[1] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
report.SetAxis(Xbox360Axes.RightThumbX, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick2[0] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
report.SetAxis(Xbox360Axes.RightThumbY, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick2[1] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
report.SetAxis(Xbox360Axes.LeftTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER_2 : Button.SHOULDER2_2)] ? Int16.MaxValue : 0));
report.SetAxis(Xbox360Axes.RightTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER2_2 : Button.SHOULDER_2)] ? Int16.MaxValue : 0));
}
return 0; return 0;
} }
private void ExtractIMUValues(byte[] report_buf, int n = 0) { private void ExtractIMUValues(byte[] report_buf, int n = 0) {

View file

@ -8,10 +8,16 @@ using System.Numerics;
using System.Threading; using System.Threading;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Timers; using System.Timers;
using static BetterJoyForCemu.HIDapi;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Diagnostics; using System.Diagnostics;
using static BetterJoyForCemu.HIDapi;
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Targets;
using System.Net;
using System.Configuration;
namespace BetterJoyForCemu { namespace BetterJoyForCemu {
public class JoyconManager { public class JoyconManager {
public bool EnableIMU = true; public bool EnableIMU = true;
@ -126,6 +132,10 @@ namespace BetterJoyForCemu {
public void Start() { public void Start() {
for (int i = 0; i < j.Count; ++i) { for (int i = 0; i < j.Count; ++i) {
Joycon jc = j[i]; Joycon jc = j[i];
if (jc.xin != null)
jc.xin.Connect();
byte LEDs = 0x0; byte LEDs = 0x0;
LEDs |= (byte)(0x1 << i); LEDs |= (byte)(0x1 << i);
jc.Attach(leds_: LEDs); jc.Attach(leds_: LEDs);
@ -141,6 +151,11 @@ namespace BetterJoyForCemu {
public void OnApplicationQuit() { public void OnApplicationQuit() {
for (int i = 0; i < j.Count; ++i) { for (int i = 0; i < j.Count; ++i) {
j[i].Detach(); j[i].Detach();
if (j[i].xin != null) {
j[i].xin.Disconnect();
j[i].xin.Dispose();
}
} }
} }
} }
@ -188,7 +203,11 @@ namespace BetterJoyForCemu {
public static UdpServer server; public static UdpServer server;
static double pollsPerSecond = 120.0; static double pollsPerSecond = 120.0;
public static ViGEmClient emClient;
static void Main(string[] args) { static void Main(string[] args) {
emClient = new ViGEmClient(); // Manages emulated XInput
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) { foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) {
// Get local BT host MAC // Get local BT host MAC
if (nic.NetworkInterfaceType != NetworkInterfaceType.FastEthernetFx && nic.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) { if (nic.NetworkInterfaceType != NetworkInterfaceType.FastEthernetFx && nic.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) {
@ -204,10 +223,7 @@ namespace BetterJoyForCemu {
server = new UdpServer(mgr.j); server = new UdpServer(mgr.j);
//updateThread = new Thread(new ThreadStart(mgr.Update)); server.Start(IPAddress.Parse(ConfigurationSettings.AppSettings["IP"]), Int32.Parse(ConfigurationSettings.AppSettings["Port"]));
//updateThread.Start();
server.Start(26760);
HighResTimer timer = new HighResTimer(pollsPerSecond, new HighResTimer.ActionDelegate(mgr.Update)); HighResTimer timer = new HighResTimer(pollsPerSecond, new HighResTimer.ActionDelegate(mgr.Update));
timer.Start(); timer.Start();

View file

@ -278,7 +278,7 @@ namespace BetterJoyForCemu {
} }
} }
public void Start(int port=26760) { public void Start(IPAddress ip, int port=26760) {
if (running) { if (running) {
if (udpSock != null) { if (udpSock != null) {
udpSock.Close(); udpSock.Close();
@ -288,7 +288,7 @@ namespace BetterJoyForCemu {
} }
udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try { udpSock.Bind(new IPEndPoint(IPAddress.Loopback, port)); } catch (SocketException ex) { try { udpSock.Bind(new IPEndPoint(ip, port)); } catch (SocketException ex) {
udpSock.Close(); udpSock.Close();
udpSock = null; udpSock = null;
@ -301,7 +301,7 @@ namespace BetterJoyForCemu {
serverId = BitConverter.ToUInt32(randomBuf, 0); serverId = BitConverter.ToUInt32(randomBuf, 0);
running = true; running = true;
Console.WriteLine("Starting server."); Console.WriteLine("Starting server on {0}:{1}", ip.ToString(), port);
StartReceive(); StartReceive();
} }