Skip to content

Commit

Permalink
Added XInput Support using ViGEm.
Browse files Browse the repository at this point in the history
 - 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)
  • Loading branch information
Davidobot committed Mar 15, 2018
1 parent 90139b3 commit 854aa02
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 20 deletions.
14 changes: 14 additions & 0 deletions BetterJoyForCemu.sln
Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 19 additions & 0 deletions BetterJoyForCemu/App.config
Expand Up @@ -3,4 +3,23 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</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>
6 changes: 6 additions & 0 deletions BetterJoyForCemu/BetterJoyForCemu.csproj
Expand Up @@ -101,5 +101,11 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</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" />
</Project>
88 changes: 76 additions & 12 deletions BetterJoyForCemu/Joycon.cs
Expand Up @@ -4,10 +4,14 @@
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;
Expand Down Expand Up @@ -197,7 +201,13 @@ private struct Rumble {
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_;
Expand All @@ -210,7 +220,21 @@ private struct Rumble {
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) {
Expand Down Expand Up @@ -343,6 +367,9 @@ private struct Rumble {
packetCounter++;
if (Program.server != null)
Program.server.NewReportIncoming(this);

if (xin != null)
xin.SendReport(report);
}

if (ts_en == raw_buf[1]) {
Expand Down Expand Up @@ -457,9 +484,25 @@ private struct Rumble {
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];
Expand All @@ -468,24 +511,35 @@ private struct Rumble {
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) {
Expand All @@ -495,6 +549,16 @@ private struct Rumble {
}
}
}

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) {
Expand Down
26 changes: 21 additions & 5 deletions BetterJoyForCemu/Program.cs
Expand Up @@ -8,10 +8,16 @@
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;
Expand Down Expand Up @@ -126,6 +132,10 @@ public class JoyconManager {
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);
Expand All @@ -141,6 +151,11 @@ public class JoyconManager {
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();
}
}
}
}
Expand Down Expand Up @@ -188,7 +203,11 @@ class Program {
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) {
Expand All @@ -204,10 +223,7 @@ class Program {

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();

Expand Down
6 changes: 3 additions & 3 deletions BetterJoyForCemu/UpdServer.cs
Expand Up @@ -278,7 +278,7 @@ class ClientRequestTimes {
}
}

public void Start(int port=26760) {
public void Start(IPAddress ip, int port=26760) {
if (running) {
if (udpSock != null) {
udpSock.Close();
Expand All @@ -288,7 +288,7 @@ class ClientRequestTimes {
}

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;

Expand All @@ -301,7 +301,7 @@ class ClientRequestTimes {
serverId = BitConverter.ToUInt32(randomBuf, 0);

running = true;
Console.WriteLine("Starting server.");
Console.WriteLine("Starting server on {0}:{1}", ip.ToString(), port);
StartReceive();
}

Expand Down

0 comments on commit 854aa02

Please sign in to comment.