Skip to content

Switch 2 Controller Support #13327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions VisualC-GDK/SDL/SDL.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steamdeck.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch2.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_wii.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
Expand Down
1 change: 1 addition & 0 deletions VisualC-GDK/SDL/SDL.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steamdeck.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch2.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_wii.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
Expand Down
1 change: 1 addition & 0 deletions VisualC/SDL/SDL.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steamdeck.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch2.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_wii.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
Expand Down
3 changes: 3 additions & 0 deletions VisualC/SDL/SDL.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,9 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch2.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_wii.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
Expand Down
4 changes: 4 additions & 0 deletions Xcode/SDL/SDL.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
A7D8B54523E2514300DCD162 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */; };
A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */; };
A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */; };
A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */; };
A7D8B55723E2514300DCD162 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */; };
A7D8B55D23E2514300DCD162 /* SDL_hidapi_xbox360w.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */; };
A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */; };
Expand Down Expand Up @@ -811,6 +812,7 @@
A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = "<group>"; };
A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = "<group>"; };
A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = "<group>"; };
A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch2.c; sourceTree = "<group>"; };
A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = "<group>"; };
A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360w.c; sourceTree = "<group>"; };
A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamecube.c; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1948,6 +1950,7 @@
F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */,
A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */,
A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */,
A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */,
F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */,
A7D8A7C223E2513E00DCD162 /* SDL_hidapi_xbox360.c */,
A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */,
Expand Down Expand Up @@ -3035,6 +3038,7 @@
F3FA5A222B59ACE000FEAD97 /* yuv_rgb_sse.c in Sources */,
F3C2CB232C5DDDB2004D7998 /* SDL_categories.c in Sources */,
A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */,
A7D8B96223E2514400DCD162 /* SDL_strtokr.c in Sources */,
A7D8BB7523E2514500DCD162 /* SDL_clipboardevents.c in Sources */,
E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions include/SDL3/SDL_hints.h
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,23 @@ extern "C" {
*/
#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED"

/**
* A variable controlling whether the HIDAPI driver for Nintendo Switch 2
* controllers should be used.
*
* The variable can be set to the following values:
*
* - "0": HIDAPI driver is not used.
* - "1": HIDAPI driver is used.
*
* The default is the value of SDL_HINT_JOYSTICK_HIDAPI.
*
* This hint should be set before initializing joysticks and gamepads.
*
* \since This hint is available since SDL 3.4.0.
*/
#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH2 "SDL_JOYSTICK_HIDAPI_SWITCH2"

/**
* A variable controlling whether Nintendo Switch Joy-Con controllers will be
* in vertical mode when using the HIDAPI driver.
Expand Down
15 changes: 14 additions & 1 deletion src/hidapi/SDL_hidapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,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);
Expand Down Expand Up @@ -776,6 +784,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
Expand Down Expand Up @@ -843,6 +852,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
Expand Down Expand Up @@ -889,7 +899,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)
Expand Down Expand Up @@ -1212,6 +1224,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)
Expand Down
78 changes: 78 additions & 0 deletions src/hidapi/libusb/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,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
};
Comment on lines +1353 to +1360
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to use some enums for things like the first byte (the command) and the second byte (the direction), but that's not needed for the initial merge.


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;
Expand Down Expand Up @@ -1368,6 +1441,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;
Expand Down
7 changes: 7 additions & 0 deletions src/joystick/SDL_gamepad.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,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 ||
Expand Down
1 change: 1 addition & 0 deletions src/joystick/SDL_joystick.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,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}
Expand Down
Loading
Loading