#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../events/SDL_keyboard_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#ifdef SDL_JOYSTICK_HIDAPI_GIP
#if 0#endif
#define MAX_MESSAGE_LENGTH 0x4000
#define MAX_ATTACHMENTS 8
#define GIP_DATA_CLASS_COMMAND (0u << 5)
#define GIP_DATA_CLASS_LOW_LATENCY (1u << 5)
#define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5)
#define GIP_DATA_CLASS_AUDIO (3u << 5)
#define GIP_DATA_CLASS_SHIFT 5
#define GIP_DATA_CLASS_MASK (7u << 5)
#define GIP_CMD_PROTO_CONTROL 0x01
#define GIP_CMD_HELLO_DEVICE 0x02
#define GIP_CMD_STATUS_DEVICE 0x03
#define GIP_CMD_METADATA 0x04
#define GIP_CMD_SET_DEVICE_STATE 0x05
#define GIP_CMD_SECURITY 0x06
#define GIP_CMD_GUIDE_BUTTON 0x07
#define GIP_CMD_AUDIO_CONTROL 0x08
#define GIP_CMD_LED 0x0a
#define GIP_CMD_HID_REPORT 0x0b
#define GIP_CMD_FIRMWARE 0x0c
#define GIP_CMD_EXTENDED 0x1e
#define GIP_CMD_DEBUG 0x1f
#define GIP_AUDIO_DATA 0x60
#define GIP_CMD_DIRECT_MOTOR 0x09
#define GIP_LL_INPUT_REPORT 0x20
#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26
#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a
#define GIP_LL_STATIC_CONFIGURATION 0x21
#define GIP_LL_BUTTON_INFO_REPORT 0x22
#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b
#define GIP_CMD_SET_EQUATIONS_STATES 0x0c
#define GIP_CMD_SET_EQUATION 0x0d
#define GIP_CMD_DEVICE_CAPABILITIES 0x00
#define GIP_CMD_LED_CAPABILITIES 0x01
#define GIP_CMD_SET_LED_STATE 0x02
#define GIP_CMD_RAW_REPORT 0x0c
#define GIP_CMD_GUIDE_COLOR 0x0e
#define GIP_SL_ELITE_CONFIG 0x4d
#define GIP_BTN_OFFSET_XBE1 28
#define GIP_BTN_OFFSET_XBE2 14
#define GIP_FLAG_FRAGMENT (1u << 7)
#define GIP_FLAG_INIT_FRAG (1u << 6)
#define GIP_FLAG_SYSTEM (1u << 5)
#define GIP_FLAG_ACME (1u << 4)
#define GIP_FLAG_ATTACHMENT_MASK 0x7
#define GIP_AUDIO_FORMAT_NULL 0
#define GIP_AUDIO_FORMAT_8000HZ_1CH 1
#define GIP_AUDIO_FORMAT_8000HZ_2CH 2
#define GIP_AUDIO_FORMAT_12000HZ_1CH 3
#define GIP_AUDIO_FORMAT_12000HZ_2CH 4
#define GIP_AUDIO_FORMAT_16000HZ_1CH 5
#define GIP_AUDIO_FORMAT_16000HZ_2CH 6
#define GIP_AUDIO_FORMAT_20000HZ_1CH 7
#define GIP_AUDIO_FORMAT_20000HZ_2CH 8
#define GIP_AUDIO_FORMAT_24000HZ_1CH 9
#define GIP_AUDIO_FORMAT_24000HZ_2CH 10
#define GIP_AUDIO_FORMAT_32000HZ_1CH 11
#define GIP_AUDIO_FORMAT_32000HZ_2CH 12
#define GIP_AUDIO_FORMAT_40000HZ_1CH 13
#define GIP_AUDIO_FORMAT_40000HZ_2CH 14
#define GIP_AUDIO_FORMAT_48000HZ_1CH 15
#define GIP_AUDIO_FORMAT_48000HZ_2CH 16
#define GIP_AUDIO_FORMAT_48000HZ_6CH 32
#define GIP_AUDIO_FORMAT_48000HZ_8CH 33
#define GIP_CONTROL_CODE_ACK 0
#define GIP_CONTROL_CODE_NACK 1
#define GIP_CONTROL_CODE_UNK 2
#define GIP_CONTROL_CODE_AB 3
#define GIP_CONTROL_CODE_MPER 4
#define GIP_CONTROL_CODE_STOP 5
#define GIP_CONTROL_CODE_START 6
#define GIP_CONTROL_CODE_ERR 7
#define GIP_POWER_LEVEL_OFF 0
#define GIP_POWER_LEVEL_STANDBY 1
#define GIP_POWER_LEVEL_FULL 2
#define GIP_NOT_CHARGING 0
#define GIP_CHARGING 1
#define GIP_CHARGE_ERROR 2
#define GIP_BATTERY_ABSENT 0
#define GIP_BATTERY_STANDARD 1
#define GIP_BATTERY_RECHARGEABLE 2
#define GIP_BATTERY_CRITICAL 0
#define GIP_BATTERY_LOW 1
#define GIP_BATTERY_MEDIUM 2
#define GIP_BATTERY_FULL 3
#define GIP_EVENT_FAULT 0x0002
#define GIP_FAULT_UNKNOWN 0
#define GIP_FAULT_HARD 1
#define GIP_FAULT_NMI 2
#define GIP_FAULT_SVC 3
#define GIP_FAULT_PEND_SV 4
#define GIP_FAULT_SMART_PTR 5
#define GIP_FAULT_MCU 6
#define GIP_FAULT_BUS 7
#define GIP_FAULT_USAGE 8
#define GIP_FAULT_RADIO_HANG 9
#define GIP_FAULT_WATCHDOG 10
#define GIP_FAULT_LINK_STALL 11
#define GIP_FAULT_ASSERTION 12
#define GIP_MESSAGE_FLAG_BIG_ENDIAN (1u << 0)
#define GIP_MESSAGE_FLAG_RELIABLE (1u << 1)
#define GIP_MESSAGE_FLAG_SEQUENCED (1u << 2)
#define GIP_MESSAGE_FLAG_DOWNSTREAM (1u << 3)
#define GIP_MESSAGE_FLAG_UPSTREAM (1u << 4)
#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE (1u << 5)
#define GIP_DATA_TYPE_CUSTOM 1
#define GIP_DATA_TYPE_AUDIO 2
#define GIP_DATA_TYPE_SECURITY 3
#define GIP_DATA_TYPE_GIP 4
#define GIP_STATE_START 0
#define GIP_STATE_STOP 1
#define GIP_STATE_STANDBY 2
#define GIP_STATE_FULL_POWER 3
#define GIP_STATE_OFF 4
#define GIP_STATE_QUIESCE 5
#define GIP_STATE_UNK6 6
#define GIP_STATE_RESET 7
#define GIP_LED_GUIDE 0
#define GIP_LID_IR 1
#define GIP_LED_GUIDE_OFF 0
#define GIP_LED_GUIDE_ON 1
#define GIP_LED_GUIDE_FAST_BLINK 2
#define GIP_LED_GUIDE_SLOW_BLINK 3
#define GIP_LED_GUIDE_CHARGING_BLINK 4
#define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd
#define GIP_LED_IR_OFF 0
#define GIP_LED_IR_ON_100MS 1
#define GIP_LED_IR_PATTERN 4
#define GIP_MOTOR_RIGHT_VIBRATION (1u << 0)
#define GIP_MOTOR_LEFT_VIBRATION (1u << 1)
#define GIP_MOTOR_RIGHT_IMPULSE (1u << 2)
#define GIP_MOTOR_LEFT_IMPULSE (1u << 3)
#define GIP_MOTOR_ALL 0xF
#define GIP_EXTCMD_GET_CAPABILITIES 0x00
#define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01
#define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04
#define GIP_EXTENDED_STATUS_OK 0
#define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1
#define GIP_EXTENDED_STATUS_NOT_READY 2
#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3
#define GIP_EXTENDED_STATUS_FAILED 4
#define GIP_HELLO_TIMEOUT 2000
#define GIP_ACME_TIMEOUT 10
#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e
#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472
#define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0)
#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1)
#define GIP_FEATURE_ELITE_BUTTONS (1u << 2)
#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT (1u << 3)
#define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4)
#define GIP_FEATURE_MOTOR_CONTROL (1u << 5)
#define GIP_FEATURE_GUIDE_COLOR (1u << 6)
#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE (1u << 7)
#define GIP_QUIRK_NO_HELLO (1u << 0)
#define GIP_QUIRK_BROKEN_METADATA (1u << 1)
#define GIP_QUIRK_NO_IMPULSE_VIBRATION (1u << 2)
typedef enum
{
GIP_METADATA_NONE = 0,
GIP_METADATA_GOT = 1,
GIP_METADATA_FAKED = 2,
GIP_METADATA_PENDING = 3,
} GIP_MetadataStatus;
#ifndef VK_LWIN
#define VK_LWIN 0x5b
#endif
typedef enum
{
GIP_TYPE_UNKNOWN = -1,
GIP_TYPE_GAMEPAD = 0,
GIP_TYPE_ARCADE_STICK = 1,
GIP_TYPE_WHEEL = 2,
GIP_TYPE_FLIGHT_STICK = 3,
GIP_TYPE_NAVIGATION_CONTROLLER = 4,
GIP_TYPE_CHATPAD = 5,
GIP_TYPE_HEADSET = 6,
} GIP_AttachmentType;
typedef enum
{
GIP_RUMBLE_STATE_IDLE,
GIP_RUMBLE_STATE_QUEUED,
GIP_RUMBLE_STATE_BUSY,
} GIP_RumbleState;
typedef enum
{
GIP_BTN_FMT_UNKNOWN,
GIP_BTN_FMT_XBE1,
GIP_BTN_FMT_XBE2_RAW,
GIP_BTN_FMT_XBE2_4,
GIP_BTN_FMT_XBE2_5,
} GIP_EliteButtonFormat;
#define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \
static const GUID NAME = { SDL_Swap32LE(A), SDL_Swap16LE(B), SDL_Swap16LE(C), { D0, D1, D2, D3, D4, D5, D6, D7 } }
typedef struct GUID
{
Uint32 a;
Uint16 b;
Uint16 c;
Uint8 d[8];
} GUID;
SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16);
MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3);
MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee);
MAKE_GUID(GUID_FlightStick, 0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0);
MAKE_GUID(GUID_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d);
MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd);
MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6);
MAKE_GUID(GUID_IDevAuthPCOptOut, 0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a);
MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89);
MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5);
MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7);
MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c);
static const int GIP_DataClassMtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 };
typedef struct GIP_Quirks
{
Uint16 vendor_id;
Uint16 product_id;
Uint8 attachment_index;
Uint32 added_features;
Uint32 filtered_features;
Uint32 quirks;
Uint32 extra_in_system[8];
Uint32 extra_out_system[8];
GIP_AttachmentType device_type;
Uint8 extra_buttons;
Uint8 extra_axes;
} GIP_Quirks;
static const GIP_Quirks quirks[] = {
{ USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, 0,
.added_features = GIP_FEATURE_ELITE_BUTTONS,
.filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP },
{ USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, 0,
.added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR | GIP_FEATURE_EXTENDED_SET_DEVICE_STATE,
.extra_in_system = { 1 << GIP_CMD_FIRMWARE },
.extra_out_system = { 1 << GIP_CMD_FIRMWARE } },
{ USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, 0,
.added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT },
{ USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, 0,
.quirks = GIP_QUIRK_NO_HELLO },
{ USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0,
.filtered_features = GIP_FEATURE_MOTOR_CONTROL },
{ USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0,
.quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION },
{ USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_SPECTRA_PRO, 0,
.quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION },
{ USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0,
.filtered_features = GIP_FEATURE_MOTOR_CONTROL,
.device_type = GIP_TYPE_ARCADE_STICK },
{ USB_VENDOR_THRUSTMASTER, USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE, 0,
.filtered_features = GIP_FEATURE_MOTOR_CONTROL,
.device_type = GIP_TYPE_FLIGHT_STICK,
.extra_buttons = 5,
.extra_axes = 3 },
{0},
};
typedef struct GIP_Header
{
Uint8 message_type;
Uint8 flags;
Uint8 sequence_id;
Uint64 length;
} GIP_Header;
typedef struct GIP_AudioFormat
{
Uint8 inbound;
Uint8 outbound;
} GIP_AudioFormat;
typedef struct GIP_DeviceMetadata
{
Uint8 num_audio_formats;
Uint8 num_preferred_types;
Uint8 num_supported_interfaces;
Uint8 hid_descriptor_size;
Uint32 in_system_messages[8];
Uint32 out_system_messages[8];
GIP_AudioFormat *audio_formats;
char **preferred_types;
GUID *supported_interfaces;
Uint8 *hid_descriptor;
GIP_AttachmentType device_type;
} GIP_DeviceMetadata;
typedef struct GIP_MessageMetadata
{
Uint8 type;
Uint16 length;
Uint16 data_type;
Uint32 flags;
Uint16 period;
Uint16 persistence_timeout;
} GIP_MessageMetadata;
typedef struct GIP_Metadata
{
Uint16 version_major;
Uint16 version_minor;
GIP_DeviceMetadata device;
Uint8 num_messages;
GIP_MessageMetadata *message_metadata;
} GIP_Metadata;
struct GIP_Device;
typedef struct GIP_Attachment
{
struct GIP_Device *device;
Uint8 attachment_index;
SDL_JoystickID joystick;
SDL_KeyboardID keyboard;
Uint8 fragment_message;
Uint16 total_length;
Uint8 *fragment_data;
Uint32 fragment_offset;
Uint64 fragment_timer;
int fragment_retries;
Uint16 firmware_major_version;
Uint16 firmware_minor_version;
GIP_MetadataStatus got_metadata;
Uint64 metadata_next;
int metadata_retries;
GIP_Metadata metadata;
Uint8 seq_system;
Uint8 seq_security;
Uint8 seq_extended;
Uint8 seq_audio;
Uint8 seq_vendor;
int device_state;
GIP_RumbleState rumble_state;
Uint64 rumble_time;
bool rumble_pending;
Uint8 left_impulse_level;
Uint8 right_impulse_level;
Uint8 left_vibration_level;
Uint8 right_vibration_level;
Uint8 last_input[64];
Uint8 last_modifiers;
bool capslock;
SDL_Keycode last_key;
Uint32 altcode;
int altcode_digit;
GIP_AttachmentType attachment_type;
GIP_EliteButtonFormat xbe_format;
Uint32 features;
Uint32 quirks;
Uint8 share_button_idx;
Uint8 paddle_idx;
Uint8 extra_button_idx;
int extra_buttons;
int extra_axes;
} GIP_Attachment;
typedef struct GIP_Device
{
SDL_HIDAPI_Device *device;
Uint64 hello_deadline;
bool got_hello;
bool reset_for_metadata;
int timeout;
GIP_Attachment *attachments[MAX_ATTACHMENTS];
} GIP_Device;
typedef struct GIP_HelloDevice
{
Uint64 device_id;
Uint16 vendor_id;
Uint16 product_id;
Uint16 firmware_major_version;
Uint16 firmware_minor_version;
Uint16 firmware_build_version;
Uint16 firmware_revision;
Uint8 hardware_major_version;
Uint8 hardware_minor_version;
Uint8 rf_proto_major_version;
Uint8 rf_proto_minor_version;
Uint8 security_major_version;
Uint8 security_minor_version;
Uint8 gip_major_version;
Uint8 gip_minor_version;
} GIP_HelloDevice;
typedef struct GIP_Status
{
int power_level;
int charge;
int battery_type;
int battery_level;
} GIP_Status;
typedef struct GIP_StatusEvent
{
Uint16 event_type;
Uint32 fault_tag;
Uint32 fault_address;
} GIP_StatusEvent;
typedef struct GIP_ExtendedStatus
{
GIP_Status base;
bool device_active;
int num_events;
GIP_StatusEvent events[5];
} GIP_ExtendedStatus;
typedef struct GIP_DirectMotor
{
Uint8 motor_bitmap;
Uint8 left_impulse_level;
Uint8 right_impulse_level;
Uint8 left_vibration_level;
Uint8 right_vibration_level;
Uint8 duration;
Uint8 delay;
Uint8 repeat;
} GIP_DirectMotor;
typedef struct GIP_InitialReportsRequest
{
Uint8 type;
Uint8 data[2];
} GIP_InitialReportsRequest;
static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment);
static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes)
{
*length = 0;
int offset;
for (offset = 0; offset < num_bytes; offset++) {
Uint8 byte = bytes[offset];
*length |= (byte & 0x7full) << (offset * 7);
if (!(byte & 0x80)) {
offset++;
break;
}
}
return offset;
}
static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes)
{
int offset;
for (offset = 0; offset < num_bytes; offset++) {
Uint8 byte = length & 0x7f;
length >>= 7;
if (length) {
byte |= 0x80;
}
bytes[offset] = byte;
if (!length) {
offset++;
break;
}
}
return offset;
}
static bool GIP_SupportsSystemMessage(GIP_Attachment *attachment, Uint8 command, bool upstream)
{
if (upstream) {
return attachment->metadata.device.in_system_messages[command >> 5] & (1u << command);
} else {
return attachment->metadata.device.out_system_messages[command >> 5] & (1u << command);
}
}
static bool GIP_SupportsVendorMessage(GIP_Attachment *attachment, Uint8 command, bool upstream)
{
size_t i;
for (i = 0; i < attachment->metadata.num_messages; i++) {
GIP_MessageMetadata *metadata = &attachment->metadata.message_metadata[i];
if (metadata->type != command) {
continue;
}
if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE) {
return true;
}
if (upstream) {
return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM;
} else {
return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM;
}
}
return false;
}
static Uint8 GIP_SequenceNext(GIP_Attachment *attachment, Uint8 command, bool system)
{
Uint8 seq;
if (system) {
switch (command) {
case GIP_CMD_SECURITY:
seq = attachment->seq_security++;
if (!seq) {
seq = attachment->seq_security++;
}
break;
case GIP_CMD_EXTENDED:
seq = attachment->seq_extended++;
if (!seq) {
seq = attachment->seq_extended++;
}
break;
case GIP_AUDIO_DATA:
seq = attachment->seq_audio++;
if (!seq) {
seq = attachment->seq_audio++;
}
break;
default:
seq = attachment->seq_system++;
if (!seq) {
seq = attachment->seq_system++;
}
break;
}
} else {
if (command == GIP_CMD_DIRECT_MOTOR) {
return 0;
}
seq = attachment->seq_vendor++;
if (!seq) {
seq = attachment->seq_vendor++;
}
}
return seq;
}
static void GIP_HandleQuirks(GIP_Attachment *attachment)
{
size_t i, j;
for (i = 0; quirks[i].vendor_id; i++) {
if (quirks[i].vendor_id != attachment->device->device->vendor_id) {
continue;
}
if (quirks[i].product_id != attachment->device->device->product_id) {
continue;
}
if (quirks[i].attachment_index != attachment->attachment_index) {
continue;
}
attachment->features |= quirks[i].added_features;
attachment->features &= ~quirks[i].filtered_features;
attachment->quirks = quirks[i].quirks;
attachment->attachment_type = quirks[i].device_type;
for (j = 0; j < 8; ++j) {
attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j];
attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j];
}
attachment->extra_buttons = quirks[i].extra_buttons;
attachment->extra_axes = quirks[i].extra_axes;
break;
}
}
static bool GIP_SendRawMessage(
GIP_Device *device,
Uint8 message_type,
Uint8 flags,
Uint8 seq,
const Uint8 *bytes,
int num_bytes,
bool async,
SDL_HIDAPI_RumbleSentCallback callback,
void *userdata)
{
Uint8 buffer[2054] = { message_type, flags, seq };
int offset = 3;
if (num_bytes < 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Invalid message length %d", num_bytes);
return false;
}
if (num_bytes > GIP_DataClassMtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
SDL_LogError(SDL_LOG_CATEGORY_INPUT,
"Attempted to send a message that requires fragmenting, which is not yet supported.");
return false;
}
offset += GIP_EncodeLength(num_bytes, &buffer[offset], sizeof(buffer) - offset);
if (num_bytes > 0) {
SDL_memcpy(&buffer[offset], bytes, num_bytes);
}
num_bytes += offset;
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("GIP sending message: size = %d", buffer, num_bytes);
#endif
if (async) {
if (!SDL_HIDAPI_LockRumble()) {
return false;
}
return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device->device, buffer, num_bytes, callback, userdata) == num_bytes;
} else {
return SDL_hid_write(device->device->dev, buffer, num_bytes) == num_bytes;
}
}
static bool GIP_SendSystemMessage(
GIP_Attachment *attachment,
Uint8 message_type,
Uint8 flags,
const Uint8 *bytes,
int num_bytes)
{
return GIP_SendRawMessage(attachment->device,
message_type,
GIP_FLAG_SYSTEM | attachment->attachment_index | flags,
GIP_SequenceNext(attachment, message_type, true),
bytes,
num_bytes,
false,
NULL,
NULL);
}
static bool GIP_SendVendorMessage(
GIP_Attachment *attachment,
Uint8 message_type,
Uint8 flags,
const Uint8 *bytes,
int num_bytes)
{
return GIP_SendRawMessage(attachment->device,
message_type,
flags,
GIP_SequenceNext(attachment, message_type, false),
bytes,
num_bytes,
true,
NULL,
NULL);
}
static bool GIP_AttachmentIsController(GIP_Attachment *attachment)
{
return attachment->attachment_type != GIP_TYPE_CHATPAD &&
attachment->attachment_type != GIP_TYPE_HEADSET;
}
static void GIP_MetadataFree(GIP_Metadata *metadata)
{
SDL_free(metadata->device.audio_formats);
if (metadata->device.preferred_types) {
int i;
for (i = 0; i < metadata->device.num_preferred_types; i++) {
SDL_free(metadata->device.preferred_types[i]);
}
SDL_free(metadata->device.preferred_types);
}
SDL_free(metadata->device.supported_interfaces);
SDL_free(metadata->device.hid_descriptor);
SDL_free(metadata->message_metadata);
SDL_zerop(metadata);
}
static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int *offset)
{
GIP_DeviceMetadata *device = &metadata->device;
int buffer_offset;
int count;
int length;
int i;
bytes = &bytes[*offset];
num_bytes -= *offset;
if (num_bytes < 16) {
return false;
}
length = bytes[0];
length |= bytes[1] << 8;
if (num_bytes < length) {
return false;
}
buffer_offset = bytes[4];
buffer_offset |= bytes[5] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
device->num_audio_formats = bytes[buffer_offset];
if (buffer_offset + device->num_audio_formats + 1 > length) {
return false;
}
device->audio_formats = SDL_malloc(device->num_audio_formats);
SDL_memcpy(device->audio_formats, &bytes[buffer_offset + 1], device->num_audio_formats);
}
buffer_offset = bytes[6];
buffer_offset |= bytes[7] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
count = bytes[buffer_offset];
if (buffer_offset + count + 1 > length) {
return false;
}
for (i = 0; i < count; i++) {
Uint8 message = bytes[buffer_offset + 1 + i];
#ifdef DEBUG_XBOX_PROTOCOL
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"GIP: Supported upstream system message %02x",
message);
#endif
device->in_system_messages[message >> 5] |= 1u << (message & 0x1F);
}
}
buffer_offset = bytes[8];
buffer_offset |= bytes[9] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
count = bytes[buffer_offset];
if (buffer_offset + count + 1 > length) {
return false;
}
for (i = 0; i < count; i++) {
Uint8 message = bytes[buffer_offset + 1 + i];
#ifdef DEBUG_XBOX_PROTOCOL
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"GIP: Supported downstream system message %02x",
message);
#endif
device->out_system_messages[message >> 5] |= 1u << (message & 0x1F);
}
}
buffer_offset = bytes[10];
buffer_offset |= bytes[11] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
device->num_preferred_types = bytes[buffer_offset];
device->preferred_types = SDL_calloc(device->num_preferred_types, sizeof(char *));
buffer_offset++;
for (i = 0; i < device->num_preferred_types; i++) {
if (buffer_offset + 2 >= length) {
return false;
}
count = bytes[buffer_offset];
count |= bytes[buffer_offset];
buffer_offset += 2;
if (buffer_offset + count > length) {
return false;
}
device->preferred_types[i] = SDL_calloc(count + 1, sizeof(char));
SDL_memcpy(device->preferred_types[i], &bytes[buffer_offset], count);
buffer_offset += count;
}
}
buffer_offset = bytes[12];
buffer_offset |= bytes[13] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
device->num_supported_interfaces = bytes[buffer_offset];
if (buffer_offset + 1 + (Sint32) (device->num_supported_interfaces * sizeof(GUID)) > length) {
return false;
}
device->supported_interfaces = SDL_calloc(device->num_supported_interfaces, sizeof(GUID));
SDL_memcpy(device->supported_interfaces,
&bytes[buffer_offset + 1],
sizeof(GUID) * device->num_supported_interfaces);
}
if (metadata->version_major > 1 || metadata->version_minor >= 1) {
buffer_offset = bytes[14];
buffer_offset |= bytes[15] << 8;
if (buffer_offset >= length) {
return false;
}
if (buffer_offset > 0) {
device->hid_descriptor_size = bytes[buffer_offset];
if (buffer_offset + 1 + device->hid_descriptor_size > length) {
return false;
}
device->hid_descriptor = SDL_malloc(device->hid_descriptor_size);
SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size);
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size);
#endif
}
}
*offset += length;
return true;
}
static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 *bytes, int num_bytes, int *offset)
{
Uint16 length;
bytes = &bytes[*offset];
num_bytes -= *offset;
if (num_bytes < 2) {
return false;
}
length = bytes[0];
length |= bytes[1] << 8;
if (num_bytes < length) {
return false;
}
if (length < 15) {
return false;
}
metadata->type = bytes[2];
metadata->length = bytes[3];
metadata->length |= bytes[4] << 8;
metadata->data_type = bytes[5];
metadata->data_type |= bytes[6] << 8;
metadata->flags = bytes[7];
metadata->flags |= bytes[8] << 8;
metadata->flags |= bytes[9] << 16;
metadata->flags |= bytes[10] << 24;
metadata->period = bytes[11];
metadata->period |= bytes[12] << 8;
metadata->persistence_timeout = bytes[13];
metadata->persistence_timeout |= bytes[14] << 8;
#ifdef DEBUG_XBOX_PROTOCOL
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"GIP: Supported vendor message type %02x of length %d, %s, %s, %s",
metadata->type,
metadata->length,
metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ?
(metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") :
metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" :
metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" :
"unknown direction",
metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced",
metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable");
#endif
*offset += length;
return true;
}
static bool GIP_ParseMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes)
{
int header_size;
int metadata_size;
int offset = 0;
int i;
if (num_bytes < 16) {
return false;
}
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("GIP received metadata: size = %d", bytes, num_bytes);
#endif
header_size = bytes[0];
header_size |= bytes[1] << 8;
if (num_bytes < header_size || header_size < 16) {
return false;
}
metadata->version_major = bytes[2];
metadata->version_major |= bytes[3] << 8;
metadata->version_minor = bytes[4];
metadata->version_minor |= bytes[5] << 8;
metadata_size = bytes[14];
metadata_size |= bytes[15] << 8;
if (num_bytes < metadata_size || metadata_size < header_size) {
return false;
}
offset = header_size;
if (!GIP_ParseDeviceMetadata(metadata, bytes, num_bytes, &offset)) {
goto err;
}
if (offset >= num_bytes) {
goto err;
}
metadata->num_messages = bytes[offset];
offset++;
if (metadata->num_messages > 0) {
metadata->message_metadata = SDL_calloc(metadata->num_messages, sizeof(*metadata->message_metadata));
for (i = 0; i < metadata->num_messages; i++) {
if (!GIP_ParseMessageMetadata(&metadata->message_metadata[i], bytes, num_bytes, &offset)) {
goto err;
}
}
}
return true;
err:
GIP_MetadataFree(metadata);
return false;
}
static bool GIP_Acknowledge(
GIP_Device *device,
const GIP_Header *header,
Uint32 fragment_offset,
Uint16 bytes_remaining)
{
Uint8 buffer[] = {
GIP_CONTROL_CODE_ACK,
header->message_type,
header->flags & GIP_FLAG_SYSTEM,
(Uint8) fragment_offset,
(Uint8) (fragment_offset >> 8),
(Uint8) (fragment_offset >> 16),
fragment_offset >> 24,
(Uint8) bytes_remaining,
bytes_remaining >> 8,
};
return GIP_SendRawMessage(device,
GIP_CMD_PROTO_CONTROL,
GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK),
header->sequence_id,
buffer,
sizeof(buffer),
false,
NULL,
NULL);
}
static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *header)
{
attachment->fragment_retries++;
if (attachment->fragment_retries > 8) {
if (attachment->fragment_data) {
SDL_free(attachment->fragment_data);
attachment->fragment_data = NULL;
}
attachment->fragment_message = 0;
}
return GIP_Acknowledge(attachment->device,
header,
attachment->fragment_offset,
(Uint16) (attachment->total_length - attachment->fragment_offset));
}
static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) {
if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT) {
if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) {
attachment->xbe_format = GIP_BTN_FMT_XBE1;
} else if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
if (attachment->firmware_major_version == 4) {
attachment->xbe_format = GIP_BTN_FMT_XBE2_4;
} else if (attachment->firmware_major_version == 5) {
if (attachment->firmware_minor_version >= 11 &&
attachment->firmware_minor_version < 17)
{
attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW;
} else {
attachment->xbe_format = GIP_BTN_FMT_XBE2_5;
}
}
}
}
if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
static const Uint8 enable_raw_report[] = { 7, 0 };
return GIP_SendVendorMessage(attachment,
GIP_SL_ELITE_CONFIG,
0,
enable_raw_report,
sizeof(enable_raw_report));
}
return true;
}
static bool GIP_SendGuideButtonLED(GIP_Attachment *attachment, Uint8 pattern, Uint8 intensity)
{
Uint8 buffer[] = {
GIP_LED_GUIDE,
pattern,
intensity,
};
if (!GIP_SupportsSystemMessage(attachment, GIP_CMD_LED, false)) {
return true;
}
return GIP_SendSystemMessage(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer));
}
static bool GIP_SendQueryFirmware(GIP_Attachment *attachment, Uint8 slot)
{
Uint8 buffer[] = { 0x1, slot, 0, 0, 0 };
return GIP_SendSystemMessage(attachment, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer));
}
static bool GIP_SendSetDeviceState(GIP_Attachment *attachment, Uint8 state)
{
Uint8 buffer[] = { state };
return GIP_SendSystemMessage(attachment,
GIP_CMD_SET_DEVICE_STATE,
attachment->attachment_index,
buffer,
sizeof(buffer));
}
static bool GIP_SendInitSequence(GIP_Attachment *attachment)
{
if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) {
static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
if (!GIP_SendSystemMessage(attachment,
GIP_CMD_SET_DEVICE_STATE,
0,
set_device_state,
sizeof(set_device_state)))
{
return false;
}
}
if (!GIP_EnableEliteButtons(attachment)) {
return false;
}
if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) {
return false;
}
attachment->device_state = GIP_STATE_START;
if (!GIP_SendGuideButtonLED(attachment, GIP_LED_GUIDE_ON, 20)) {
return false;
}
if (GIP_SupportsSystemMessage(attachment, GIP_CMD_SECURITY, false) &&
!(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT))
{
Uint8 buffer[] = { 0x1, 0x0 };
GIP_SendSystemMessage(attachment, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer));
}
if (GIP_SupportsVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) {
GIP_InitialReportsRequest request = { 0 };
GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request));
}
if (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) {
GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0);
}
if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) {
return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick);
}
if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) {
attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment;
SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad");
}
return true;
}
static bool GIP_EnsureMetadata(GIP_Attachment *attachment)
{
switch (attachment->got_metadata) {
case GIP_METADATA_GOT:
case GIP_METADATA_FAKED:
return true;
case GIP_METADATA_NONE:
if (attachment->device->got_hello) {
attachment->device->timeout = GIP_ACME_TIMEOUT;
attachment->got_metadata = GIP_METADATA_PENDING;
attachment->metadata_next = SDL_GetTicks() + 500;
attachment->metadata_retries = 0;
return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0);
} else {
return GIP_SetMetadataDefaults(attachment);
}
default:
return true;
}
}
static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment)
{
if (attachment->attachment_index == 0) {
attachment->features |= GIP_FEATURE_MOTOR_CONTROL;
attachment->attachment_type = GIP_TYPE_GAMEPAD;
attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON);
if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) {
attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
}
}
GIP_HandleQuirks(attachment);
if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) {
GIP_SendQueryFirmware(attachment, 2);
}
attachment->got_metadata = GIP_METADATA_FAKED;
attachment->device->hello_deadline = 0;
if (!attachment->joystick) {
return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick);
}
return true;
}
static bool GIP_HandleCommandProtocolControl(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Protocol Control message");
return false;
}
static bool GIP_HandleCommandHelloDevice(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
GIP_HelloDevice message = {0};
if (num_bytes != 28) {
return false;
}
message.device_id = (Uint64) bytes[0];
message.device_id |= (Uint64) bytes[1] << 8;
message.device_id |= (Uint64) bytes[2] << 16;
message.device_id |= (Uint64) bytes[3] << 24;
message.device_id |= (Uint64) bytes[4] << 32;
message.device_id |= (Uint64) bytes[5] << 40;
message.device_id |= (Uint64) bytes[6] << 48;
message.device_id |= (Uint64) bytes[7] << 56;
message.vendor_id = bytes[8];
message.vendor_id |= bytes[9] << 8;
message.product_id = bytes[10];
message.product_id |= bytes[11] << 8;
message.firmware_major_version = bytes[12];
message.firmware_major_version |= bytes[13] << 8;
message.firmware_minor_version = bytes[14];
message.firmware_minor_version |= bytes[15] << 8;
message.firmware_build_version = bytes[16];
message.firmware_build_version |= bytes[17] << 8;
message.firmware_revision = bytes[18];
message.firmware_revision |= bytes[19] << 8;
message.hardware_major_version = bytes[20];
message.hardware_minor_version = bytes[21];
message.rf_proto_major_version = bytes[22];
message.rf_proto_minor_version = bytes[23];
message.security_major_version = bytes[24];
message.security_minor_version = bytes[25];
message.gip_major_version = bytes[26];
message.gip_minor_version = bytes[27];
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT,
"GIP: Device hello from %" SDL_PRIx64 " (%04x:%04x)",
message.device_id, message.vendor_id, message.product_id);
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT,
"GIP: Firmware version %d.%d.%d rev %d",
message.firmware_major_version,
message.firmware_minor_version,
message.firmware_build_version,
message.firmware_revision);
if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Invalid RF protocol version %d.%d, expected 1.0",
message.rf_proto_major_version, message.rf_proto_minor_version);
}
if (message.security_major_version != 1 && message.security_minor_version != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Invalid security protocol version %d.%d, expected 1.0",
message.security_major_version, message.security_minor_version);
}
if (message.gip_major_version != 1 && message.gip_minor_version != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Invalid GIP version %d.%d, expected 1.0",
message.gip_major_version, message.gip_minor_version);
}
if (header->flags & GIP_FLAG_ATTACHMENT_MASK) {
return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0);
} else {
attachment->firmware_major_version = message.firmware_major_version;
attachment->firmware_minor_version = message.firmware_minor_version;
if (attachment->attachment_index == 0) {
attachment->device->hello_deadline = 0;
attachment->device->got_hello = true;
}
if (attachment->got_metadata == GIP_METADATA_FAKED) {
attachment->got_metadata = GIP_METADATA_NONE;
}
GIP_EnsureMetadata(attachment);
}
return true;
}
static bool GIP_HandleCommandStatusDevice(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
GIP_ExtendedStatus status;
SDL_Joystick *joystick = NULL;
SDL_PowerState power_state;
int power_percent = 0;
int i;
if (num_bytes < 1) {
return false;
}
SDL_zero(status);
status.base.battery_level = bytes[0] & 3;
status.base.battery_type = (bytes[0] >> 2) & 3;
status.base.charge = (bytes[0] >> 4) & 3;
status.base.power_level = (bytes[0] >> 6) & 3;
if (attachment->joystick) {
joystick = SDL_GetJoystickFromID(attachment->joystick);
}
if (joystick) {
switch (status.base.battery_level) {
case GIP_BATTERY_CRITICAL:
power_percent = 1;
break;
case GIP_BATTERY_LOW:
power_percent = 25;
break;
case GIP_BATTERY_MEDIUM:
power_percent = 50;
break;
case GIP_BATTERY_FULL:
power_percent = 100;
break;
}
switch (status.base.charge) {
case GIP_CHARGING:
if (status.base.battery_level == GIP_BATTERY_FULL) {
power_state = SDL_POWERSTATE_CHARGED;
} else {
power_state = SDL_POWERSTATE_CHARGING;
}
break;
case GIP_NOT_CHARGING:
power_state = SDL_POWERSTATE_ON_BATTERY;
break;
case GIP_CHARGE_ERROR:
default:
power_state = SDL_POWERSTATE_UNKNOWN;
break;
}
switch (status.base.battery_type) {
case GIP_BATTERY_ABSENT:
power_state = SDL_POWERSTATE_NO_BATTERY;
break;
case GIP_BATTERY_STANDARD:
case GIP_BATTERY_RECHARGEABLE:
break;
default:
power_state = SDL_POWERSTATE_UNKNOWN;
break;
}
SDL_SendJoystickPowerInfo(joystick, power_state, power_percent);
}
if (num_bytes >= 4) {
status.device_active = bytes[1] & 1;
if (bytes[1] & 2) {
if (num_bytes < 5) {
return false;
}
status.num_events = bytes[4];
if (status.num_events > 5) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Device reported too many events, %d > 5",
status.num_events);
return false;
}
if (5 + status.num_events * 10 > num_bytes) {
return false;
}
for (i = 0; i < status.num_events; i++) {
status.events[i].event_type = bytes[i * 10 + 5];
status.events[i].event_type |= bytes[i * 10 + 6] << 8;
status.events[i].fault_tag = bytes[i * 10 + 7];
status.events[i].fault_tag |= bytes[i * 10 + 8] << 8;
status.events[i].fault_tag |= bytes[i * 10 + 9] << 16;
status.events[i].fault_tag |= bytes[i * 10 + 10] << 24;
status.events[i].fault_tag = bytes[i * 10 + 11];
status.events[i].fault_tag |= bytes[i * 10 + 12] << 8;
status.events[i].fault_tag |= bytes[i * 10 + 13] << 16;
status.events[i].fault_tag |= bytes[i * 10 + 14] << 24;
}
}
}
GIP_EnsureMetadata(attachment);
return true;
}
static bool GIP_HandleCommandMetadataRespose(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
GIP_Metadata metadata = {0};
const GUID *expected_guid = NULL;
bool found_expected_guid;
bool found_controller_guid = false;
int i;
if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) {
return false;
}
if (attachment->got_metadata == GIP_METADATA_GOT) {
GIP_MetadataFree(&attachment->metadata);
}
attachment->metadata = metadata;
attachment->got_metadata = GIP_METADATA_GOT;
attachment->features = 0;
attachment->attachment_type = GIP_TYPE_UNKNOWN;
#ifdef DEBUG_XBOX_PROTOCOL
for (i = 0; i < metadata.device.num_preferred_types; i++) {
const char *type = metadata.device.preferred_types[i];
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type);
}
#endif
for (i = 0; i < metadata.device.num_preferred_types; i++) {
const char *type = metadata.device.preferred_types[i];
if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) {
attachment->attachment_type = GIP_TYPE_GAMEPAD;
expected_guid = &GUID_IGamepad;
break;
}
if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) {
attachment->attachment_type = GIP_TYPE_ARCADE_STICK;
expected_guid = &GUID_ArcadeStick;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) {
attachment->attachment_type = GIP_TYPE_ARCADE_STICK;
expected_guid = &GUID_ArcadeStick;
break;
}
if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) {
attachment->attachment_type = GIP_TYPE_FLIGHT_STICK;
expected_guid = &GUID_FlightStick;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) {
attachment->attachment_type = GIP_TYPE_FLIGHT_STICK;
expected_guid = &GUID_FlightStick;
break;
}
if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) {
attachment->attachment_type = GIP_TYPE_WHEEL;
expected_guid = &GUID_Wheel;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) {
attachment->attachment_type = GIP_TYPE_WHEEL;
expected_guid = &GUID_Wheel;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) {
attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER;
expected_guid = &GUID_NavigationController;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) {
attachment->attachment_type = GIP_TYPE_CHATPAD;
break;
}
if (SDL_strcmp(type, "Windows.Xbox.Input.Headset") == 0) {
attachment->attachment_type = GIP_TYPE_HEADSET;
expected_guid = &GUID_IHeadset;
break;
}
}
found_expected_guid = !expected_guid;
for (i = 0; i < metadata.device.num_supported_interfaces; i++) {
const GUID* guid = &metadata.device.supported_interfaces[i];
#ifdef DEBUG_XBOX_PROTOCOL
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"GIP: Supported interface: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
guid->a, guid->b, guid->c, guid->d[0], guid->d[1],
guid->d[2], guid->d[3], guid->d[4], guid->d[5], guid->d[6], guid->d[7]);
#endif
if (expected_guid && SDL_memcmp(expected_guid, guid, sizeof(GUID)) == 0) {
found_expected_guid = true;
}
if (SDL_memcmp(&GUID_IController, guid, sizeof(GUID)) == 0) {
found_controller_guid = true;
continue;
}
if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) {
attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT;
continue;
}
if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) {
attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
continue;
}
if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) {
attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW;
continue;
}
if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) {
attachment->features |= GIP_FEATURE_ELITE_BUTTONS;
continue;
}
if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) {
attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT;
continue;
}
}
for (i = 0; i < metadata.num_messages; i++) {
GIP_MessageMetadata *message = &metadata.message_metadata[i];
if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 &&
(message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) {
attachment->features |= GIP_FEATURE_MOTOR_CONTROL;
}
}
if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox.");
}
if ((attachment->features & GIP_FEATURE_GUIDE_COLOR) &&
!GIP_SupportsVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, false))
{
attachment->features &= ~GIP_FEATURE_GUIDE_COLOR;
}
GIP_HandleQuirks(attachment);
return GIP_SendInitSequence(attachment);
}
static bool GIP_HandleCommandSecurity(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Security message");
return false;
}
static bool GIP_HandleCommandGuideButtonStatus(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
Uint64 timestamp = SDL_GetTicksNS();
SDL_Joystick *joystick = NULL;
if (attachment->device->device->num_joysticks < 1) {
return true;
}
joystick = SDL_GetJoystickFromID(attachment->joystick);
if (!joystick) {
return false;
}
if (bytes[1] == VK_LWIN) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x03) != 0);
}
return true;
}
static bool GIP_HandleCommandAudioControl(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Control message");
return false;
}
static bool GIP_HandleCommandFirmware(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
if (num_bytes < 1) {
return false;
}
if (bytes[0] == 1) {
Uint16 major, minor, build, rev;
if (num_bytes < 14) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short firmware message");
return false;
}
major = bytes[6];
major |= bytes[7] << 8;
minor = bytes[8];
minor |= bytes[9] << 8;
build = bytes[10];
build |= bytes[11] << 8;
rev = bytes[12];
rev |= bytes[13] << 8;
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev);
attachment->firmware_major_version = major;
attachment->firmware_minor_version = minor;
if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT &&
attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)
{
return GIP_EnableEliteButtons(attachment);
}
return true;
} else {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message");
return false;
}
}
static bool GIP_HandleCommandRawReport(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
Uint64 timestamp = SDL_GetTicksNS();
SDL_Joystick *joystick = NULL;
if (attachment->device->device->num_joysticks < 1) {
return true;
}
joystick = SDL_GetJoystickFromID(attachment->joystick);
if (!joystick) {
return true;
}
if (num_bytes < 17) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report");
return false;
}
if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
if (bytes[15] & 3) {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
0);
} else {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0);
}
}
return true;
}
static bool GIP_HandleCommandHidReport(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
Uint64 timestamp = SDL_GetTicksNS();
if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message");
return false;
}
Uint8 modifiers = bytes[0];
Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers;
if (changed_modifiers & 0x02) {
if (modifiers & 0x02) {
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true);
} else {
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false);
}
}
if (changed_modifiers & 0x04) {
if (modifiers & 0x04) {
attachment->altcode_digit = 0;
attachment->altcode = 0;
} else {
if (attachment->altcode_digit == 4) {
char utf8[4] = {0};
switch (attachment->altcode) {
case 128:
SDL_UCS4ToUTF8(0x20AC, utf8);
break;
case 138:
SDL_UCS4ToUTF8(0x0160, utf8);
break;
case 140:
SDL_UCS4ToUTF8(0x0152, utf8);
break;
case 154:
SDL_UCS4ToUTF8(0x0161, utf8);
break;
case 156:
SDL_UCS4ToUTF8(0x0153, utf8);
break;
default:
SDL_UCS4ToUTF8(attachment->altcode, utf8);
break;
}
SDL_SendKeyboardText(utf8);
}
attachment->altcode_digit = -1;
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true);
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false);
}
}
if (!bytes[2] && attachment->last_key) {
if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) {
attachment->capslock = !attachment->capslock;
}
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false);
if (!(attachment->last_modifiers & 0xfd)) {
SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL,
attachment->last_key,
((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0);
if (keycode && keycode < 0x80) {
char text[2] = { (char)keycode };
SDL_SendKeyboardText(text);
}
}
attachment->last_key = 0;
} else {
SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true);
attachment->last_key = bytes[2];
if ((modifiers & 0x04) && attachment->altcode_digit >= 0) {
int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1;
if (digit < 1 || digit > 10) {
attachment->altcode_digit = -1;
} else {
attachment->altcode_digit++;
attachment->altcode *= 10;
if (digit < 10) {
attachment->altcode += digit;
}
}
}
}
attachment->last_modifiers = modifiers;
return true;
}
static bool GIP_HandleCommandExtended(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
char serial[33] = {0};
if (num_bytes < 2) {
return false;
}
switch (bytes[0]) {
case GIP_EXTCMD_GET_SERIAL_NUMBER:
if (bytes[1] != GIP_EXTENDED_STATUS_OK) {
return true;
}
if (header->flags & GIP_FLAG_ATTACHMENT_MASK) {
return true;
}
SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2));
HIDAPI_SetDeviceSerial(attachment->device->device, serial);
break;
default:
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Extended message type %02x", bytes[0]);
return false;
}
return true;
}
static void GIP_HandleNavigationReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
if (attachment->last_input[0] != bytes[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((bytes[0] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((bytes[0] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0));
}
if (attachment->last_input[1] != bytes[1]) {
Uint8 hat = 0;
if (bytes[1] & 0x01) {
hat |= SDL_HAT_UP;
}
if (bytes[1] & 0x02) {
hat |= SDL_HAT_DOWN;
}
if (bytes[1] & 0x04) {
hat |= SDL_HAT_LEFT;
}
if (bytes[1] & 0x08) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x20) != 0));
} else {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x20) != 0));
}
}
}
static void GIP_HandleGamepadReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
Sint16 axis;
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[1] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0));
axis = bytes[2];
axis |= bytes[3] << 8;
axis = SDL_clamp(axis, 0, 1023);
axis = (axis - 512) * 64;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = bytes[4];
axis |= bytes[5] << 8;
axis = SDL_clamp(axis, 0, 1023);
axis = (axis - 512) * 64;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = bytes[6];
axis |= bytes[7] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = bytes[8];
axis |= bytes[9] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
axis = bytes[10];
axis |= bytes[11] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = bytes[12];
axis |= bytes[13] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
}
static void GIP_HandleArcadeStickReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
Sint16 axis;
axis = bytes[2];
axis |= bytes[3] << 8;
axis = SDL_clamp(axis, 0, 1023);
axis = (axis - 512) * 64;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = bytes[4];
axis |= bytes[5] << 8;
axis = SDL_clamp(axis, 0, 1023);
axis = (axis - 512) * 64;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
if (num_bytes >= 19) {
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768);
}
}
static void GIP_HandleFlightStickReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
Sint16 axis;
int i;
if (num_bytes < 19) {
return;
}
if (attachment->last_input[2] != bytes[2]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[2] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[2] & 0x02) != 0));
}
for (i = 0; i < attachment->extra_buttons;) {
if (attachment->last_input[i / 8 + 3] != bytes[i / 8 + 3]) {
for (; i < attachment->extra_buttons; i++) {
SDL_SendJoystickButton(timestamp,
joystick,
(Uint8) (attachment->extra_button_idx + i),
((bytes[i / 8 + 3] & (1u << i)) != 0));
}
} else {
i += 8;
}
}
axis = bytes[11];
axis |= bytes[12] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = bytes[13];
axis |= bytes[14] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = bytes[15];
axis |= bytes[16] << 8;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = (bytes[18] << 8) - 0x8000;
axis |= bytes[17];
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
for (i = 0; i < attachment->extra_axes; i++) {
if (20 + i * 2 >= num_bytes) {
return;
}
axis = (bytes[20 + i * 2] << 8) - 0x8000;
axis |= bytes[19 + i * 2];
SDL_SendJoystickAxis(timestamp, joystick, (Uint8) (SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + i), axis);
}
}
static bool GIP_HandleLLInputReport(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
Uint64 timestamp = SDL_GetTicksNS();
SDL_Joystick *joystick = NULL;
if (attachment->device->device->num_joysticks < 1) {
GIP_EnsureMetadata(attachment);
if (attachment->got_metadata != GIP_METADATA_GOT && attachment->got_metadata != GIP_METADATA_FAKED) {
return true;
}
}
joystick = SDL_GetJoystickFromID(attachment->joystick);
if (!joystick) {
return false;
}
if (attachment->device_state != GIP_STATE_START) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report");
attachment->device_state = GIP_STATE_START;
return true;
}
if (num_bytes < 14) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report");
return false;
}
GIP_HandleNavigationReport(attachment, joystick, timestamp, bytes, num_bytes);
switch (attachment->attachment_type) {
case GIP_TYPE_GAMEPAD:
default:
GIP_HandleGamepadReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
case GIP_TYPE_ARCADE_STICK:
GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
case GIP_TYPE_FLIGHT_STICK:
GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
}
if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
bool clear = false;
if (attachment->xbe_format == GIP_BTN_FMT_XBE1 &&
num_bytes > GIP_BTN_OFFSET_XBE1 &&
attachment->last_input[GIP_BTN_OFFSET_XBE1] != bytes[GIP_BTN_OFFSET_XBE1] &&
(bytes[GIP_BTN_OFFSET_XBE1] & 0x10))
{
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
(bytes[GIP_BTN_OFFSET_XBE1] & 0x02) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
(bytes[GIP_BTN_OFFSET_XBE1] & 0x08) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
(bytes[GIP_BTN_OFFSET_XBE1] & 0x01) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
(bytes[GIP_BTN_OFFSET_XBE1] & 0x04) != 0);
} else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ||
attachment->xbe_format == GIP_BTN_FMT_XBE2_5) &&
num_bytes > GIP_BTN_OFFSET_XBE2)
{
int profile_offset = attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ? 15 : 20;
if (attachment->last_input[GIP_BTN_OFFSET_XBE2] != bytes[GIP_BTN_OFFSET_XBE2] ||
attachment->last_input[profile_offset] != bytes[profile_offset])
{
if (bytes[profile_offset] & 3) {
clear = true;
} else {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
(bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0);
}
}
} else {
clear = true;
}
if (clear) {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
0);
}
}
if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) {
int function_map_offset = -1;
if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
if (num_bytes >= 40) {
function_map_offset = num_bytes - 26;
}
} else {
function_map_offset = num_bytes - 18;
}
if (function_map_offset >= 14) {
if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->share_button_idx,
(bytes[function_map_offset] & 0x01) != 0);
}
}
}
SDL_memcpy(attachment->last_input, bytes, SDL_min(num_bytes, sizeof(attachment->last_input)));
return true;
}
static bool GIP_HandleLLStaticConfiguration(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Static Configuration message");
return false;
}
static bool GIP_HandleLLButtonInfoReport(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Button Info Report message");
return false;
}
static bool GIP_HandleLLOverflowInputReport(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Overflow Input Report message");
return false;
}
static bool GIP_HandleAudioData(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Data message");
return false;
}
static bool GIP_HandleSystemMessage(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) {
if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) {
if (!attachment->keyboard) {
attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment;
SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad");
}
attachment->attachment_type = GIP_TYPE_CHATPAD;
attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT);
}
}
if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received claimed-unsupported system message type %02x",
header->message_type);
return false;
}
switch (header->message_type) {
case GIP_CMD_PROTO_CONTROL:
return GIP_HandleCommandProtocolControl(attachment, header, bytes, num_bytes);
case GIP_CMD_HELLO_DEVICE:
return GIP_HandleCommandHelloDevice(attachment, header, bytes, num_bytes);
case GIP_CMD_STATUS_DEVICE:
return GIP_HandleCommandStatusDevice(attachment, header, bytes, num_bytes);
case GIP_CMD_METADATA:
return GIP_HandleCommandMetadataRespose(attachment, header, bytes, num_bytes);
case GIP_CMD_SECURITY:
return GIP_HandleCommandSecurity(attachment, header, bytes, num_bytes);
case GIP_CMD_GUIDE_BUTTON:
return GIP_HandleCommandGuideButtonStatus(attachment, header, bytes, num_bytes);
case GIP_CMD_AUDIO_CONTROL:
return GIP_HandleCommandAudioControl(attachment, header, bytes, num_bytes);
case GIP_CMD_FIRMWARE:
return GIP_HandleCommandFirmware(attachment, header, bytes, num_bytes);
case GIP_CMD_HID_REPORT:
return GIP_HandleCommandHidReport(attachment, header, bytes, num_bytes);
case GIP_CMD_EXTENDED:
return GIP_HandleCommandExtended(attachment, header, bytes, num_bytes);
case GIP_AUDIO_DATA:
return GIP_HandleAudioData(attachment, header, bytes, num_bytes);
default:
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received unknown system message type %02x",
header->message_type);
return false;
}
}
static GIP_Attachment *GIP_EnsureAttachment(GIP_Device *device, Uint8 attachment_index)
{
GIP_Attachment *attachment = device->attachments[attachment_index];
if (!attachment) {
attachment = SDL_calloc(1, sizeof(*attachment));
attachment->attachment_index = attachment_index;
if (attachment_index > 0) {
attachment->attachment_type = GIP_TYPE_UNKNOWN;
}
attachment->device = device;
attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES;
attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
device->attachments[attachment_index] = attachment;
}
return attachment;
}
static bool GIP_HandleMessage(
GIP_Attachment *attachment,
const GIP_Header *header,
const Uint8 *bytes,
int num_bytes)
{
if (header->flags & GIP_FLAG_SYSTEM) {
return GIP_HandleSystemMessage(attachment, header, bytes, num_bytes);
} else {
switch (header->message_type) {
case GIP_CMD_RAW_REPORT:
if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
return GIP_HandleCommandRawReport(attachment, header, bytes, num_bytes);
}
break;
case GIP_LL_INPUT_REPORT:
return GIP_HandleLLInputReport(attachment, header, bytes, num_bytes);
case GIP_LL_STATIC_CONFIGURATION:
return GIP_HandleLLStaticConfiguration(attachment, header, bytes, num_bytes);
case GIP_LL_BUTTON_INFO_REPORT:
return GIP_HandleLLButtonInfoReport(attachment, header, bytes, num_bytes);
case GIP_LL_OVERFLOW_INPUT_REPORT:
return GIP_HandleLLOverflowInputReport(attachment, header, bytes, num_bytes);
}
}
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received unknown vendor message type %02x",
header->message_type);
return false;
}
static void GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes)
{
GIP_Header header;
int offset = 3;
bool ok = true;
Uint64 fragment_offset = 0;
Uint16 bytes_remaining = 0;
bool is_fragment;
Uint8 attachment_index;
GIP_Attachment *attachment;
if (num_bytes < 5) {
return;
}
header.message_type = bytes[0];
header.flags = bytes[1];
header.sequence_id = bytes[2];
offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset);
is_fragment = header.flags & GIP_FLAG_FRAGMENT;
attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK;
attachment = GIP_EnsureAttachment(device, attachment_index);
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes);
#endif
if (is_fragment) {
if (header.flags & GIP_FLAG_INIT_FRAG) {
Uint64 total_length;
if (attachment->fragment_message) {
if (attachment->fragment_data) {
SDL_free(attachment->fragment_data);
attachment->fragment_data = NULL;
}
}
offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset);
if (total_length > MAX_MESSAGE_LENGTH) {
return;
}
attachment->total_length = (Uint16) total_length;
attachment->fragment_message = header.message_type;
if (header.length > num_bytes - offset) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i",
header.length, num_bytes - offset);
return;
}
if (header.length > total_length) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d",
header.length, attachment->total_length);
return;
}
attachment->fragment_data = SDL_malloc(attachment->total_length);
SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length);
fragment_offset = header.length;
attachment->fragment_offset = (Uint32) fragment_offset;
bytes_remaining = (Uint16) (attachment->total_length - fragment_offset);
} else {
if (header.message_type != attachment->fragment_message) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received out of sequence message type %02x, expected %02x",
header.message_type, attachment->fragment_message);
GIP_FragmentFailed(attachment, &header);
return;
}
offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset);
if (fragment_offset != attachment->fragment_offset) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)",
fragment_offset, attachment->fragment_offset);
GIP_Acknowledge(device,
&header,
attachment->fragment_offset,
(Uint16) (attachment->total_length - attachment->fragment_offset));
return;
} else if (fragment_offset + header.length > attachment->total_length) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d",
fragment_offset + header.length, attachment->total_length);
GIP_FragmentFailed(attachment, &header);
return;
}
bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length);
if (header.length != 0) {
SDL_memcpy(&attachment->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length);
} else {
ok = GIP_HandleMessage(attachment, &header, attachment->fragment_data, attachment->total_length);
if (attachment->fragment_data) {
SDL_free(attachment->fragment_data);
attachment->fragment_data = NULL;
}
attachment->fragment_message = 0;
}
fragment_offset += header.length;
attachment->fragment_offset = (Uint16) fragment_offset;
}
attachment->fragment_timer = SDL_GetTicks();
} else if (header.length + offset > num_bytes) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
"GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding",
header.length + offset, num_bytes);
return;
} else {
num_bytes -= offset;
bytes += offset;
fragment_offset = header.length;
ok = GIP_HandleMessage(attachment, &header, bytes, num_bytes);
}
if (ok && (header.flags & GIP_FLAG_ACME)) {
GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining);
}
}
static void HIDAPI_DriverGIP_RumbleSent(void *userdata)
{
GIP_Attachment *ctx = (GIP_Attachment *)userdata;
ctx->rumble_time = SDL_GetTicks();
}
static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Attachment *attachment)
{
GIP_DirectMotor motor;
if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) {
return true;
}
if (attachment->rumble_state == GIP_RUMBLE_STATE_QUEUED && attachment->rumble_time) {
attachment->rumble_state = GIP_RUMBLE_STATE_BUSY;
}
if (attachment->rumble_state == GIP_RUMBLE_STATE_BUSY) {
const int RUMBLE_BUSY_TIME_MS = 10;
if (SDL_GetTicks() >= (attachment->rumble_time + RUMBLE_BUSY_TIME_MS)) {
attachment->rumble_time = 0;
attachment->rumble_state = GIP_RUMBLE_STATE_IDLE;
}
}
if (!attachment->rumble_pending) {
return true;
}
if (attachment->rumble_state != GIP_RUMBLE_STATE_IDLE) {
return true;
}
attachment->rumble_pending = false;
motor.motor_bitmap = GIP_MOTOR_ALL;
motor.left_impulse_level = attachment->left_impulse_level;
motor.right_impulse_level = attachment->right_impulse_level;
motor.left_vibration_level = attachment->left_vibration_level;
motor.right_vibration_level = attachment->right_vibration_level;
motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; motor.delay = 0;
motor.repeat = 0;
Uint8 message[9] = {0};
SDL_memcpy(&message[1], &motor, sizeof(motor));
if (!GIP_SendRawMessage(attachment->device,
GIP_CMD_DIRECT_MOTOR,
attachment->attachment_index,
GIP_SequenceNext(attachment, GIP_CMD_DIRECT_MOTOR, false),
message,
sizeof(message),
true,
HIDAPI_DriverGIP_RumbleSent,
attachment))
{
return SDL_SetError("Couldn't send rumble packet");
}
attachment->rumble_state = GIP_RUMBLE_STATE_QUEUED;
return true;
}
static void HIDAPI_DriverGIP_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata);
}
static void HIDAPI_DriverGIP_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata);
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata);
}
static bool HIDAPI_DriverGIP_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));
}
static bool HIDAPI_DriverGIP_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 (device && device->is_bluetooth) {
return false;
}
#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {
return false;
}
#endif
return (type == SDL_GAMEPAD_TYPE_XBOXONE);
}
static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device)
{
GIP_Device *ctx;
GIP_Attachment *attachment;
ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
}
ctx->device = device;
ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false);
attachment = GIP_EnsureAttachment(ctx, 0);
GIP_HandleQuirks(attachment);
if (attachment->quirks & GIP_QUIRK_NO_HELLO) {
ctx->got_hello = true;
GIP_EnsureMetadata(attachment);
} else {
ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT;
}
device->context = ctx;
device->type = SDL_GAMEPAD_TYPE_XBOXONE;
return true;
}
static int HIDAPI_DriverGIP_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
GIP_Device *ctx = (GIP_Device *)device->context;
int i;
SDL_AssertJoysticksLocked();
for (i = 0; i < MAX_ATTACHMENTS; i++) {
if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) {
return ctx->attachments[i];
}
}
return NULL;
}
static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick);
if (!attachment) {
return SDL_SetError("Invalid joystick");
}
SDL_AssertJoysticksLocked();
attachment->left_impulse_level = 0;
attachment->right_impulse_level = 0;
attachment->left_vibration_level = 0;
attachment->right_vibration_level = 0;
attachment->rumble_state = GIP_RUMBLE_STATE_IDLE;
attachment->rumble_time = 0;
attachment->rumble_pending = false;
SDL_zeroa(attachment->last_input);
joystick->nbuttons = 11;
GIP_EnableEliteButtons(attachment);
if (attachment->xbe_format != GIP_BTN_FMT_UNKNOWN ||
(device->vendor_id == USB_VENDOR_MICROSOFT &&
device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2))
{
attachment->paddle_idx = (Uint8) joystick->nbuttons;
joystick->nbuttons += 4;
}
if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) {
attachment->share_button_idx = (Uint8) joystick->nbuttons;
joystick->nbuttons++;
}
if (attachment->extra_buttons > 0) {
attachment->extra_button_idx = (Uint8) joystick->nbuttons;
joystick->nbuttons += attachment->extra_buttons;
}
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK) {
joystick->naxes += attachment->extra_axes - 1;
}
joystick->nhats = 1;
return true;
}
static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick);
if (!attachment) {
return SDL_SetError("Invalid joystick");
}
if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) {
return SDL_Unsupported();
}
attachment->left_vibration_level = (Uint8)(low_frequency_rumble / 655);
attachment->right_vibration_level = (Uint8)(high_frequency_rumble / 655);
attachment->rumble_pending = true;
return HIDAPI_DriverGIP_UpdateRumble(attachment);
}
static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick);
if (!attachment) {
return SDL_SetError("Invalid joystick");
}
if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL) || (attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) {
return SDL_Unsupported();
}
attachment->left_impulse_level = (Uint8)(left_rumble / 655);
attachment->right_impulse_level = (Uint8)(right_rumble / 655);
attachment->rumble_pending = true;
return HIDAPI_DriverGIP_UpdateRumble(attachment);
}
static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick);
Uint32 result = 0;
if (!attachment) {
return 0;
}
if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) {
result |= SDL_JOYSTICK_CAP_RUMBLE;
if (!(attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) {
result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE;
}
}
if (attachment->features & GIP_FEATURE_GUIDE_COLOR) {
result |= SDL_JOYSTICK_CAP_RGB_LED;
}
return result;
}
static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick);
Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
if (!attachment) {
return SDL_SetError("Invalid joystick");
}
if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR)) {
return SDL_Unsupported();
}
buffer[1] = 0x00; buffer[2] = red;
buffer[3] = green;
buffer[4] = blue;
if (!GIP_SendVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) {
return SDL_SetError("Couldn't send LED packet");
}
return true;
}
static bool HIDAPI_DriverGIP_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverGIP_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device)
{
GIP_Device *ctx = (GIP_Device *)device->context;
Uint8 bytes[USB_PACKET_LENGTH];
int i;
int num_bytes;
bool perform_reset = false;
Uint64 timestamp;
while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) {
ctx->timeout = 0;
GIP_ReceivePacket(ctx, bytes, num_bytes);
}
timestamp = SDL_GetTicks();
if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) {
ctx->hello_deadline = 0;
perform_reset = true;
}
for (i = 0; i < MAX_ATTACHMENTS; i++) {
GIP_Attachment *attachment = ctx->attachments[i];
if (!attachment) {
continue;
}
if (attachment->fragment_message && timestamp >= attachment->fragment_timer + 1000) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed");
attachment->fragment_message = 0;
}
if (!perform_reset &&
attachment->got_metadata == GIP_METADATA_PENDING &&
timestamp >= attachment->metadata_next &&
attachment->fragment_message != GIP_CMD_METADATA)
{
if (attachment->metadata_retries < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request");
attachment->metadata_retries++;
attachment->metadata_next = timestamp + 500;
GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0);
} else {
perform_reset = true;
}
}
if (perform_reset) {
if (ctx->reset_for_metadata) {
GIP_SendSetDeviceState(attachment, GIP_STATE_RESET);
} else {
GIP_SetMetadataDefaults(attachment);
GIP_SendInitSequence(attachment);
}
perform_reset = false;
}
HIDAPI_DriverGIP_UpdateRumble(attachment);
}
if (num_bytes < 0 && device->num_joysticks > 0) {
for (i = 0; i < MAX_ATTACHMENTS; i++) {
GIP_Attachment *attachment = ctx->attachments[i];
if (attachment) {
HIDAPI_JoystickDisconnected(device, attachment->joystick);
}
}
}
return (num_bytes >= 0);
}
static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device)
{
GIP_Device *context = (GIP_Device *)device->context;
int i;
for (i = 0; i < MAX_ATTACHMENTS; i++) {
GIP_Attachment *attachment = context->attachments[i];
if (!attachment) {
continue;
}
if (attachment->fragment_data) {
SDL_free(attachment->fragment_data);
attachment->fragment_data = NULL;
}
if (attachment->keyboard) {
SDL_RemoveKeyboard(attachment->keyboard);
}
GIP_MetadataFree(&attachment->metadata);
SDL_free(attachment);
context->attachments[i] = NULL;
}
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = {
SDL_HINT_JOYSTICK_HIDAPI_GIP,
true,
HIDAPI_DriverGIP_RegisterHints,
HIDAPI_DriverGIP_UnregisterHints,
HIDAPI_DriverGIP_IsEnabled,
HIDAPI_DriverGIP_IsSupportedDevice,
HIDAPI_DriverGIP_InitDevice,
HIDAPI_DriverGIP_GetDevicePlayerIndex,
HIDAPI_DriverGIP_SetDevicePlayerIndex,
HIDAPI_DriverGIP_UpdateDevice,
HIDAPI_DriverGIP_OpenJoystick,
HIDAPI_DriverGIP_RumbleJoystick,
HIDAPI_DriverGIP_RumbleJoystickTriggers,
HIDAPI_DriverGIP_GetJoystickCapabilities,
HIDAPI_DriverGIP_SetJoystickLED,
HIDAPI_DriverGIP_SendJoystickEffect,
HIDAPI_DriverGIP_SetJoystickSensorsEnabled,
HIDAPI_DriverGIP_CloseJoystick,
HIDAPI_DriverGIP_FreeDevice,
};
#endif
#endif