Skip to content

Commit 27346e0

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 a80c467 commit 27346e0

File tree

5 files changed

+155
-4
lines changed

5 files changed

+155
-4
lines changed

src/hidapi/SDL_hidapi.c

Lines changed: 13 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,8 @@ 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+
{ 0x057e, 0x0337 }, // Nintendo WUP-028, Wii U/Switch GameCube Adapter
903+
{ 0x057e, 0x2073 }, // Nintendo Switch 2 NSO GameCube Controller
893904
};
894905

895906
static bool IsInWhitelist(Uint16 vendor, Uint16 product)
@@ -1212,6 +1223,7 @@ int SDL_hid_init(void)
12121223
LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer)
12131224
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)
12141225
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer)
1226+
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer)
12151227
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events)
12161228
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed)
12171229
LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name)

src/hidapi/libusb/hid.c

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

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

1431+
/* Initialize NSO GameCube controllers */
1432+
if ((desc.idVendor == 0x057e) && (desc.idProduct == 0x2073)) {
1433+
init_nsogcn(dev->device_handle);
1434+
}
1435+
13711436
/* Store off the string descriptor indexes */
13721437
dev->manufacturer_index = desc.iManufacturer;
13731438
dev->product_index = desc.iProduct;

src/joystick/SDL_gamepad.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,10 @@ 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:17,misc2:20,rightshoulder:b5,righttrigger:a5,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
718722
} else if (vendor == USB_VENDOR_NINTENDO &&
719723
(guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft ||
720724
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
@@ -487,6 +487,7 @@ static Uint32 initial_gamecube_devices[] = {
487487
MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch
488488
MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box}
489489
MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style
490+
MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller
490491
};
491492
static SDL_vidpid_list gamecube_devices = {
492493
SDL_HINT_JOYSTICK_GAMECUBE_DEVICES, 0, 0, NULL,

src/joystick/hidapi/SDL_hidapi_switch2.c

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, co
6161

6262
static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device)
6363
{
64-
return false;
64+
HIDAPI_SetDeviceName(device, "Nintendo Switch 2 GameCube Controller");
65+
HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER);
66+
return HIDAPI_JoystickConnected(device, NULL);
6567
}
6668

6769
static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
@@ -75,12 +77,79 @@ static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device,
7577

7678
static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device)
7779
{
78-
return false;
80+
const struct {
81+
int byte;
82+
unsigned char mask;
83+
} buttons[] = {
84+
{3, 0x01}, // B
85+
{3, 0x02}, // A
86+
{3, 0x04}, // Y
87+
{3, 0x08}, // X
88+
{3, 0x10}, // R (GameCube R Click)
89+
{3, 0x20}, // ZR (GameCube Z)
90+
{3, 0x40}, // PLUS (GameCube Start)
91+
{3, 0x80}, // RS (not on GameCube)
92+
{4, 0x01}, // DPAD_DOWN
93+
{4, 0x02}, // DPAD_RIGHT
94+
{4, 0x04}, // DPAD_LEFT
95+
{4, 0x08}, // DPAD_UP
96+
{4, 0x10}, // L (GameCube L Click)
97+
{4, 0x20}, // ZL
98+
{4, 0x40}, // MINUS (not on GameCube)
99+
{4, 0x80}, // LS (not on GameCube)
100+
{5, 0x01}, // Home
101+
{5, 0x02}, // Capture
102+
{5, 0x04}, // GR (not on GameCube)
103+
{5, 0x08}, // GL (not on GameCube)
104+
{5, 0x10}, // C
105+
};
106+
107+
SDL_Joystick *joystick = NULL;
108+
if (device->num_joysticks > 0) {
109+
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
110+
}
111+
if (joystick == NULL) {
112+
return true;
113+
}
114+
115+
// Read input packet
116+
117+
Uint8 packet[USB_PACKET_LENGTH];
118+
int size;
119+
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
120+
if (size > 14) {
121+
Uint64 timestamp = SDL_GetTicksNS();
122+
for (size_t i = 0; i < SDL_arraysize(buttons); ++i) {
123+
SDL_SendJoystickButton(
124+
timestamp,
125+
joystick,
126+
(Uint8) i,
127+
(packet[buttons[i].byte] & buttons[i].mask) != 0);
128+
}
129+
SDL_SendJoystickAxis(
130+
timestamp,
131+
joystick,
132+
SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
133+
(Sint16) HIDAPI_RemapVal(packet[13], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
134+
);
135+
SDL_SendJoystickAxis(
136+
timestamp,
137+
joystick,
138+
SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
139+
(Sint16) HIDAPI_RemapVal(packet[14], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
140+
);
141+
}
142+
}
143+
return true;
79144
}
80145

81146
static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
82147
{
83-
return false;
148+
// Initialize the joystick capabilities
149+
joystick->nbuttons = 21;
150+
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
151+
152+
return true;
84153
}
85154

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

0 commit comments

Comments
 (0)