Skip to content

Commit 854aa02

Browse files
committed
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)
1 parent 90139b3 commit 854aa02

6 files changed

Lines changed: 139 additions & 20 deletions

File tree

BetterJoyForCemu.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27130.2036
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterJoyForCemu", "BetterJoyForCemu\BetterJoyForCemu.csproj", "{1BF709E9-C133-41DF-933A-C9FF3F664C7B}"
77
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViGEmClient", "..\..\..\Downloads\ViGEm-master\ViGEm-master\NET\ViGEmClient\ViGEmClient.csproj", "{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}"
9+
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1012
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,18 @@ Global
2729
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x64.Build.0 = Release|x64
2830
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.ActiveCfg = Release|x86
2931
{1BF709E9-C133-41DF-933A-C9FF3F664C7B}.Release|x86.Build.0 = Release|x86
32+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x64.ActiveCfg = Debug|Any CPU
35+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x64.Build.0 = Debug|Any CPU
36+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x86.ActiveCfg = Debug|Any CPU
37+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Debug|x86.Build.0 = Debug|Any CPU
38+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
39+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x64.ActiveCfg = Release|Any CPU
41+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x64.Build.0 = Release|Any CPU
42+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x86.ActiveCfg = Release|Any CPU
43+
{AA18EBCF-7E9D-4BC5-8760-E8C6E9A773E5}.Release|x86.Build.0 = Release|Any CPU
3044
EndGlobalSection
3145
GlobalSection(SolutionProperties) = preSolution
3246
HideSolutionNode = FALSE

BetterJoyForCemu/App.config

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

BetterJoyForCemu/BetterJoyForCemu.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,11 @@
101101
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
102102
</Content>
103103
</ItemGroup>
104+
<ItemGroup>
105+
<ProjectReference Include="..\..\..\..\Downloads\ViGEm-master\ViGEm-master\NET\ViGEmClient\ViGEmClient.csproj">
106+
<Project>{aa18ebcf-7e9d-4bc5-8760-e8c6e9a773e5}</Project>
107+
<Name>ViGEmClient</Name>
108+
</ProjectReference>
109+
</ItemGroup>
104110
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
105111
</Project>

BetterJoyForCemu/Joycon.cs

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
using System.Linq;
55
using System.Net.NetworkInformation;
66
using System.Numerics;
7-
using System.Text;
7+
using System.Configuration;
88
using System.Threading;
99
using System.Threading.Tasks;
1010

11+
using Nefarius.ViGEm.Client;
12+
using Nefarius.ViGEm.Client.Targets;
13+
using Nefarius.ViGEm.Client.Targets.Xbox360;
14+
1115
namespace BetterJoyForCemu {
1216
public class Joycon {
1317
float timing = 120.0f;
@@ -197,7 +201,13 @@ public byte[] GetData() {
197201
public PhysicalAddress PadMacAddress = new PhysicalAddress(new byte[] { 01, 02, 03, 04, 05, 06 });
198202
public ulong Timestamp = 0;
199203
public int packetCounter = 0;
200-
//
204+
// For XInput
205+
public Xbox360Controller xin;
206+
Xbox360Report report;
207+
208+
int rumblePeriod = Int32.Parse(ConfigurationSettings.AppSettings["RumblePeriod"]);
209+
int lowFreq = Int32.Parse(ConfigurationSettings.AppSettings["LowFreqRumble"]);
210+
int highFreq = Int32.Parse(ConfigurationSettings.AppSettings["HighFreqRumble"]);
201211

202212
public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, int id = 0, bool isPro=false, bool usb = false) {
203213
handle = handle_;
@@ -210,7 +220,21 @@ public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left, i
210220
PadId = id;
211221
this.isPro = isPro;
212222
isUSB = usb;
223+
224+
if (isLeft || isPro) {
225+
xin = new Xbox360Controller(Program.emClient);
226+
xin.FeedbackReceived += ReceiveRumble;
227+
report = new Xbox360Report();
228+
}
229+
}
230+
231+
public void ReceiveRumble(object sender, Nefarius.ViGEm.Client.Targets.Xbox360.Xbox360FeedbackReceivedEventArgs e) {
232+
SetRumble(lowFreq, highFreq, (float) e.LargeMotor / (float) 255, rumblePeriod);
233+
234+
if (other != null)
235+
other.SetRumble(lowFreq, highFreq, (float)e.LargeMotor / (float)255, rumblePeriod);
213236
}
237+
214238
public void DebugPrint(String s, DebugType d) {
215239
if (debug_type == DebugType.NONE) return;
216240
if (d == DebugType.ALL || d == debug_type || debug_type == DebugType.ALL) {
@@ -343,6 +367,9 @@ private int ReceiveRaw() {
343367
packetCounter++;
344368
if (Program.server != null)
345369
Program.server.NewReportIncoming(this);
370+
371+
if (xin != null)
372+
xin.SendReport(report);
346373
}
347374

348375
if (ts_en == raw_buf[1]) {
@@ -457,9 +484,25 @@ private int ProcessButtonsAndStick(byte[] report_buf) {
457484
buttons[(int)Button.STICK2] = ((report_buf[4] & (!isLeft ? 0x08 : 0x04)) != 0);
458485
buttons[(int)Button.SHOULDER2_1] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x40) != 0;
459486
buttons[(int)Button.SHOULDER2_2] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x80) != 0;
487+
488+
report.SetButtonState(Xbox360Buttons.A, buttons[(int)Button.B]);
489+
report.SetButtonState(Xbox360Buttons.B, buttons[(int)Button.A]);
490+
report.SetButtonState(Xbox360Buttons.Y, buttons[(int)Button.X]);
491+
report.SetButtonState(Xbox360Buttons.X, buttons[(int)Button.Y]);
492+
report.SetButtonState(Xbox360Buttons.Up, buttons[(int)Button.DPAD_UP]);
493+
report.SetButtonState(Xbox360Buttons.Down, buttons[(int)Button.DPAD_DOWN]);
494+
report.SetButtonState(Xbox360Buttons.Left, buttons[(int)Button.DPAD_LEFT]);
495+
report.SetButtonState(Xbox360Buttons.Right, buttons[(int)Button.DPAD_RIGHT]);
496+
report.SetButtonState(Xbox360Buttons.Back, buttons[(int)Button.MINUS]);
497+
report.SetButtonState(Xbox360Buttons.Start, buttons[(int)Button.PLUS]);
498+
report.SetButtonState(Xbox360Buttons.Guide, buttons[(int)Button.HOME]);
499+
report.SetButtonState(Xbox360Buttons.LeftShoulder, buttons[(int)Button.SHOULDER_1]);
500+
report.SetButtonState(Xbox360Buttons.RightShoulder, buttons[(int)Button.SHOULDER2_1]);
501+
report.SetButtonState(Xbox360Buttons.LeftThumb, buttons[(int)Button.STICK]);
502+
report.SetButtonState(Xbox360Buttons.RightThumb, buttons[(int)Button.STICK2]);
460503
}
461504

462-
if (isLeft && other != null) {
505+
if (other != null) {
463506
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
464507
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
465508
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
@@ -468,24 +511,35 @@ private int ProcessButtonsAndStick(byte[] report_buf) {
468511
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
469512
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
470513
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
514+
}
471515

516+
if (isLeft && other != null) {
472517
buttons[(int)Button.HOME] = other.buttons[(int)Button.HOME];
473518
buttons[(int)Button.PLUS] = other.buttons[(int)Button.PLUS];
474519
}
475520

476521
if (!isLeft && other != null) {
477-
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
478-
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
479-
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
480-
buttons[(int)Button.Y] = other.buttons[(int)Button.DPAD_LEFT];
481-
482-
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
483-
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
484-
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
485-
486522
buttons[(int)Button.MINUS] = other.buttons[(int)Button.MINUS];
487523
}
488524

525+
if (!isPro && other != null && xin != null) {
526+
report.SetButtonState(Xbox360Buttons.A, buttons[(int)(isLeft ? Button.B : Button.DPAD_DOWN)]);
527+
report.SetButtonState(Xbox360Buttons.B, buttons[(int)(isLeft ? Button.A : Button.DPAD_RIGHT)]);
528+
report.SetButtonState(Xbox360Buttons.Y, buttons[(int)(isLeft ? Button.X : Button.DPAD_UP)]);
529+
report.SetButtonState(Xbox360Buttons.X, buttons[(int)(isLeft ? Button.Y : Button.DPAD_LEFT)]);
530+
report.SetButtonState(Xbox360Buttons.Up, buttons[(int)(isLeft ? Button.DPAD_UP : Button.X)]);
531+
report.SetButtonState(Xbox360Buttons.Down, buttons[(int)(isLeft ? Button.DPAD_DOWN : Button.B)]);
532+
report.SetButtonState(Xbox360Buttons.Left, buttons[(int)(isLeft ? Button.DPAD_LEFT : Button.Y)]);
533+
report.SetButtonState(Xbox360Buttons.Right, buttons[(int)(isLeft ? Button.DPAD_RIGHT : Button.A)]);
534+
report.SetButtonState(Xbox360Buttons.Back, buttons[(int)Button.MINUS]);
535+
report.SetButtonState(Xbox360Buttons.Start, buttons[(int)Button.PLUS]);
536+
report.SetButtonState(Xbox360Buttons.Guide, buttons[(int)Button.HOME]);
537+
report.SetButtonState(Xbox360Buttons.LeftShoulder, buttons[(int)(isLeft ? Button.SHOULDER_1 : Button.SHOULDER2_1)]);
538+
report.SetButtonState(Xbox360Buttons.RightShoulder, buttons[(int)(isLeft ? Button.SHOULDER2_1 : Button.SHOULDER_1)]);
539+
report.SetButtonState(Xbox360Buttons.LeftThumb, buttons[(int)(isLeft ? Button.STICK : Button.STICK2)]);
540+
report.SetButtonState(Xbox360Buttons.RightThumb, buttons[(int)(isLeft ? Button.STICK2 : Button.STICK)]);
541+
}
542+
489543
lock (buttons_up) {
490544
lock (buttons_down) {
491545
for (int i = 0; i < buttons.Length; ++i) {
@@ -495,6 +549,16 @@ private int ProcessButtonsAndStick(byte[] report_buf) {
495549
}
496550
}
497551
}
552+
553+
if (xin != null) {
554+
report.SetAxis(Xbox360Axes.LeftThumbX, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick[0] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
555+
report.SetAxis(Xbox360Axes.LeftThumbY, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick[1] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
556+
report.SetAxis(Xbox360Axes.RightThumbX, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick2[0] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
557+
report.SetAxis(Xbox360Axes.RightThumbY, (short)Math.Max(Int16.MinValue, Math.Min(Int16.MaxValue, stick2[1] * (stick[0] > 0 ? Int16.MaxValue : -Int16.MinValue))));
558+
report.SetAxis(Xbox360Axes.LeftTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER_2 : Button.SHOULDER2_2)] ? Int16.MaxValue : 0));
559+
report.SetAxis(Xbox360Axes.RightTrigger, (short)(buttons[(int)(isLeft ? Button.SHOULDER2_2 : Button.SHOULDER_2)] ? Int16.MaxValue : 0));
560+
}
561+
498562
return 0;
499563
}
500564
private void ExtractIMUValues(byte[] report_buf, int n = 0) {

BetterJoyForCemu/Program.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88
using System.Threading;
99
using System.Runtime.InteropServices;
1010
using System.Timers;
11-
using static BetterJoyForCemu.HIDapi;
11+
1212
using System.Net.NetworkInformation;
1313
using System.Diagnostics;
1414

15+
using static BetterJoyForCemu.HIDapi;
16+
using Nefarius.ViGEm.Client;
17+
using Nefarius.ViGEm.Client.Targets;
18+
using System.Net;
19+
using System.Configuration;
20+
1521
namespace BetterJoyForCemu {
1622
public class JoyconManager {
1723
public bool EnableIMU = true;
@@ -126,6 +132,10 @@ public void Awake() {
126132
public void Start() {
127133
for (int i = 0; i < j.Count; ++i) {
128134
Joycon jc = j[i];
135+
136+
if (jc.xin != null)
137+
jc.xin.Connect();
138+
129139
byte LEDs = 0x0;
130140
LEDs |= (byte)(0x1 << i);
131141
jc.Attach(leds_: LEDs);
@@ -141,6 +151,11 @@ public void Update() {
141151
public void OnApplicationQuit() {
142152
for (int i = 0; i < j.Count; ++i) {
143153
j[i].Detach();
154+
155+
if (j[i].xin != null) {
156+
j[i].xin.Disconnect();
157+
j[i].xin.Dispose();
158+
}
144159
}
145160
}
146161
}
@@ -188,7 +203,11 @@ class Program {
188203
public static UdpServer server;
189204
static double pollsPerSecond = 120.0;
190205

206+
public static ViGEmClient emClient;
207+
191208
static void Main(string[] args) {
209+
emClient = new ViGEmClient(); // Manages emulated XInput
210+
192211
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) {
193212
// Get local BT host MAC
194213
if (nic.NetworkInterfaceType != NetworkInterfaceType.FastEthernetFx && nic.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) {
@@ -204,10 +223,7 @@ static void Main(string[] args) {
204223

205224
server = new UdpServer(mgr.j);
206225

207-
//updateThread = new Thread(new ThreadStart(mgr.Update));
208-
//updateThread.Start();
209-
210-
server.Start(26760);
226+
server.Start(IPAddress.Parse(ConfigurationSettings.AppSettings["IP"]), Int32.Parse(ConfigurationSettings.AppSettings["Port"]));
211227
HighResTimer timer = new HighResTimer(pollsPerSecond, new HighResTimer.ActionDelegate(mgr.Update));
212228
timer.Start();
213229

BetterJoyForCemu/UpdServer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private void StartReceive() {
278278
}
279279
}
280280

281-
public void Start(int port=26760) {
281+
public void Start(IPAddress ip, int port=26760) {
282282
if (running) {
283283
if (udpSock != null) {
284284
udpSock.Close();
@@ -288,7 +288,7 @@ public void Start(int port=26760) {
288288
}
289289

290290
udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
291-
try { udpSock.Bind(new IPEndPoint(IPAddress.Loopback, port)); } catch (SocketException ex) {
291+
try { udpSock.Bind(new IPEndPoint(ip, port)); } catch (SocketException ex) {
292292
udpSock.Close();
293293
udpSock = null;
294294

@@ -301,7 +301,7 @@ public void Start(int port=26760) {
301301
serverId = BitConverter.ToUInt32(randomBuf, 0);
302302

303303
running = true;
304-
Console.WriteLine("Starting server.");
304+
Console.WriteLine("Starting server on {0}:{1}", ip.ToString(), port);
305305
StartReceive();
306306
}
307307

0 commit comments

Comments
 (0)