From d819106c65f23b666ff281c01c168d734a88a17b Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 14 Oct 2025 09:48:03 -0700 Subject: [PATCH] Try matching gamepad mappings with CRC first Fixes https://github.com/libsdl-org/SDL/issues/13874 --- src/joystick/SDL_gamepad.c | 16 ++++- test/testautomation_joystick.c | 114 ++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index c4b7ade14b..d2e2ca3274 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -1372,7 +1372,8 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool { GamepadMapping_t *mapping; - mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping); + // Try first with an exact match on version and CRC + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, true); if (mapping) { return mapping; } @@ -1382,10 +1383,19 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool return NULL; } - // Try harder to get the best match, or create a mapping + // Try without CRC match + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, false); + if (mapping) { + return mapping; + } + // Try without version match if (SDL_JoystickGUIDUsesVersion(guid)) { - // Try again, ignoring the version + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, true); + if (mapping) { + return mapping; + } + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false); if (mapping) { return mapping; diff --git a/test/testautomation_joystick.c b/test/testautomation_joystick.c index bce6c7f2d1..13ad53c1dd 100644 --- a/test/testautomation_joystick.c +++ b/test/testautomation_joystick.c @@ -9,6 +9,24 @@ /* ================= Test Case Implementation ================== */ +/* Fixture */ + +/* Create a 32-bit writable surface for blitting tests */ +static void SDLCALL joystickSetUp(void **arg) +{ + SDL_InitSubSystem(SDL_INIT_GAMEPAD); + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); +} + +static void SDLCALL joystickTearDown(void *arg) +{ + SDL_ResetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS); + + SDL_QuitSubSystem(SDL_INIT_GAMEPAD); +} + + /* Test case functions */ /** @@ -16,23 +34,19 @@ * * \sa SDL_AttachVirtualJoystick */ -static int SDLCALL TestVirtualJoystick(void *arg) +static int SDLCALL joystick_testVirtual(void *arg) { SDL_VirtualJoystickDesc desc; SDL_Joystick *joystick = NULL; SDL_Gamepad *gamepad = NULL; SDL_JoystickID device_id; - SDLTest_AssertCheck(SDL_InitSubSystem(SDL_INIT_GAMEPAD), "SDL_InitSubSystem(SDL_INIT_GAMEPAD)"); - - SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); - SDL_INIT_INTERFACE(&desc); desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; desc.naxes = SDL_GAMEPAD_AXIS_COUNT; desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; desc.vendor_id = USB_VENDOR_NVIDIA; - desc.product_id = USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104; + desc.product_id = USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103; desc.name = "Virtual NVIDIA SHIELD Controller"; device_id = SDL_AttachVirtualJoystick(&desc); SDLTest_AssertCheck(device_id > 0, "SDL_AttachVirtualJoystick() -> %" SDL_PRIs32 " (expected > 0)", device_id); @@ -111,7 +125,7 @@ static int SDLCALL TestVirtualJoystick(void *arg) } /* Set an explicit mapping with a different name */ - SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Gamepad,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff008316550900001072000000007601,Virtual Gamepad,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); { const char *name = SDL_GetGamepadName(gamepad); SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Gamepad") == 0, "SDL_GetGamepadName() ->\"%s\" (expected \"%s\")", name, "Virtual Gamepad"); @@ -133,7 +147,7 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == false"); /* Set an explicit mapping with legacy GameCube style buttons */ - SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo GameCube,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,"); + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff008316550900001072000000007601,Virtual Nintendo GameCube,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,"); { const char *name = SDL_GetGamepadName(gamepad); SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Nintendo GameCube") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual Nintendo GameCube"); @@ -150,7 +164,7 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == false"); /* Set an explicit mapping with legacy Nintendo style buttons */ - SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff008316550900001072000000007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); { const char *name = SDL_GetGamepadName(gamepad); SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Nintendo Gamepad") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual Nintendo Gamepad"); @@ -167,7 +181,7 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == false"); /* Set an explicit mapping with PS4 style buttons */ - SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual PS4 Gamepad,type:ps4,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff008316550900001072000000007601,Virtual PS4 Gamepad,type:ps4,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); { const char *name = SDL_GetGamepadName(gamepad); SDLTest_AssertCheck(SDL_strcmp(name, "Virtual PS4 Gamepad") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual PS4 Gamepad"); @@ -192,9 +206,75 @@ static int SDLCALL TestVirtualJoystick(void *arg) } SDLTest_AssertCheck(!SDL_IsJoystickVirtual(device_id), "!SDL_IsJoystickVirtual()"); - SDL_ResetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS); + return TEST_COMPLETED; +} - SDL_QuitSubSystem(SDL_INIT_GAMEPAD); +/** + * Check gamepad mappings + */ +static int SDLCALL joystick_testMappings(void *arg) +{ + SDL_VirtualJoystickDesc desc; + SDL_Gamepad *gamepad = NULL; + SDL_JoystickID device_id; + + /* Add a mapping for the virtual controller in advance */ + SDL_AddGamepadMapping("ff000000550900001472000000007601,Virtual Gamepad,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + + SDL_INIT_INTERFACE(&desc); + desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; + desc.naxes = SDL_GAMEPAD_AXIS_COUNT; + desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; + desc.vendor_id = USB_VENDOR_NVIDIA; + desc.product_id = USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104; + desc.name = "Virtual NVIDIA SHIELD Controller"; + device_id = SDL_AttachVirtualJoystick(&desc); + SDLTest_AssertCheck(device_id > 0, "SDL_AttachVirtualJoystick() -> %" SDL_PRIs32 " (expected > 0)", device_id); + SDLTest_AssertCheck(SDL_IsJoystickVirtual(device_id), "SDL_IsJoystickVirtual()"); + + gamepad = SDL_OpenGamepad(device_id); + SDLTest_AssertCheck(gamepad != NULL, "SDL_OpenGamepad() succeeded"); + if (gamepad) { + /* Verify that the gamepad picked up the predefined mapping */ + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Gamepad") == 0, "SDL_GetGamepadName() ->\"%s\" (expected \"%s\")", name, "Virtual Gamepad"); + } + + /* Verify that the gamepad picks up a new mapping with no CRC */ + SDL_AddGamepadMapping("ff000000550900001472000000007601,Virtual Gamepad V2,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Gamepad V2") == 0, "SDL_GetGamepadName() ->\"%s\" (expected \"%s\")", name, "Virtual Gamepad V2"); + } + + /* Verify that the gamepad picks up a new mapping with valid CRC */ + SDL_AddGamepadMapping("ff008316550900001472000000007601,Virtual Gamepad V3,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,"); + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Gamepad V3") == 0, "SDL_GetGamepadName() ->\"%s\" (expected \"%s\")", name, "Virtual Gamepad V3"); + } + + SDL_CloseGamepad(gamepad); + } + SDLTest_AssertCheck(SDL_DetachVirtualJoystick(device_id), "SDL_DetachVirtualJoystick()"); + + /* Try matching mappings with a new CRC */ + desc.name = "Virtual NVIDIA SHIELD Controller V2"; + device_id = SDL_AttachVirtualJoystick(&desc); + SDLTest_AssertCheck(device_id > 0, "SDL_AttachVirtualJoystick() -> %" SDL_PRIs32 " (expected > 0)", device_id); + SDLTest_AssertCheck(SDL_IsJoystickVirtual(device_id), "SDL_IsJoystickVirtual()"); + + gamepad = SDL_OpenGamepad(device_id); + SDLTest_AssertCheck(gamepad != NULL, "SDL_OpenGamepad() succeeded"); + if (gamepad) { + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Gamepad V2") == 0, "SDL_GetGamepadName() ->\"%s\" (expected \"%s\")", name, "Virtual Gamepad V2"); + } + SDL_CloseGamepad(gamepad); + } + SDLTest_AssertCheck(SDL_DetachVirtualJoystick(device_id), "SDL_DetachVirtualJoystick()"); return TEST_COMPLETED; } @@ -203,19 +283,23 @@ static int SDLCALL TestVirtualJoystick(void *arg) /* Joystick routine test cases */ static const SDLTest_TestCaseReference joystickTest1 = { - TestVirtualJoystick, "TestVirtualJoystick", "Test virtual joystick functionality", TEST_ENABLED + joystick_testVirtual, "joystick_testVirtual", "Test virtual joystick functionality", TEST_ENABLED +}; +static const SDLTest_TestCaseReference joystickTest2 = { + joystick_testMappings, "joystick_testMappings", "Test gamepad mapping functionality", TEST_ENABLED }; /* Sequence of Joystick routine test cases */ static const SDLTest_TestCaseReference *joystickTests[] = { &joystickTest1, + &joystickTest2, NULL }; /* Joystick routine test suite (global) */ SDLTest_TestSuiteReference joystickTestSuite = { "Joystick", - NULL, + joystickSetUp, joystickTests, - NULL + joystickTearDown };