From 854aa02c2f2eb382fcc50813d2bae53ebd0ef9b5 Mon Sep 17 00:00:00 2001 From: David Khachaturov Date: Thu, 15 Mar 2018 17:14:54 +0000 Subject: [PATCH] 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) --- BetterJoyForCemu.sln | 14 ++++ BetterJoyForCemu/App.config | 19 +++++ BetterJoyForCemu/BetterJoyForCemu.csproj | 6 ++ BetterJoyForCemu/Joycon.cs | 88 ++++++++++++++++++++---- BetterJoyForCemu/Program.cs | 26 +++++-- BetterJoyForCemu/UpdServer.cs | 6 +- 6 files changed, 139 insertions(+), 20 deletions(-) diff --git a/BetterJoyForCemu.sln b/BetterJoyForCemu.sln index 6a27a13..d85b105 100644 --- a/BetterJoyForCemu.sln +++ b/BetterJoyForCemu.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterJoyForCemu", "BetterJoyForCemu\BetterJoyForCemu.csproj", "{1BF709E9-C133-41DF-933A-C9FF3F664C7B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViGEmClient", "..\..\..\Downloads\ViGEm-master\ViGEm-master\NET\ViGEmClient\ViGEmClient.csproj", "{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|x86.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BetterJoyForCemu/App.config b/BetterJoyForCemu/App.config index 731f6de..6b4b890 100644 --- a/BetterJoyForCemu/App.config +++ b/BetterJoyForCemu/App.config @@ -3,4 +3,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterJoyForCemu/BetterJoyForCemu.csproj b/BetterJoyForCemu/BetterJoyForCemu.csproj index d7c9bf1..835c3da 100644 --- a/BetterJoyForCemu/BetterJoyForCemu.csproj +++ b/BetterJoyForCemu/BetterJoyForCemu.csproj @@ -101,5 +101,11 @@ Always + + + {aa18ebcf-7e9d-4bc5-8760-e8c6e9a773e5} + ViGEmClient + + \ No newline at end of file diff --git a/BetterJoyForCemu/Joycon.cs b/BetterJoyForCemu/Joycon.cs index 14a49c3..7a53ed3 100644 --- a/BetterJoyForCemu/Joycon.cs +++ b/BetterJoyForCemu/Joycon.cs @@ -4,10 +4,14 @@ using System.Diagnostics; using System.Linq; using System.Net.NetworkInformation; using System.Numerics; -using System.Text; +using System.Configuration; using System.Threading; using System.Threading.Tasks; +using Nefarius.ViGEm.Client; +using Nefarius.ViGEm.Client.Targets; +using Nefarius.ViGEm.Client.Targets.Xbox360; + namespace BetterJoyForCemu { public class Joycon { float timing = 120.0f; @@ -197,7 +201,13 @@ namespace BetterJoyForCemu { public PhysicalAddress PadMacAddress = new PhysicalAddress(new byte[] { 01, 02, 03, 04, 05, 06 }); public ulong Timestamp = 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) { handle = handle_; @@ -210,7 +220,21 @@ namespace BetterJoyForCemu { PadId = id; this.isPro = isPro; 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) { if (debug_type == DebugType.NONE) return; if (d == DebugType.ALL || d == debug_type || debug_type == DebugType.ALL) { @@ -343,6 +367,9 @@ namespace BetterJoyForCemu { packetCounter++; if (Program.server != null) Program.server.NewReportIncoming(this); + + if (xin != null) + xin.SendReport(report); } 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.SHOULDER2_1] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x40) != 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.A] = other.buttons[(int)Button.DPAD_RIGHT]; 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.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1]; 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.PLUS] = other.buttons[(int)Button.PLUS]; } 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]; } + 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_down) { 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; } private void ExtractIMUValues(byte[] report_buf, int n = 0) { diff --git a/BetterJoyForCemu/Program.cs b/BetterJoyForCemu/Program.cs index ab78aa2..9aadab9 100644 --- a/BetterJoyForCemu/Program.cs +++ b/BetterJoyForCemu/Program.cs @@ -8,10 +8,16 @@ using System.Numerics; using System.Threading; using System.Runtime.InteropServices; using System.Timers; -using static BetterJoyForCemu.HIDapi; + using System.Net.NetworkInformation; using System.Diagnostics; +using static BetterJoyForCemu.HIDapi; +using Nefarius.ViGEm.Client; +using Nefarius.ViGEm.Client.Targets; +using System.Net; +using System.Configuration; + namespace BetterJoyForCemu { public class JoyconManager { public bool EnableIMU = true; @@ -126,6 +132,10 @@ namespace BetterJoyForCemu { public void Start() { for (int i = 0; i < j.Count; ++i) { Joycon jc = j[i]; + + if (jc.xin != null) + jc.xin.Connect(); + byte LEDs = 0x0; LEDs |= (byte)(0x1 << i); jc.Attach(leds_: LEDs); @@ -141,6 +151,11 @@ namespace BetterJoyForCemu { public void OnApplicationQuit() { for (int i = 0; i < j.Count; ++i) { 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; static double pollsPerSecond = 120.0; + public static ViGEmClient emClient; + static void Main(string[] args) { + emClient = new ViGEmClient(); // Manages emulated XInput + foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) { // Get local BT host MAC if (nic.NetworkInterfaceType != NetworkInterfaceType.FastEthernetFx && nic.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) { @@ -204,10 +223,7 @@ namespace BetterJoyForCemu { server = new UdpServer(mgr.j); - //updateThread = new Thread(new ThreadStart(mgr.Update)); - //updateThread.Start(); - - server.Start(26760); + server.Start(IPAddress.Parse(ConfigurationSettings.AppSettings["IP"]), Int32.Parse(ConfigurationSettings.AppSettings["Port"])); HighResTimer timer = new HighResTimer(pollsPerSecond, new HighResTimer.ActionDelegate(mgr.Update)); timer.Start(); diff --git a/BetterJoyForCemu/UpdServer.cs b/BetterJoyForCemu/UpdServer.cs index 85f6989..56c2707 100644 --- a/BetterJoyForCemu/UpdServer.cs +++ b/BetterJoyForCemu/UpdServer.cs @@ -278,7 +278,7 @@ namespace BetterJoyForCemu { } } - public void Start(int port=26760) { + public void Start(IPAddress ip, int port=26760) { if (running) { if (udpSock != null) { udpSock.Close(); @@ -288,7 +288,7 @@ namespace BetterJoyForCemu { } 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 = null; @@ -301,7 +301,7 @@ namespace BetterJoyForCemu { serverId = BitConverter.ToUInt32(randomBuf, 0); running = true; - Console.WriteLine("Starting server."); + Console.WriteLine("Starting server on {0}:{1}", ip.ToString(), port); StartReceive(); }