From 0ac6f972f92acf93f02498e10c1d7e56d497827a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 16 Dec 2025 16:26:21 -0800
Subject: [PATCH] Added enhanced support for the Flydigi Vader 5 Pro controller
---
src/SDL_utils.c | 1 +
src/joystick/SDL_gamepad.c | 4 +
src/joystick/hidapi/SDL_hidapi_flydigi.c | 98 ++++++++++++++++++++----
src/joystick/hidapi/SDL_hidapi_flydigi.h | 2 +-
4 files changed, 89 insertions(+), 16 deletions(-)
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index 399c3f5157dcb..f860b910d11fe 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -445,6 +445,7 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam
const char *prefix;
const char *replacement;
} replacements[] = {
+ { "(Standard system devices) ", "" },
{ "8BitDo Tech Ltd", "8BitDo" },
{ "ASTRO Gaming", "ASTRO" },
{ "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 4f688028fdffd..f01986b1eb44d 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -1258,6 +1258,10 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
if (guid.data[15] >= SDL_FLYDIGI_VADER2) {
// Vader series of controllers have C/Z buttons
SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string));
+ if (guid.data[15] == SDL_FLYDIGI_VADER5_PRO) {
+ // Vader 5 has additional shoulder macro buttons and a circle button
+ SDL_strlcat(mapping_string, "misc4:b17,misc5:b18,misc6:b19", sizeof(mapping_string));
+ }
} else if (guid.data[15] == SDL_FLYDIGI_APEX5) {
// Apex 5 has additional shoulder macro buttons
SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string));
diff --git a/src/joystick/hidapi/SDL_hidapi_flydigi.c b/src/joystick/hidapi/SDL_hidapi_flydigi.c
index 4b42673761095..e260eb2105b34 100644
--- a/src/joystick/hidapi/SDL_hidapi_flydigi.c
+++ b/src/joystick/hidapi/SDL_hidapi_flydigi.c
@@ -53,6 +53,9 @@ enum
/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */
#define SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ 500
#define SENSOR_INTERVAL_VADER4_PRO_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ)
+/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */
+#define SENSOR_INTERVAL_VADER5_PRO_RATE_HZ 500
+#define SENSOR_INTERVAL_VADER5_PRO_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER5_PRO_RATE_HZ)
/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 295hz */
#define SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ 295
@@ -86,6 +89,7 @@ typedef struct
bool available;
bool has_cz;
bool has_lmrm;
+ bool has_circle;
bool wireless;
bool sensors_supported;
bool sensors_enabled;
@@ -117,7 +121,19 @@ static bool HIDAPI_DriverFlydigi_IsEnabled(void)
static bool HIDAPI_DriverFlydigi_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)
{
- return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2;
+ if (SDL_IsJoystickFlydigiController(vendor_id, product_id)) {
+ if (vendor_id == USB_VENDOR_FLYDIGI_V1) {
+ if (interface_number == 2) {
+ // Early controllers have their custom protocol on interface 2
+ return true;
+ }
+ } else {
+ // Newer controllers have their custom protocol on interface 1 or 2, but
+ // only expose one HID interface, so we'll accept any interface we see.
+ return true;
+ }
+ }
+ return false;
}
static bool HIDAPI_DriverFlydigi_InitControllerV1(SDL_HIDAPI_Device *device)
@@ -217,8 +233,15 @@ static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size
HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size);
#endif
- if (size == 32 && data[1] == FLYDIGI_V2_MAGIC1 && data[2] == FLYDIGI_V2_MAGIC2 && data[3] == command) {
- return true;
+ if (size == 32) {
+ if (data[1] == FLYDIGI_V2_MAGIC1 && data[2] == FLYDIGI_V2_MAGIC2) {
+ // Skip the report ID
+ SDL_memmove(&data[0], &data[1], size - 1);
+ data[size - 1] = 0;
+ }
+ if (data[0] == FLYDIGI_V2_MAGIC1 && data[1] == FLYDIGI_V2_MAGIC2 && data[2] == command) {
+ return true;
+ }
}
}
return false;
@@ -264,12 +287,27 @@ static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
}
// Check the firmware version
- ctx->firmware_version = LOAD16(data[17], data[16]);
- if (ctx->firmware_version < 0x7031) {
+ Uint16 min_firmware_version;
+ ctx->firmware_version = LOAD16(data[16], data[15]);
+ switch (device->product_id) {
+ case USB_PRODUCT_FLYDIGI_V2_APEX:
+ // Minimum supported firmware version, Apex 5
+ min_firmware_version = 0x7031;
+ break;
+ case USB_PRODUCT_FLYDIGI_V2_VADER:
+ // Minimum supported firmware version, Vader 5 Pro
+ min_firmware_version = 0x7141;
+ break;
+ default:
+ // Unknown product, presumably this version is okay?
+ min_firmware_version = 0;
+ break;
+ }
+ if (ctx->firmware_version < min_firmware_version) {
return SDL_SetError("Unsupported firmware version");
}
- switch (data[7]) {
+ switch (data[6]) {
case 1:
// Wired connection
ctx->wireless = false;
@@ -281,7 +319,7 @@ static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
default:
break;
}
- ctx->deviceID = data[6];
+ ctx->deviceID = data[5];
// See whether we can acquire the controller
const Uint8 query_status[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_STATUS_COMMAND };
@@ -291,7 +329,7 @@ static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
if (!GetReply(device, FLYDIGI_V2_GET_STATUS_COMMAND, data, sizeof(data))) {
return SDL_SetError("Couldn't get controller status");
}
- if (data[10] == 1) {
+ if (data[9] == 1) {
ctx->available = true;
} else {
// Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app
@@ -338,19 +376,26 @@ static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
break;
case 128:
case 129:
+ controller_type = SDL_FLYDIGI_APEX5;
+ break;
+ case 130:
+ controller_type = SDL_FLYDIGI_VADER5_PRO;
+ break;
case 133:
case 134:
controller_type = SDL_FLYDIGI_APEX5;
break;
default:
// Try to guess from the name of the controller
- if (SDL_strstr(device->name, "VADER") != NULL) {
+ if (SDL_strcasestr(device->name, "VADER") != NULL) {
if (SDL_strstr(device->name, "VADER2") != NULL) {
controller_type = SDL_FLYDIGI_VADER2;
} else if (SDL_strstr(device->name, "VADER3") != NULL) {
controller_type = SDL_FLYDIGI_VADER3;
} else if (SDL_strstr(device->name, "VADER4") != NULL) {
- controller_type = SDL_FLYDIGI_VADER4;
+ controller_type = SDL_FLYDIGI_VADER4_PRO;
+ } else if (SDL_strstr(device->name, "Vader 5") != NULL) {
+ controller_type = SDL_FLYDIGI_VADER5_PRO;
}
} else if (SDL_strstr(device->name, "APEX") != NULL) {
if (SDL_strstr(device->name, "APEX2") != NULL) {
@@ -410,7 +455,6 @@ static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f;
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS;
break;
- case SDL_FLYDIGI_VADER4:
case SDL_FLYDIGI_VADER4_PRO:
HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro");
ctx->has_cz = true;
@@ -418,6 +462,16 @@ static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f;
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS;
break;
+ case SDL_FLYDIGI_VADER5_PRO:
+ HIDAPI_SetDeviceName(device, "Flydigi Vader 5 Pro");
+ ctx->has_cz = true;
+ ctx->has_lmrm = true;
+ ctx->has_circle = true;
+ ctx->sensors_supported = true;
+ ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f;
+ ctx->gyroScale = DEG2RAD(2000.0f);
+ ctx->sensor_timestamp_step_ns = SENSOR_INTERVAL_VADER5_PRO_NS;
+ break;
default:
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name);
break;
@@ -479,6 +533,9 @@ static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
if (ctx->has_lmrm) {
joystick->nbuttons += 2;
}
+ if (ctx->has_circle) {
+ joystick->nbuttons += 1;
+ }
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
@@ -748,17 +805,28 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[13] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[13] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[13] & 0x20) != 0));
+ if (ctx->has_cz) {
+ SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x01) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x02) != 0));
+ }
if (ctx->has_lmrm) {
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x80) != 0));
}
+ } else {
+ if (ctx->has_cz) {
+ extra_button_index += 2;
+ }
+ if (ctx->has_lmrm) {
+ extra_button_index += 2;
+ }
}
if (ctx->last_state[14] != data[14]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[14] & 0x08) != 0));
- SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[14] & 0x01) != 0));
- // The '-' button is only available on the Vader 2, for simplicity let's ignore that
- SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0));
+ if (ctx->has_circle) {
+ SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[14] & 0x01) != 0));
+ }
}
axis = LOAD16(data[3], data[4]);
@@ -813,7 +881,7 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL
static void HIDAPI_DriverFlydigi_HandleStatusUpdate(SDL_HIDAPI_Device *device, Uint8 *data, int size)
{
- if (data[9] == 1) {
+ if (data[9] & 0x01) {
// We can now acquire the controller
HIDAPI_DriverFlydigi_SetAvailable(device, true);
} else {
diff --git a/src/joystick/hidapi/SDL_hidapi_flydigi.h b/src/joystick/hidapi/SDL_hidapi_flydigi.h
index 42d6ef7ee257b..91a15b506b5ca 100644
--- a/src/joystick/hidapi/SDL_hidapi_flydigi.h
+++ b/src/joystick/hidapi/SDL_hidapi_flydigi.h
@@ -32,7 +32,7 @@ typedef enum
SDL_FLYDIGI_VADER2_PRO,
SDL_FLYDIGI_VADER3,
SDL_FLYDIGI_VADER3_PRO,
- SDL_FLYDIGI_VADER4,
SDL_FLYDIGI_VADER4_PRO,
+ SDL_FLYDIGI_VADER5_PRO,
} SDL_FlyDigiControllerType;