Skip to content

Commit 3292a84

Browse files
authored
Add rumble support
Add status bits for wired and wireless controllers Fix a bug where all controllers for an adapter are seen as conencted when the grey USB port is attached for rumble
1 parent 7370ed9 commit 3292a84

File tree

1 file changed

+117
-20
lines changed

1 file changed

+117
-20
lines changed

GameCubeAdapterUnlimited/main.cpp

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,17 @@ BOOL WINAPI CtrlHandler(DWORD event) {
3737
struct 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.
220264
std::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+
222271
class 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+
477575
int 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

Comments
 (0)