فهرست منبع

hidapi: Add support for NSO GameCube controller via libusb.

Thanks to Nohzockt for the initial libusb init and hidapi polling work!
Ethan Lee 11 ماه پیش
والد
کامیت
a798da2ec7
6فایلهای تغییر یافته به همراه225 افزوده شده و 4 حذف شده
  1. 14 1
      src/hidapi/SDL_hidapi.c
  2. 78 0
      src/hidapi/libusb/hid.c
  3. 7 0
      src/joystick/SDL_gamepad.c
  4. 1 0
      src/joystick/SDL_joystick.c
  5. 124 3
      src/joystick/hidapi/SDL_hidapi_switch2.c
  6. 1 0
      src/joystick/usb_ids.h

+ 14 - 1
src/hidapi/SDL_hidapi.c

@@ -752,6 +752,14 @@ static struct
         int *actual_length,
         unsigned int timeout
     );
+    int (LIBUSB_CALL *bulk_transfer)(
+        libusb_device_handle *dev_handle,
+        unsigned char endpoint,
+        unsigned char *data,
+        int length,
+        int *transferred,
+        unsigned int timeout
+    );
     int (LIBUSB_CALL *handle_events)(libusb_context *ctx);
     int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed);
     const char * (LIBUSB_CALL *error_name)(int errcode);
@@ -785,6 +793,7 @@ static struct
 #define libusb_free_transfer                libusb_ctx.free_transfer
 #define libusb_control_transfer             libusb_ctx.control_transfer
 #define libusb_interrupt_transfer           libusb_ctx.interrupt_transfer
+#define libusb_bulk_transfer                libusb_ctx.bulk_transfer
 #define libusb_handle_events                libusb_ctx.handle_events
 #define libusb_handle_events_completed      libusb_ctx.handle_events_completed
 #define libusb_error_name                   libusb_ctx.error_name
@@ -852,6 +861,7 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device;
 #undef libusb_free_transfer
 #undef libusb_control_transfer
 #undef libusb_interrupt_transfer
+#undef libusb_bulk_transfer
 #undef libusb_handle_events
 #undef libusb_handle_events_completed
 #undef libusb_error_name
@@ -898,7 +908,9 @@ static const struct {
     Uint16 vendor;
     Uint16 product;
 } SDL_libusb_whitelist[] = {
-    { 0x057e, 0x0337 } // Nintendo WUP-028, Wii U/Switch GameCube Adapter
+    { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER },
+    { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_PRO },
+    { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER },
 };
 
 static bool IsInWhitelist(Uint16 vendor, Uint16 product)
@@ -1221,6 +1233,7 @@ int SDL_hid_init(void)
             LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer)
             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)
             LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer)
+            LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer)
             LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events)
             LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed)
             LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name)

+ 78 - 0
src/hidapi/libusb/hid.c

@@ -1324,6 +1324,79 @@ static void init_xboxone(libusb_device_handle *device_handle, unsigned short idV
 	}
 }
 
+static bool is_ns2(unsigned short idVendor, unsigned short idProduct)
+{
+	if (idVendor == 0x057e) {
+		if (idProduct == 0x2069) {
+			return true;
+		}
+		if (idProduct == 0x2073) {
+			return true;
+		}
+	}
+	return false;
+}
+
+static bool ns2_find_bulk_out_endpoint(libusb_device_handle* handle, uint8_t* endpoint_out)
+{
+	struct libusb_config_descriptor* config;
+	if (libusb_get_config_descriptor(libusb_get_device(handle), 0, &config) != 0) {
+		return false;
+	}
+
+	for (int i = 0; i < config->bNumInterfaces; i++) {
+		const struct libusb_interface* iface = &config->interface[i];
+		for (int j = 0; j < iface->num_altsetting; j++) {
+			const struct libusb_interface_descriptor* altsetting = &iface->altsetting[j];
+			if (altsetting->bInterfaceNumber == 1) {
+				for (int k = 0; k < altsetting->bNumEndpoints; k++) {
+					const struct libusb_endpoint_descriptor* ep = &altsetting->endpoint[k];
+					if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK && (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) {
+						*endpoint_out = ep->bEndpointAddress;
+						libusb_free_config_descriptor(config);
+						return true;
+					}
+				}
+			}
+		}
+	}
+
+	libusb_free_config_descriptor(config);
+	return false;
+}
+
+static void init_ns2(libusb_device_handle *device_handle)
+{
+	const unsigned char DEFAULT_REPORT_DATA[] = {
+		0x03, 0x91, 0x00, 0x0d, 0x00, 0x08,
+		0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+	};
+	const unsigned char SET_LED_DATA[] = {
+		0x09, 0x91, 0x00, 0x07, 0x00, 0x08,
+		0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	uint8_t endpoint_out = 0;
+	if (!ns2_find_bulk_out_endpoint(device_handle, &endpoint_out)) {
+		return;
+	}
+
+	int transferred;
+	libusb_bulk_transfer(device_handle,
+				endpoint_out,
+				(unsigned char*)DEFAULT_REPORT_DATA,
+				sizeof(DEFAULT_REPORT_DATA),
+				&transferred,
+				1000);
+
+	libusb_bulk_transfer(device_handle,
+				endpoint_out,
+				(unsigned char*)SET_LED_DATA,
+				sizeof(SET_LED_DATA),
+				&transferred,
+				1000);
+}
+
 static void calculate_device_quirks(hid_device *dev, unsigned short idVendor, unsigned short idProduct)
 {
 	static const int VENDOR_SONY = 0x054c;
@@ -1385,6 +1458,11 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa
 		init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
 	}
 
+	/* Initialize NSO GameCube controllers */
+	if (is_ns2(desc.idVendor, desc.idProduct)) {
+		init_ns2(dev->device_handle);
+	}
+
 	/* Store off the string descriptor indexes */
 	dev->manufacturer_index = desc.iManufacturer;
 	dev->product_index      = desc.iProduct;

+ 7 - 0
src/joystick/SDL_gamepad.c

@@ -715,6 +715,13 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
           product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) {
         // GameCube driver has 12 buttons and 6 axes
         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,misc3:b11,misc4:b10,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
+    } else if (vendor == USB_VENDOR_NINTENDO &&
+               (product == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER)) {
+        // Switch 2 GameCube has additional buttons for ZL and C
+        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));
+    } else if (vendor == USB_VENDOR_NINTENDO &&
+               (product == USB_PRODUCT_NINTENDO_SWITCH2_PRO)) {
+        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));
     } else if (vendor == USB_VENDOR_NINTENDO &&
                (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft ||
                 guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight ||

+ 1 - 0
src/joystick/SDL_joystick.c

@@ -484,6 +484,7 @@ static Uint32 initial_gamecube_devices[] = {
     MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter
     MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter
     MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter
+    MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller
     MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller
     MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch
     MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box}

+ 124 - 3
src/joystick/hidapi/SDL_hidapi_switch2.c

@@ -51,6 +51,9 @@ static bool HIDAPI_DriverSwitch2_IsEnabled(void)
 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)
 {
     if (vendor_id == USB_VENDOR_NINTENDO) {
+        if (product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) {
+            return true;
+        }
         if (product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) {
             return true;
         }
@@ -61,7 +64,7 @@ static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, co
 
 static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device)
 {
-    return SDL_Unsupported();
+    return HIDAPI_JoystickConnected(device, NULL);
 }
 
 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,
 
 static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device)
 {
-    return SDL_Unsupported();
+    const struct {
+        int byte;
+        unsigned char mask;
+    } buttons[] = {
+        {3, 0x01}, // B
+        {3, 0x02}, // A
+        {3, 0x04}, // Y
+        {3, 0x08}, // X
+        {3, 0x10}, // R (GameCube R Click)
+        {3, 0x20}, // ZR (GameCube Z)
+        {3, 0x40}, // PLUS (GameCube Start)
+        {3, 0x80}, // RS (not on GameCube)
+        {4, 0x01}, // DPAD_DOWN
+        {4, 0x02}, // DPAD_RIGHT
+        {4, 0x04}, // DPAD_LEFT
+        {4, 0x08}, // DPAD_UP
+        {4, 0x10}, // L (GameCube L Click)
+        {4, 0x20}, // ZL
+        {4, 0x40}, // MINUS (not on GameCube)
+        {4, 0x80}, // LS (not on GameCube)
+        {5, 0x01}, // Home
+        {5, 0x02}, // Capture
+        {5, 0x04}, // GR (not on GameCube)
+        {5, 0x08}, // GL (not on GameCube)
+        {5, 0x10}, // C
+    };
+
+    SDL_Joystick *joystick = NULL;
+    if (device->num_joysticks > 0) {
+        joystick = SDL_GetJoystickFromID(device->joysticks[0]);
+    }
+    if (joystick == NULL) {
+        return true;
+    }
+
+    // Read input packet
+
+    Uint8 packet[USB_PACKET_LENGTH];
+    int size;
+    while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
+        if (size < 15) {
+            continue;
+        }
+
+        Uint64 timestamp = SDL_GetTicksNS();
+        for (size_t i = 0; i < SDL_arraysize(buttons); ++i) {
+            SDL_SendJoystickButton(
+                timestamp,
+                joystick,
+                (Uint8) i,
+                (packet[buttons[i].byte] & buttons[i].mask) != 0
+            );
+        }
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_LEFTX,
+            (Sint16) HIDAPI_RemapVal(
+                (float) (packet[6] | ((packet[7] & 0x0F) << 8)),
+                0,
+                4096,
+                SDL_MIN_SINT16,
+                SDL_MAX_SINT16
+            )
+        );
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_LEFTY,
+            (Sint16) HIDAPI_RemapVal(
+                (float) ((packet[7] >> 4) | (packet[8] << 4)),
+                0,
+                4096,
+                SDL_MIN_SINT16,
+                SDL_MAX_SINT16
+            )
+        );
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_RIGHTX,
+            (Sint16) HIDAPI_RemapVal(
+                (float) (packet[9] | ((packet[10] & 0x0F) << 8)),
+                0,
+                4096,
+                SDL_MIN_SINT16,
+                SDL_MAX_SINT16
+            )
+        );
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_RIGHTY,
+            (Sint16) HIDAPI_RemapVal(
+                (float) ((packet[10] >> 4) | (packet[11] << 4)),
+                0,
+                4096,
+                SDL_MIN_SINT16,
+                SDL_MAX_SINT16
+            )
+        );
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
+            (Sint16) HIDAPI_RemapVal(packet[13], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
+        );
+        SDL_SendJoystickAxis(
+            timestamp,
+            joystick,
+            SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
+            (Sint16) HIDAPI_RemapVal(packet[14], 0, 255, SDL_MIN_SINT16, SDL_MAX_SINT16)
+        );
+    }
+    return true;
 }
 
 static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return SDL_Unsupported();
+    // Initialize the joystick capabilities
+    joystick->nbuttons = 21;
+    joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
+
+    return true;
 }
 
 static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)

+ 1 - 0
src/joystick/usb_ids.h

@@ -103,6 +103,7 @@
 #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR           0x2008 // Used by joycond
 #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT          0x2007
 #define USB_PRODUCT_NINTENDO_SWITCH_PRO                   0x2009
+#define USB_PRODUCT_NINTENDO_SWITCH2_PRO                  0x2069
 #define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER  0x2073
 #define USB_PRODUCT_NINTENDO_WII_REMOTE                   0x0306
 #define USB_PRODUCT_NINTENDO_WII_REMOTE2                  0x0330