Skip to content

Commit d235308

Browse files
committed
hidapi: Add support for NSO GameCube controller via libusb.
Thanks to Nohzockt for the initial libusb init and hidapi polling work!
1 parent fd10eec commit d235308

File tree

6 files changed

+225
-4
lines changed

6 files changed

+225
-4
lines changed

src/hidapi/SDL_hidapi.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,14 @@ static struct
743743
int *actual_length,
744744
unsigned int timeout
745745
);
746+
int (LIBUSB_CALL *bulk_transfer)(
747+
libusb_device_handle *dev_handle,
748+
unsigned char endpoint,
749+
unsigned char *data,
750+
int length,
751+
int *transferred,
752+
unsigned int timeout
753+
);
746754
int (LIBUSB_CALL *handle_events)(libusb_context *ctx);
747755
int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed);
748756
const char * (LIBUSB_CALL *error_name)(int errcode);
@@ -776,6 +784,7 @@ static struct
776784
#define libusb_free_transfer libusb_ctx.free_transfer
777785
#define libusb_control_transfer libusb_ctx.control_transfer
778786
#define libusb_interrupt_transfer libusb_ctx.interrupt_transfer
787+
#define libusb_bulk_transfer libusb_ctx.bulk_transfer
779788
#define libusb_handle_events libusb_ctx.handle_events
780789
#define libusb_handle_events_completed libusb_ctx.handle_events_completed
781790
#define libusb_error_name libusb_ctx.error_name
@@ -843,6 +852,7 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device;
843852
#undef libusb_free_transfer
844853
#undef libusb_control_transfer
845854
#undef libusb_interrupt_transfer
855+
#undef libusb_bulk_transfer
846856
#undef libusb_handle_events
847857
#undef libusb_handle_events_completed
848858
#undef libusb_error_name
@@ -889,7 +899,9 @@ static const struct {
889899
Uint16 vendor;
890900
Uint16 product;
891901
} SDL_libusb_whitelist[] = {
892-
{ 0x057e, 0x0337 } // Nintendo WUP-028, Wii U/Switch GameCube Adapter
902+
{ USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER },
903+
{ USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_PRO },
904+
{ USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER },
893905
};
894906

895907
static bool IsInWhitelist(Uint16 vendor, Uint16 product)
@@ -1212,6 +1224,7 @@ int SDL_hid_init(void)
12121224
LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer)
12131225
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer)
12141226
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer)
1227+
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer)
12151228
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events)
12161229
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed)
12171230
LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name)

src/hidapi/libusb/hid.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,79 @@ static void init_xboxone(libusb_device_handle *device_handle, unsigned short idV
13071307
}
13081308
}
13091309

1310+
static bool is_ns2(unsigned short idVendor, unsigned short idProduct)
1311+
{
1312+
if (idVendor == 0x057e) {
1313+
if (idProduct == 0x2069) {
1314+
return true;
1315+
}
1316+
if (idProduct == 0x2073) {
1317+
return true;
1318+
}
1319+
}
1320+
return false;
1321+
}
1322+
1323+
static bool ns2_find_bulk_out_endpoint(libusb_device_handle* handle, uint8_t* endpoint_out)
1324+
{
1325+
struct libusb_config_descriptor* config;
1326+
if (libusb_get_config_descriptor(libusb_get_device(handle), 0, &config) != 0) {
1327+
return false;
1328+
}
1329+
1330+
for (int i = 0; i < config->bNumInterfaces; i++) {
1331+
const struct libusb_interface* iface = &config->interface[i];
1332+
for (int j = 0; j < iface->num_altsetting; j++) {
1333+
const struct libusb_interface_descriptor* altsetting = &iface->altsetting[j];
1334+
if (altsetting->bInterfaceNumber == 1) {
1335+
for (int k = 0; k < altsetting->bNumEndpoints; k++) {
1336+
const struct libusb_endpoint_descriptor* ep = &altsetting->endpoint[k];
1337+
if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK && (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) {
1338+
*endpoint_out = ep->bEndpointAddress;
1339+
libusb_free_config_descriptor(config);
1340+
return true;
1341+
}
1342+
}
1343+
}
1344+
}
1345+
}
1346+
1347+
libusb_free_config_descriptor(config);
1348+
return false;
1349+
}
1350+
1351+
static void init_ns2(libusb_device_handle *device_handle)
1352+
{
1353+
const unsigned char DEFAULT_REPORT_DATA[] = {
1354+
0x03, 0x91, 0x00, 0x0d, 0x00, 0x08,
1355+
0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
1356+
};
1357+
const unsigned char SET_LED_DATA[] = {
1358+
0x09, 0x91, 0x00, 0x07, 0x00, 0x08,
1359+
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1360+
};
1361+
1362+
uint8_t endpoint_out = 0;
1363+
if (!ns2_find_bulk_out_endpoint(device_handle, &endpoint_out)) {
1364+
return;
1365+
}
1366+
1367+
int transferred;
1368+
libusb_bulk_transfer(device_handle,
1369+
endpoint_out,
1370+
(unsigned char*)DEFAULT_REPORT_DATA,
1371+
sizeof(DEFAULT_REPORT_DATA),
1372+
&transferred,
1373+
1000);
1374+
1375+
libusb_bulk_transfer(device_handle,
1376+
endpoint_out,
1377+
(unsigned char*)SET_LED_DATA,
1378+
sizeof(SET_LED_DATA),
1379+
&transferred,
1380+
1000);
1381+
}
1382+
13101383
static void calculate_device_quirks(hid_device *dev, unsigned short idVendor, unsigned short idProduct)
13111384
{
13121385
static const int VENDOR_SONY = 0x054c;
@@ -1368,6 +1441,11 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa
13681441
init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
13691442
}
13701443

1444+
/* Initialize NSO GameCube controllers */
1445+
if (is_ns2(desc.idVendor, desc.idProduct)) {
1446+
init_ns2(dev->device_handle);
1447+
}
1448+
13711449
/* Store off the string descriptor indexes */
13721450
dev->manufacturer_index = desc.iManufacturer;
13731451
dev->product_index = desc.iProduct;

src/joystick/SDL_gamepad.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,13 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
715715
product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) {
716716
// GameCube driver has 12 buttons and 6 axes
717717
SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
718+
} else if (vendor == USB_VENDOR_NINTENDO &&
719+
(product == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER)) {
720+
// Switch 2 GameCube has additional buttons for ZL and C
721+
SDL_strlcat(mapping_string, "a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:a4,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,misc3:b4,misc4:b12,rightshoulder:b5,righttrigger:a5,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
722+
} else if (vendor == USB_VENDOR_NINTENDO &&
723+
(product == USB_PRODUCT_NINTENDO_SWITCH2_PRO)) {
724+
SDL_strlcat(mapping_string, "a:b1,b:b0,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,rightshoulder:b4,righttrigger:b5,rightx:a2,righty:a3~,start:b6,back:b14,x:b3,y:b2,leftstick:b15,rightstick:b7,paddle1:b18,paddle2:b19,", sizeof(mapping_string));
718725
} else if (vendor == USB_VENDOR_NINTENDO &&
719726
(guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft ||
720727
guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight ||

src/joystick/SDL_joystick.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ static Uint32 initial_gamecube_devices[] = {
483483
MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter
484484
MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter
485485
MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter
486+
MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller
486487
MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller
487488
MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch
488489
MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box}

src/joystick/hidapi/SDL_hidapi_switch2.c

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ static bool HIDAPI_DriverSwitch2_IsEnabled(void)
5151
static bool HIDAPI_DriverSwitch2_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)
5252
{
5353
if (vendor_id == USB_VENDOR_NINTENDO) {
54+
if (product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) {
55+
return true;
56+
}
5457
if (product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) {
5558
return true;
5659
}
@@ -61,7 +64,7 @@ static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, co
6164

6265
static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device)
6366
{
64-
return SDL_Unsupported();
67+
return HIDAPI_JoystickConnected(device, NULL);
6568
}
6669

6770
static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
@@ -75,12 +78,130 @@ static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device,
7578

7679
static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device)
7780
{
78-
return SDL_Unsupported();
81+
const struct {
82+
int byte;
83+
unsigned char mask;
84+
} buttons[] = {
85+
{3, 0x01}, // B
86+
{3, 0x02}, // A
87+
{3, 0x04}, // Y
88+
{3, 0x08}, // X
89+
{3, 0x10}, // R (GameCube R Click)
90+
{3, 0x20}, // ZR (GameCube Z)
91+
{3, 0x40}, // PLUS (GameCube Start)
92+
{3, 0x80}, // RS (not on GameCube)
93+
{4, 0x01}, // DPAD_DOWN
94+
{4, 0x02}, // DPAD_RIGHT
95+
{4, 0x04}, // DPAD_LEFT
96+
{4, 0x08}, // DPAD_UP
97+
{4, 0x10}, // L (GameCube L Click)
98+
{4, 0x20}, // ZL
99+
{4, 0x40}, // MINUS (not on GameCube)
100+
{4, 0x80}, // LS (not on GameCube)
101+
{5, 0x01}, // Home
102+
{5, 0x02}, // Capture
103+
{5, 0x04}, // GR (not on GameCube)
104+
{5, 0x08}, // GL (not on GameCube)
105+
{5, 0x10}, // C
106+
};
107+
108+
SDL_Joystick *joystick = NULL;
109+
if (device->num_joysticks > 0) {
110+
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
111+
}
112+
if (joystick == NULL) {
113+
return true;
114+
}
115+
116+
// Read input packet
117+
118+
Uint8 packet[USB_PACKET_LENGTH];
119+
int size;
120+
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
121+
if (size < 15) {
122+
continue;
123+
}
124+
125+
Uint64 timestamp = SDL_GetTicksNS();
126+
for (size_t i = 0; i < SDL_arraysize(buttons); ++i) {
127+
SDL_SendJoystickButton(
128+
timestamp,
129+
joystick,
130+
(Uint8) i,
131+
(packet[buttons[i].byte] & buttons[i].mask) != 0
132+
);
133+
}
134+
SDL_SendJoystickAxis(
135+
timestamp,
136+
joystick,
137+
SDL_GAMEPAD_AXIS_LEFTX,
138+
(Sint16) HIDAPI_RemapVal(
139+
(float) (packet[6] | ((packet[7] & 0x0F) << 8)),
140+
0,
141+
4096,
142+
SDL_MIN_SINT16,
143+
SDL_MAX_SINT16
144+
)
145+
);
146+
SDL_SendJoystickAxis(
147+
timestamp,
148+
joystick,
149+
SDL_GAMEPAD_AXIS_LEFTY,
150+
(Sint16) HIDAPI_RemapVal(
151+
(float) ((packet[7] >> 4) | (packet[8] << 4)),
152+
0,
153+
4096,
154+
SDL_MIN_SINT16,
155+
SDL_MAX_SINT16
156+
)
157+
);
158+
SDL_SendJoystickAxis(
159+
timestamp,
160+
joystick,
161+
SDL_GAMEPAD_AXIS_RIGHTX,
162+
(Sint16) HIDAPI_RemapVal(
163+
(float) (packet[9] | ((packet[10] & 0x0F) << 8)),
164+
0,
165+
4096,
166+
SDL_MIN_SINT16,
167+
SDL_MAX_SINT16
168+
)
169+
);
170+
SDL_SendJoystickAxis(
171+
timestamp,
172+
joystick,
173+
SDL_GAMEPAD_AXIS_RIGHTY,
174+
(Sint16) HIDAPI_RemapVal(
175+
(float) ((packet[10] >> 4) | (packet[11] << 4)),
176+
0,
177+
4096,
178+
SDL_MIN_SINT16,
179+
SDL_MAX_SINT16
180+
)
181+
);
182+
SDL_SendJoystickAxis(
183+
timestamp,
184+
joystick,
185+
SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
186+
(Sint16) HIDAPI_RemapVal(packet[13], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
187+
);
188+
SDL_SendJoystickAxis(
189+
timestamp,
190+
joystick,
191+
SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
192+
(Sint16) HIDAPI_RemapVal(packet[14], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
193+
);
194+
}
195+
return true;
79196
}
80197

81198
static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
82199
{
83-
return SDL_Unsupported();
200+
// Initialize the joystick capabilities
201+
joystick->nbuttons = 21;
202+
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
203+
204+
return true;
84205
}
85206

86207
static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)

src/joystick/usb_ids.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond
102102
#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007
103103
#define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009
104+
#define USB_PRODUCT_NINTENDO_SWITCH2_PRO 0x2069
104105
#define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER 0x2073
105106
#define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306
106107
#define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330

0 commit comments

Comments
 (0)