1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29#include "../../hidapi/SDL_hidapi_c.h"
30
31#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
32
33// Define this if you want to log all packets from the controller
34// #define DEBUG_GAMECUBE_PROTOCOL
35
36#define MAX_CONTROLLERS 4
37
38typedef struct
39{
40 bool pc_mode;
41 SDL_JoystickID joysticks[MAX_CONTROLLERS];
42 Uint8 wireless[MAX_CONTROLLERS];
43 Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
44 Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
45 Uint8 rumbleAllowed[MAX_CONTROLLERS];
46 Uint8 rumble[1 + MAX_CONTROLLERS];
47 // Without this variable, hid_write starts to lag a TON
48 bool rumbleUpdate;
49 bool useRumbleBrake;
50} SDL_DriverGameCube_Context;
51
52static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata)
53{
54 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
55}
56
57static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata)
58{
59 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
60}
61
62static bool HIDAPI_DriverGameCube_IsEnabled(void)
63{
64 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
65 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
66 SDL_HIDAPI_DEFAULT));
67}
68
69static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
70{
71 if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
72 // Nintendo Co., Ltd. Wii U GameCube Controller Adapter
73 return true;
74 }
75 if (vendor_id == USB_VENDOR_DRAGONRISE &&
76 (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
77 product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) {
78 // EVORETRO GameCube Controller Adapter
79 return true;
80 }
81 return false;
82}
83
84static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
85{
86 SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT);
87 SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT);
88
89 // Trigger axes may have a higher resting value
90 ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40;
91 ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40;
92}
93
94static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
95{
96 if (hint) {
97 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
98 ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false);
99 }
100}
101
102static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
103{
104 SDL_DriverGameCube_Context *ctx;
105 Uint8 packet[37];
106 Uint8 *curSlot;
107 Uint8 i;
108 int size;
109 Uint8 initMagic = 0x13;
110 Uint8 rumbleMagic = 0x11;
111
112#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
113 SDL_EnableGameCubeAdaptors();
114#endif
115
116 ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
117 if (!ctx) {
118 return false;
119 }
120 device->context = ctx;
121
122 ctx->joysticks[0] = 0;
123 ctx->joysticks[1] = 0;
124 ctx->joysticks[2] = 0;
125 ctx->joysticks[3] = 0;
126 ctx->rumble[0] = rumbleMagic;
127 ctx->useRumbleBrake = false;
128
129 if (device->vendor_id != USB_VENDOR_NINTENDO) {
130 ctx->pc_mode = true;
131 }
132
133 if (ctx->pc_mode) {
134 for (i = 0; i < MAX_CONTROLLERS; ++i) {
135 ResetAxisRange(ctx, i);
136 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
137 }
138 } else {
139 // This is all that's needed to initialize the device. Really!
140 if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
141 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
142 "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
143 return false;
144 }
145
146 // Wait for the adapter to initialize
147 SDL_Delay(10);
148
149 // Add all the applicable joysticks
150 while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
151#ifdef DEBUG_GAMECUBE_PROTOCOL
152 HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
153#endif
154 if (size < 37 || packet[0] != 0x21) {
155 continue; // Nothing to do yet...?
156 }
157
158 // Go through all 4 slots
159 curSlot = packet + 1;
160 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
161 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
162
163 // Only allow rumble if the adapter's second USB cable is connected
164 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
165
166 if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
167 if (ctx->joysticks[i] == 0) {
168 ResetAxisRange(ctx, i);
169 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
170 }
171 } else {
172 if (ctx->joysticks[i] != 0) {
173 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
174 ctx->joysticks[i] = 0;
175 }
176 continue;
177 }
178 }
179 }
180 }
181
182 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
183 SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
184
185 HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
186
187 return true;
188}
189
190static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
191{
192 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
193 Uint8 i;
194
195 for (i = 0; i < 4; ++i) {
196 if (instance_id == ctx->joysticks[i]) {
197 return i;
198 }
199 }
200 return -1;
201}
202
203static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
204{
205}
206
207static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size)
208{
209 SDL_Joystick *joystick;
210 Uint8 i, v;
211 Sint16 axis_value;
212 Uint64 timestamp = SDL_GetTicksNS();
213
214 if (size != 10) {
215 return; // How do we handle this packet?
216 }
217
218 i = packet[0] - 1;
219 if (i >= MAX_CONTROLLERS) {
220 return; // How do we handle this packet?
221 }
222
223 joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
224 if (!joystick) {
225 // Hasn't been opened yet, skip
226 return;
227 }
228
229#define READ_BUTTON(off, flag, button) \
230 SDL_SendJoystickButton( \
231 timestamp, \
232 joystick, \
233 button, \
234 ((packet[off] & flag) != 0));
235 READ_BUTTON(1, 0x02, 0) // A
236 READ_BUTTON(1, 0x04, 1) // B
237 READ_BUTTON(1, 0x08, 3) // Y
238 READ_BUTTON(1, 0x01, 2) // X
239 READ_BUTTON(2, 0x80, 4) // DPAD_LEFT
240 READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT
241 READ_BUTTON(2, 0x40, 6) // DPAD_DOWN
242 READ_BUTTON(2, 0x10, 7) // DPAD_UP
243 READ_BUTTON(2, 0x02, 8) // START
244 READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER
245 /* These two buttons are for the bottoms of the analog triggers.
246 * More than likely, you're going to want to read the axes instead!
247 * -flibit
248 */
249 READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT
250 READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT
251#undef READ_BUTTON
252
253#define READ_AXIS(off, axis, invert) \
254 v = invert ? (0xff - packet[off]) : packet[off]; \
255 if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
256 ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
257 if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
258 ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
259 axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
260 SDL_SendJoystickAxis( \
261 timestamp, \
262 joystick, \
263 axis, axis_value);
264 READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0)
265 READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1)
266 READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0)
267 READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1)
268 READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0)
269 READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0)
270#undef READ_AXIS
271}
272
273static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size)
274{
275 SDL_Joystick *joystick;
276 Uint8 *curSlot;
277 Uint8 i;
278 Sint16 axis_value;
279 Uint64 timestamp = SDL_GetTicksNS();
280
281 if (size < 37 || packet[0] != 0x21) {
282 return; // Nothing to do right now...?
283 }
284
285 // Go through all 4 slots
286 curSlot = packet + 1;
287 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
288 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
289
290 // Only allow rumble if the adapter's second USB cable is connected
291 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
292
293 if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
294 if (ctx->joysticks[i] == 0) {
295 ResetAxisRange(ctx, i);
296 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
297 }
298 joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
299
300 // Hasn't been opened yet, skip
301 if (!joystick) {
302 continue;
303 }
304 } else {
305 if (ctx->joysticks[i] != 0) {
306 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
307 ctx->joysticks[i] = 0;
308 }
309 continue;
310 }
311
312#define READ_BUTTON(off, flag, button) \
313 SDL_SendJoystickButton( \
314 timestamp, \
315 joystick, \
316 button, \
317 ((curSlot[off] & flag) != 0));
318 READ_BUTTON(1, 0x01, 0) // A
319 READ_BUTTON(1, 0x02, 1) // B
320 READ_BUTTON(1, 0x04, 2) // X
321 READ_BUTTON(1, 0x08, 3) // Y
322 READ_BUTTON(1, 0x10, 4) // DPAD_LEFT
323 READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT
324 READ_BUTTON(1, 0x40, 6) // DPAD_DOWN
325 READ_BUTTON(1, 0x80, 7) // DPAD_UP
326 READ_BUTTON(2, 0x01, 8) // START
327 READ_BUTTON(2, 0x02, 9) // RIGHTSHOULDER
328 /* These two buttons are for the bottoms of the analog triggers.
329 * More than likely, you're going to want to read the axes instead!
330 * -flibit
331 */
332 READ_BUTTON(2, 0x04, 10) // TRIGGERRIGHT
333 READ_BUTTON(2, 0x08, 11) // TRIGGERLEFT
334#undef READ_BUTTON
335
336#define READ_AXIS(off, axis) \
337 if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
338 ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
339 if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
340 ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
341 axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
342 SDL_SendJoystickAxis( \
343 timestamp, \
344 joystick, \
345 axis, axis_value);
346 READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX)
347 READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY)
348 READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX)
349 READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY)
350 READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
351 READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
352#undef READ_AXIS
353 }
354}
355
356static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
357{
358 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
359 Uint8 packet[USB_PACKET_LENGTH];
360 int size;
361
362 // Read input packet
363 while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
364#ifdef DEBUG_GAMECUBE_PROTOCOL
365 HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
366#endif
367 if (ctx->pc_mode) {
368 HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size);
369 } else {
370 HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size);
371 }
372 }
373
374 // Write rumble packet
375 if (ctx->rumbleUpdate) {
376 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
377 ctx->rumbleUpdate = false;
378 }
379
380 // If we got here, nothing bad happened!
381 return true;
382}
383
384static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
385{
386 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
387 Uint8 i;
388
389 SDL_AssertJoysticksLocked();
390
391 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
392 if (joystick->instance_id == ctx->joysticks[i]) {
393 joystick->nbuttons = 12;
394 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
395 if (ctx->wireless[i]) {
396 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
397 } else {
398 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
399 }
400 return true;
401 }
402 }
403 return false; // Should never get here!
404}
405
406static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
407{
408 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
409 Uint8 i, val;
410
411 SDL_AssertJoysticksLocked();
412
413 if (ctx->pc_mode) {
414 return SDL_Unsupported();
415 }
416
417 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
418 if (joystick->instance_id == ctx->joysticks[i]) {
419 if (ctx->wireless[i]) {
420 return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
421 }
422 if (!ctx->rumbleAllowed[i]) {
423 return SDL_SetError("Second USB cable for WUP-028 not connected");
424 }
425 if (ctx->useRumbleBrake) {
426 if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
427 val = 0; // if only low is 0 we want to do a regular stop
428 } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
429 val = 2; // if both frequencies are 0 we want to do a hard stop
430 } else {
431 val = 1; // normal rumble
432 }
433 } else {
434 val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
435 }
436 if (val != ctx->rumble[i + 1]) {
437 ctx->rumble[i + 1] = val;
438 ctx->rumbleUpdate = true;
439 }
440 return true;
441 }
442 }
443
444 // Should never get here!
445 return SDL_SetError("Couldn't find joystick");
446}
447
448static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
449{
450 return SDL_Unsupported();
451}
452
453static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
454{
455 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
456 Uint32 result = 0;
457
458 SDL_AssertJoysticksLocked();
459
460 if (!ctx->pc_mode) {
461 Uint8 i;
462
463 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
464 if (joystick->instance_id == ctx->joysticks[i]) {
465 if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
466 result |= SDL_JOYSTICK_CAP_RUMBLE;
467 break;
468 }
469 }
470 }
471 }
472
473 return result;
474}
475
476static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
477{
478 return SDL_Unsupported();
479}
480
481static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
482{
483 return SDL_Unsupported();
484}
485
486static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
487{
488 return SDL_Unsupported();
489}
490
491static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
492{
493 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
494
495 // Stop rumble activity
496 if (ctx->rumbleUpdate) {
497 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
498 ctx->rumbleUpdate = false;
499 }
500}
501
502static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
503{
504 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
505
506 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
507 SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
508}
509
510SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = {
511 SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
512 true,
513 HIDAPI_DriverGameCube_RegisterHints,
514 HIDAPI_DriverGameCube_UnregisterHints,
515 HIDAPI_DriverGameCube_IsEnabled,
516 HIDAPI_DriverGameCube_IsSupportedDevice,
517 HIDAPI_DriverGameCube_InitDevice,
518 HIDAPI_DriverGameCube_GetDevicePlayerIndex,
519 HIDAPI_DriverGameCube_SetDevicePlayerIndex,
520 HIDAPI_DriverGameCube_UpdateDevice,
521 HIDAPI_DriverGameCube_OpenJoystick,
522 HIDAPI_DriverGameCube_RumbleJoystick,
523 HIDAPI_DriverGameCube_RumbleJoystickTriggers,
524 HIDAPI_DriverGameCube_GetJoystickCapabilities,
525 HIDAPI_DriverGameCube_SetJoystickLED,
526 HIDAPI_DriverGameCube_SendJoystickEffect,
527 HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
528 HIDAPI_DriverGameCube_CloseJoystick,
529 HIDAPI_DriverGameCube_FreeDevice,
530};
531
532#endif // SDL_JOYSTICK_HIDAPI_GAMECUBE
533
534#endif // SDL_JOYSTICK_HIDAPI
535