1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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.h"
26 #include "SDL_events.h"
27 #include "SDL_timer.h"
28 #include "SDL_haptic.h"
29 #include "SDL_joystick.h"
30 #include "SDL_gamecontroller.h"
31 #include "../../SDL_hints_c.h"
32 #include "../SDL_sysjoystick.h"
33 #include "SDL_hidapijoystick_c.h"
34 #include "SDL_hidapi_rumble.h"
35 
36 
37 #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
38 
39 #define MAX_CONTROLLERS 4
40 
41 typedef struct {
42     SDL_JoystickID joysticks[MAX_CONTROLLERS];
43     Uint8 wireless[MAX_CONTROLLERS];
44     Uint8 min_axis[MAX_CONTROLLERS*SDL_CONTROLLER_AXIS_MAX];
45     Uint8 max_axis[MAX_CONTROLLERS*SDL_CONTROLLER_AXIS_MAX];
46     Uint8 rumbleAllowed[MAX_CONTROLLERS];
47     Uint8 rumble[1+MAX_CONTROLLERS];
48     /* Without this variable, hid_write starts to lag a TON */
49     SDL_bool rumbleUpdate;
50     SDL_bool m_bUseButtonLabels;
51 } SDL_DriverGameCube_Context;
52 
53 static SDL_bool
HIDAPI_DriverGameCube_IsSupportedDevice(const char * name,SDL_GameControllerType type,Uint16 vendor_id,Uint16 product_id,Uint16 version,int interface_number,int interface_class,int interface_subclass,int interface_protocol)54 HIDAPI_DriverGameCube_IsSupportedDevice(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
55 {
56     if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
57         /* Nintendo Co., Ltd.  Wii U GameCube Controller Adapter */
58         return SDL_TRUE;
59     }
60     return SDL_FALSE;
61 }
62 
63 static const char *
HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id,Uint16 product_id)64 HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
65 {
66     return "Nintendo GameCube Controller";
67 }
68 
69 static void
ResetAxisRange(SDL_DriverGameCube_Context * ctx,int joystick_index)70 ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
71 {
72     SDL_memset(&ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX], 128-88, SDL_CONTROLLER_AXIS_MAX);
73     SDL_memset(&ctx->max_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX], 128+88, SDL_CONTROLLER_AXIS_MAX);
74 
75     /* Trigger axes may have a higher resting value */
76     ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX+SDL_CONTROLLER_AXIS_TRIGGERLEFT] = 40;
77     ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX+SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = 40;
78 }
79 
fsel(float fComparand,float fValGE,float fLT)80 static float fsel(float fComparand, float fValGE, float fLT)
81 {
82     return fComparand >= 0 ? fValGE : fLT;
83 }
84 
RemapVal(float val,float A,float B,float C,float D)85 static float RemapVal(float val, float A, float B, float C, float D)
86 {
87     if (A == B) {
88         return fsel(val - B , D , C);
89     }
90     if (val < A) {
91         val = A;
92     }
93     if (val > B) {
94         val = B;
95     }
96     return C + (D - C) * (val - A) / (B - A);
97 }
98 
SDL_GameControllerButtonReportingHintChanged(void * userdata,const char * name,const char * oldValue,const char * hint)99 static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
100 {
101     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
102     ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE);
103 }
104 
RemapButton(SDL_DriverGameCube_Context * ctx,Uint8 button)105 static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button)
106 {
107     if (!ctx->m_bUseButtonLabels) {
108         /* Use button positions */
109         switch (button) {
110         case SDL_CONTROLLER_BUTTON_B:
111             return SDL_CONTROLLER_BUTTON_X;
112         case SDL_CONTROLLER_BUTTON_X:
113             return SDL_CONTROLLER_BUTTON_B;
114         default:
115             break;
116         }
117     }
118     return button;
119 }
120 
121 static SDL_bool
HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device * device)122 HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
123 {
124     SDL_DriverGameCube_Context *ctx;
125     Uint8 packet[37];
126     Uint8 *curSlot;
127     Uint8 i;
128     int size;
129     Uint8 initMagic = 0x13;
130     Uint8 rumbleMagic = 0x11;
131 
132     ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
133     if (!ctx) {
134         SDL_OutOfMemory();
135         return SDL_FALSE;
136     }
137 
138     device->dev = hid_open_path(device->path, 0);
139     if (!device->dev) {
140         SDL_free(ctx);
141         SDL_SetError("Couldn't open %s", device->path);
142         return SDL_FALSE;
143     }
144     device->context = ctx;
145 
146     ctx->joysticks[0] = -1;
147     ctx->joysticks[1] = -1;
148     ctx->joysticks[2] = -1;
149     ctx->joysticks[3] = -1;
150     ctx->rumble[0] = rumbleMagic;
151 
152     /* This is all that's needed to initialize the device. Really! */
153     if (hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
154         SDL_SetError("Couldn't initialize WUP-028");
155         goto error;
156     }
157 
158     /* Wait for the adapter to initialize */
159     SDL_Delay(10);
160 
161     /* Add all the applicable joysticks */
162     while ((size = hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
163         if (size < 37 || packet[0] != 0x21) {
164             continue; /* Nothing to do yet...? */
165         }
166 
167         /* Go through all 4 slots */
168         curSlot = packet + 1;
169         for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
170             ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
171 
172             /* Only allow rumble if the adapter's second USB cable is connected */
173             ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
174 
175             if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
176                 if (ctx->joysticks[i] == -1) {
177                     ResetAxisRange(ctx, i);
178                     HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE);
179                 }
180             } else {
181                 if (ctx->joysticks[i] != -1) {
182                     HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE);
183                     ctx->joysticks[i] = -1;
184                 }
185                 continue;
186             }
187         }
188     }
189 
190     SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
191                         SDL_GameControllerButtonReportingHintChanged, ctx);
192 
193     return SDL_TRUE;
194 
195 error:
196     if (device->dev) {
197         hid_close(device->dev);
198         device->dev = NULL;
199     }
200     if (device->context) {
201         SDL_free(device->context);
202         device->context = NULL;
203     }
204     return SDL_FALSE;
205 }
206 
207 static int
HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device * device,SDL_JoystickID instance_id)208 HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
209 {
210     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
211     Uint8 i;
212 
213     for (i = 0; i < 4; ++i) {
214         if (instance_id == ctx->joysticks[i]) {
215             return i;
216         }
217     }
218     return -1;
219 }
220 
221 static void
HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device * device,SDL_JoystickID instance_id,int player_index)222 HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
223 {
224 }
225 
226 static SDL_bool
HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device * device)227 HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
228 {
229     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
230     SDL_Joystick *joystick;
231     Uint8 packet[37];
232     Uint8 *curSlot;
233     Uint8 i;
234     Sint16 axis_value;
235     int size;
236 
237     /* Read input packet */
238     while ((size = hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
239         if (size < 37 || packet[0] != 0x21) {
240             continue; /* Nothing to do right now...? */
241         }
242 
243         /* Go through all 4 slots */
244         curSlot = packet + 1;
245         for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
246             ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
247 
248             /* Only allow rumble if the adapter's second USB cable is connected */
249             ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
250 
251             if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
252                 if (ctx->joysticks[i] == -1) {
253                     ResetAxisRange(ctx, i);
254                     HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE);
255                 }
256                 joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
257 
258                 /* Hasn't been opened yet, skip */
259                 if (joystick == NULL) {
260                     continue;
261                 }
262             } else {
263                 if (ctx->joysticks[i] != -1) {
264                     HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE);
265                     ctx->joysticks[i] = -1;
266                 }
267                 continue;
268             }
269 
270             #define READ_BUTTON(off, flag, button) \
271                 SDL_PrivateJoystickButton( \
272                     joystick, \
273                     RemapButton(ctx, button), \
274                     (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
275                 );
276             READ_BUTTON(1, 0x01, 0) /* A */
277             READ_BUTTON(1, 0x04, 1) /* B */
278             READ_BUTTON(1, 0x02, 2) /* X */
279             READ_BUTTON(1, 0x08, 3) /* Y */
280             READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
281             READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
282             READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
283             READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
284             READ_BUTTON(2, 0x01, 8) /* START */
285             READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
286             /* These two buttons are for the bottoms of the analog triggers.
287              * More than likely, you're going to want to read the axes instead!
288              * -flibit
289              */
290             READ_BUTTON(2, 0x04, 10) /* TRIGGERRIGHT */
291             READ_BUTTON(2, 0x08, 11) /* TRIGGERLEFT */
292             #undef READ_BUTTON
293 
294             #define READ_AXIS(off, axis) \
295                 if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) \
296                 if (curSlot[off] < ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis]) ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis] = curSlot[off]; \
297                 if (curSlot[off] > ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis]) ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis] = curSlot[off]; \
298                 axis_value = (Sint16)(RemapVal(curSlot[off], ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis], ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis], SDL_MIN_SINT16, SDL_MAX_SINT16)); \
299                 SDL_PrivateJoystickAxis( \
300                     joystick, \
301                     axis, axis_value \
302                 );
303             READ_AXIS(3, SDL_CONTROLLER_AXIS_LEFTX)
304             READ_AXIS(4, SDL_CONTROLLER_AXIS_LEFTY)
305             READ_AXIS(5, SDL_CONTROLLER_AXIS_RIGHTX)
306             READ_AXIS(6, SDL_CONTROLLER_AXIS_RIGHTY)
307             READ_AXIS(7, SDL_CONTROLLER_AXIS_TRIGGERLEFT)
308             READ_AXIS(8, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
309             #undef READ_AXIS
310         }
311     }
312 
313     /* Write rumble packet */
314     if (ctx->rumbleUpdate) {
315         SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
316         ctx->rumbleUpdate = SDL_FALSE;
317     }
318 
319     /* If we got here, nothing bad happened! */
320     return SDL_TRUE;
321 }
322 
323 static SDL_bool
HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick)324 HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
325 {
326     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
327     Uint8 i;
328     for (i = 0; i < MAX_CONTROLLERS; i += 1) {
329         if (joystick->instance_id == ctx->joysticks[i]) {
330             joystick->nbuttons = 12;
331             joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
332             joystick->epowerlevel = ctx->wireless[i] ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
333             return SDL_TRUE;
334         }
335     }
336     return SDL_FALSE; /* Should never get here! */
337 }
338 
339 static int
HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick,Uint16 low_frequency_rumble,Uint16 high_frequency_rumble)340 HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
341 {
342     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
343     Uint8 i, val;
344     for (i = 0; i < MAX_CONTROLLERS; i += 1) {
345         if (joystick->instance_id == ctx->joysticks[i]) {
346             if (ctx->wireless[i]) {
347                 return SDL_SetError("Ninteno GameCube WaveBird controllers do not support rumble");
348             }
349             if (!ctx->rumbleAllowed[i]) {
350                 return SDL_SetError("Second USB cable for WUP-028 not connected");
351             }
352             val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
353             if (val != ctx->rumble[i + 1]) {
354                 ctx->rumble[i + 1] = val;
355                 ctx->rumbleUpdate = SDL_TRUE;
356             }
357             return 0;
358         }
359     }
360 
361     /* Should never get here! */
362     SDL_SetError("Couldn't find joystick");
363     return -1;
364 }
365 
366 static void
HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick)367 HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
368 {
369     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
370 
371     /* Stop rumble activity */
372     if (ctx->rumbleUpdate) {
373         SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
374         ctx->rumbleUpdate = SDL_FALSE;
375     }
376 }
377 
378 static void
HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device * device)379 HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
380 {
381     SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
382 
383     hid_close(device->dev);
384     device->dev = NULL;
385 
386     SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
387                         SDL_GameControllerButtonReportingHintChanged, ctx);
388 
389     SDL_free(device->context);
390     device->context = NULL;
391 }
392 
393 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
394 {
395     SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
396     SDL_TRUE,
397     HIDAPI_DriverGameCube_IsSupportedDevice,
398     HIDAPI_DriverGameCube_GetDeviceName,
399     HIDAPI_DriverGameCube_InitDevice,
400     HIDAPI_DriverGameCube_GetDevicePlayerIndex,
401     HIDAPI_DriverGameCube_SetDevicePlayerIndex,
402     HIDAPI_DriverGameCube_UpdateDevice,
403     HIDAPI_DriverGameCube_OpenJoystick,
404     HIDAPI_DriverGameCube_RumbleJoystick,
405     HIDAPI_DriverGameCube_CloseJoystick,
406     HIDAPI_DriverGameCube_FreeDevice,
407     NULL,
408 };
409 
410 #endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
411 
412 #endif /* SDL_JOYSTICK_HIDAPI */
413 
414 /* vi: set ts=4 sw=4 expandtab: */
415