@@ -37,7 +37,17 @@ BOOL WINAPI CtrlHandler(DWORD event) {
3737struct Controller {
3838#pragma pack(push, 1)
3939 struct GCInput {
40- unsigned char On;
40+ union {
41+ unsigned char Status;
42+ struct {
43+ unsigned char _pad : 1 ;
44+ // This bit is set when the grey USB cable is attached, to power rumble.
45+ unsigned char CanRumble : 1 ;
46+ unsigned char _pad2 : 2 ;
47+ unsigned char Wired : 1 ;
48+ unsigned char Wireless : 1 ;
49+ };
50+ };
4151 union {
4252 unsigned short Buttons;
4353 struct {
@@ -63,14 +73,15 @@ struct Controller {
6373 unsigned char RightTrigger;
6474
6575 GCInput ()
66- : On( false ),
76+ : Status( 0 ),
6777 Buttons (0 ),
6878 AnalogX(128 ),
6979 AnalogY(128 ),
7080 CStickX(128 ),
7181 CStickY(128 ),
7282 LeftTrigger(0 ),
7383 RightTrigger(0 ){};
84+ bool On () { return Wired || Wireless; }
7485 };
7586#pragma pack(pop)
7687
@@ -123,6 +134,7 @@ class Adapter {
123134 static const unsigned char WriteEndpoint = 2 | LIBUSB_ENDPOINT_OUT;
124135
125136 libusb_device_handle* dev_handle;
137+ std::array<unsigned char , 5 > rumblePayload;
126138
127139 size_t failedReads = 0 ;
128140
@@ -137,6 +149,11 @@ class Adapter {
137149 }
138150 return interrupt == LIBUSB_SUCCESS && length == actual;
139151 }
152+ bool WriteRumble () {
153+ unsigned char * data = rumblePayload.data ();
154+ const int length = static_cast <int >(rumblePayload.size ());
155+ return Write (data, length);
156+ }
140157
141158 public:
142159 struct Inputs {
@@ -165,11 +182,9 @@ class Adapter {
165182 // Inputs can be read by the read endpoint.
166183 unsigned char init = 0x13 ;
167184 Write (&init, 1 );
168- // Rumble payload.
169- // Disable rumble on all controllers.
170- // Likely nonessential.
171- unsigned char rumble[]{0x11 , 0x0 , 0x0 , 0x0 , 0x0 };
172- Write (rumble, sizeof (rumble));
185+
186+ // Rumble should default to off.
187+ ResetRumble ();
173188 }
174189 ~Adapter () {
175190 const int release = libusb_release_interface (dev_handle, 0 );
@@ -212,17 +227,51 @@ class Adapter {
212227 }
213228 return false ;
214229 }
230+ bool ResetRumble () {
231+ rumblePayload = {0x11 , 0x0 , 0x0 , 0x0 , 0x0 };
232+ return WriteRumble ();
233+ }
234+ bool SetRumble (ssize_t index, unsigned char val) {
235+ if (index < 0 || index >= 4 ) {
236+ std::cout << " Rumble index out of range: " << index << std::endl;
237+ return false ;
238+ }
239+
240+ // NOTE: Rumble should probably be disabled in the following circumstances,
241+ // but it seems to not matter, so we ignore it for now:
242+ // The controller is wireless. WaveBirds do not have rumble functionality.
243+ // The grey USB cable is disconnected. Without it, there isn't enough power
244+ // for the motors.
245+ // The controller is disconnected. Rumble for detached controllers is
246+ // pointless.
247+
248+ rumblePayload[1 + index] = val;
249+
250+ if (DEBUG) {
251+ std::cout << " Rumble payload: " ;
252+ for (const auto & val : rumblePayload) {
253+ std::cout << " 0x" << std::hex << static_cast <int >(val) << " , " ;
254+ }
255+ std::cout << std::endl;
256+ }
257+
258+ return WriteRumble ();
259+ }
215260};
216261
217262// Globals.
218263// The list of GameCube Adapters.
219- // TODO: Make this a non-resizable vector.
220264std::array<std::atomic<Adapter*>, MAX_ADAPTERS> adapters;
221265
266+ // Forward declaration.
267+ _Function_class_ (EVT_VIGEM_DS4_NOTIFICATION) VOID
268+ UpdateRumble(PVIGEM_CLIENT Client, PVIGEM_TARGET Target, UCHAR LargeMotor,
269+ UCHAR SmallMotor, DS4_LIGHTBAR_COLOR LightbarColor);
270+
222271class ViGEmClient {
272+ public:
223273 PVIGEM_CLIENT client;
224274
225- public:
226275 ViGEmClient () {
227276 client = vigem_alloc ();
228277 if (!client) {
@@ -244,16 +293,25 @@ class ViGEmClient {
244293 // Allocate handle to identify new pad.
245294 const PVIGEM_TARGET pad = vigem_target_ds4_alloc ();
246295 // Add client to the bus, this equals a plug-in event.
247- const VIGEM_ERROR pir = vigem_target_add (client, pad);
248- if (!VIGEM_SUCCESS (pir )) {
296+ const VIGEM_ERROR add_err = vigem_target_add (client, pad);
297+ if (!VIGEM_SUCCESS (add_err )) {
249298 std::stringstream ss;
250- ss << " vigem_target_add failed with error code: 0x" << std::hex << pir
299+ ss << " vigem_target_add failed with error code: 0x" << std::hex << add_err
251300 << std::endl;
252301 throw std::runtime_error (ss.str ());
253302 }
303+ const VIGEM_ERROR reg_err =
304+ vigem_target_ds4_register_notification (client, pad, &UpdateRumble);
305+ if (!VIGEM_SUCCESS (reg_err)) {
306+ std::stringstream ss;
307+ ss << " vigem_target_ds4_register_notification failed with error code: 0x"
308+ << std::hex << add_err << std::endl;
309+ throw std::runtime_error (ss.str ());
310+ }
254311 return pad;
255312 }
256313 void RemoveController (PVIGEM_TARGET& pad) {
314+ vigem_target_ds4_unregister_notification (pad);
257315 vigem_target_remove (client, pad);
258316 vigem_target_free (pad);
259317 pad = nullptr ;
@@ -402,6 +460,15 @@ class AdapterThread {
402460 }
403461 }
404462
463+ size_t GetPadIndex (PVIGEM_TARGET pad) {
464+ auto it = find (pads.begin (), pads.end (), pad);
465+ if (it != pads.end ()) {
466+ return distance (pads.begin (), it);
467+ } else {
468+ return SIZE_MAX;
469+ }
470+ }
471+
405472 void run () {
406473 while (running) {
407474 // Allocate new virtual pads as needed.
@@ -419,7 +486,7 @@ class AdapterThread {
419486 LibUSB::RemoveAdapter (adapters[i]);
420487 std::cout << " Adapter " << i << " disconnected" << std::endl;
421488 // Associated pads are marked as disconnected.
422- // NOTE: This assumes inputs.Controllers[j].On remains true.
489+ // NOTE: This assumes inputs.Controllers[j].On() remains true.
423490 for (size_t j = 0 ; j < 4 ; j++) {
424491 size_t index = i * 4 + j;
425492 isConnected[index] = false ;
@@ -434,10 +501,14 @@ class AdapterThread {
434501 for (size_t j = 0 ; j < 4 ; j++) {
435502 const size_t index = i * 4 + j;
436503 // Check for a connection change.
437- if (isConnected[index] != (bool )inputs.Controllers [j].On ) {
438- isConnected[index] = (bool )inputs.Controllers [j].On ;
504+ if (isConnected[index] != (bool )inputs.Controllers [j].On () ) {
505+ isConnected[index] = (bool )inputs.Controllers [j].On () ;
439506 if (isConnected[index]) {
440- std::cout << " Controller " << index << " connected" << std::endl;
507+ std::cout << " Controller " << index << " connected" ;
508+ if (inputs.Controllers [j].Wired )
509+ std::cout << " (wired)" << std::endl;
510+ if (inputs.Controllers [j].Wireless )
511+ std::cout << " (wireless)" << std::endl;
441512 } else {
442513 // Disconnected controllers are reset.
443514 const Controller::GCInput resetGCInput;
@@ -447,7 +518,7 @@ class AdapterThread {
447518 << std::endl;
448519 }
449520 }
450- if (!inputs.Controllers [j].On ) {
521+ if (!inputs.Controllers [j].On () ) {
451522 continue ;
452523 }
453524 if (pads.size () < index) {
@@ -474,6 +545,33 @@ class AdapterThread {
474545 std::vector<bool > isConnected;
475546};
476547
548+ // Global adapter thread.
549+ // Must be global to allow UpdateRumble() to access required fields.
550+ AdapterThread adapterThread = AdapterThread();
551+
552+ _Function_class_ (EVT_VIGEM_DS4_NOTIFICATION) VOID
553+ UpdateRumble(PVIGEM_CLIENT Client, PVIGEM_TARGET Target, UCHAR LargeMotor,
554+ UCHAR SmallMotor, DS4_LIGHTBAR_COLOR LightbarColor) {
555+ if (Client != adapterThread.vigemClient .client ) {
556+ std::stringstream ss;
557+ ss << " VIGEM_DS4_NOTIFICATION failed: "
558+ << " client did not match." << std::endl;
559+ throw std::runtime_error (ss.str ());
560+ }
561+ // Identify the target controller.
562+ size_t index = adapterThread.GetPadIndex (Target);
563+ if (index == SIZE_MAX) {
564+ std::stringstream ss;
565+ ss << " Could not find the requested vigemClient gamepad." << std::endl;
566+ throw std::runtime_error (ss.str ());
567+ }
568+ Adapter* adapter = adapters[index / 4 ].load ();
569+ if (adapter) {
570+ bool motor = SmallMotor || LargeMotor;
571+ adapter->SetRumble (index % 4 , motor);
572+ }
573+ }
574+
477575int main (int argc, char * argv[]) {
478576 // This should ideally be enabled for the first run of the application.
479577 // Subsequent runs shouldn't matter. If Windows butchers the device orderings,
@@ -484,7 +582,7 @@ int main(int argc, char* argv[]) {
484582 // Then run:
485583 // GameCubeAdapterUnlimited.exe --det
486584 if (argc > 1 && strcmp (argv[1 ], " --det" ) == 0 ) {
487- AdapterThread () .AddDeterministic ();
585+ adapterThread .AddDeterministic ();
488586 return 0 ;
489587 }
490588 std::cout << " Input feeder started" << std::endl;
@@ -497,7 +595,6 @@ int main(int argc, char* argv[]) {
497595 // Start the adapter thread to update inputs.
498596 // Multithreading ensures that polling for new adapters doesn't stall input
499597 // updates.
500- AdapterThread adapterThread = AdapterThread ();
501598 adapterThread.SetupPads ();
502599 std::thread thread (&AdapterThread::run, &adapterThread);
503600
@@ -515,4 +612,4 @@ int main(int argc, char* argv[]) {
515612 thread.join ();
516613
517614 return 0 ;
518- }
615+ }
0 commit comments