Skip to content

Commit 040524d

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 1cddb52 commit 040524d

File tree

5 files changed

+211
-4
lines changed

5 files changed

+211
-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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,75 @@ 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+
{
1314+
return (idProduct == 0x2073);
1315+
}
1316+
return false;
1317+
}
1318+
1319+
static bool ns2_find_bulk_out_endpoint(libusb_device_handle* handle, uint8_t* endpoint_out)
1320+
{
1321+
struct libusb_config_descriptor* config;
1322+
if (libusb_get_config_descriptor(libusb_get_device(handle), 0, &config) != 0) {
1323+
return false;
1324+
}
1325+
1326+
for (int i = 0; i < config->bNumInterfaces; i++) {
1327+
const struct libusb_interface* iface = &config->interface[i];
1328+
for (int j = 0; j < iface->num_altsetting; j++) {
1329+
const struct libusb_interface_descriptor* altsetting = &iface->altsetting[j];
1330+
if (altsetting->bInterfaceNumber == 1) {
1331+
for (int k = 0; k < altsetting->bNumEndpoints; k++) {
1332+
const struct libusb_endpoint_descriptor* ep = &altsetting->endpoint[k];
1333+
if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK && (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) {
1334+
*endpoint_out = ep->bEndpointAddress;
1335+
libusb_free_config_descriptor(config);
1336+
return true;
1337+
}
1338+
}
1339+
}
1340+
}
1341+
}
1342+
1343+
libusb_free_config_descriptor(config);
1344+
return false;
1345+
}
1346+
1347+
static void init_ns2(libusb_device_handle *device_handle)
1348+
{
1349+
const unsigned char DEFAULT_REPORT_DATA[] = {
1350+
0x03, 0x91, 0x00, 0x0d, 0x00, 0x08,
1351+
0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
1352+
};
1353+
const unsigned char SET_LED_DATA[] = {
1354+
0x09, 0x91, 0x00, 0x07, 0x00, 0x08,
1355+
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1356+
};
1357+
1358+
uint8_t endpoint_out = 0;
1359+
if (!ns2_find_bulk_out_endpoint(device_handle, &endpoint_out)) {
1360+
return;
1361+
}
1362+
1363+
int transferred;
1364+
libusb_bulk_transfer(device_handle,
1365+
endpoint_out,
1366+
(unsigned char*)DEFAULT_REPORT_DATA,
1367+
sizeof(DEFAULT_REPORT_DATA),
1368+
&transferred,
1369+
1000);
1370+
1371+
libusb_bulk_transfer(device_handle,
1372+
endpoint_out,
1373+
(unsigned char*)SET_LED_DATA,
1374+
sizeof(SET_LED_DATA),
1375+
&transferred,
1376+
1000);
1377+
}
1378+
13101379
static void calculate_device_quirks(hid_device *dev, unsigned short idVendor, unsigned short idProduct)
13111380
{
13121381
static const int VENDOR_SONY = 0x054c;
@@ -1368,6 +1437,11 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa
13681437
init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
13691438
}
13701439

1440+
/* Initialize NSO GameCube controllers */
1441+
if (is_ns2(desc.idVendor, desc.idProduct)) {
1442+
init_ns2(dev->device_handle);
1443+
}
1444+
13711445
/* Store off the string descriptor indexes */
13721446
dev->manufacturer_index = desc.iManufacturer;
13731447
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
@@ -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: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, co
6161

6262
static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device)
6363
{
64-
return SDL_Unsupported();
64+
return HIDAPI_JoystickConnected(device, NULL);
6565
}
6666

6767
static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
@@ -75,12 +75,128 @@ static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device,
7575

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

81193
static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
82194
{
83-
return SDL_Unsupported();
195+
// Initialize the joystick capabilities
196+
joystick->nbuttons = 21;
197+
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
198+
199+
return true;
84200
}
85201

86202
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)