From a807b14957038349ee503db491bd6f43a7d74e10 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 22 Jun 2023 14:33:07 -0700 Subject: [PATCH] Greatly improved Xbox One controller initialization sequence --- src/joystick/hidapi/SDL_hidapi_xboxone.c | 933 +++++++++++++++-------- 1 file changed, 609 insertions(+), 324 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index 48c63b59b..94323718b 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -35,7 +35,13 @@ /* Define this if you want to log all packets from the controller */ /*#define DEBUG_XBOX_PROTOCOL*/ -#define CONTROLLER_NEGOTIATION_TIMEOUT_MS 300 +#if defined(__WIN32__) || defined(__WINGDK__) +#define XBOX_ONE_DRIVER_ACTIVE 1 +#else +#define XBOX_ONE_DRIVER_ACTIVE 0 +#endif + +#define CONTROLLER_IDENTIFY_TIMEOUT_MS 100 #define CONTROLLER_PREPARE_INPUT_TIMEOUT_MS 50 /* Deadzone thresholds */ @@ -43,28 +49,28 @@ #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 /* Uint8 30 scaled to Sint16 full range */ -/* Start controller */ -static const Uint8 xboxone_init0[] = { - 0x05, 0x20, 0x03, 0x01, 0x00 +/* Power on */ +static const Uint8 xbox_init_power_on[] = { + 0x05, 0x20, 0x00, 0x01, 0x00 }; /* Enable LED */ -static const Uint8 xboxone_init1[] = { +static const Uint8 xbox_init_enable_led[] = { 0x0A, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14 }; +/* This controller passed security check */ +static const Uint8 xbox_init_security_passed[] = { + 0x06, 0x20, 0x00, 0x02, 0x01, 0x00 +}; /* Some PowerA controllers need to actually start the rumble motors */ -static const Uint8 xboxone_powera_rumble_init[] = { +static const Uint8 xbox_init_powera_rumble[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x1D, 0x1D, 0xFF, 0x00, 0x00 }; /* Setup rumble (not needed for Microsoft controllers, but it doesn't hurt) */ -static const Uint8 xboxone_init2[] = { +static const Uint8 xbox_init_rumble[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; -/* This controller passed security check */ -static const Uint8 security_passed_packet[] = { - 0x06, 0x20, 0x00, 0x02, 0x01, 0x00 -}; /* * This specifies the selection of init packets that a gamepad @@ -76,28 +82,25 @@ typedef struct { Uint16 vendor_id; Uint16 product_id; - Uint16 exclude_vendor_id; - Uint16 exclude_product_id; const Uint8 *data; int size; - const Uint8 response[2]; } SDL_DriverXboxOne_InitPacket; static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = { - { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init0, sizeof(xboxone_init0), { 0x00, 0x00 } }, - { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init1, sizeof(xboxone_init1), { 0x00, 0x00 } }, - /* The PDP Rock Candy and Victrix Gambit controllers don't start sending input until they get this packet */ - { 0x0e6f, 0x0000, 0x0000, 0x0000, security_passed_packet, sizeof(security_passed_packet), { 0x00, 0x00 } }, - { 0x24c6, 0x541a, 0x0000, 0x0000, xboxone_powera_rumble_init, sizeof(xboxone_powera_rumble_init), { 0x00, 0x00 } }, - { 0x24c6, 0x542a, 0x0000, 0x0000, xboxone_powera_rumble_init, sizeof(xboxone_powera_rumble_init), { 0x00, 0x00 } }, - { 0x24c6, 0x543a, 0x0000, 0x0000, xboxone_powera_rumble_init, sizeof(xboxone_powera_rumble_init), { 0x00, 0x00 } }, - { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init2, sizeof(xboxone_init2), { 0x00, 0x00 } }, + { 0x0000, 0x0000, xbox_init_power_on, sizeof(xbox_init_power_on) }, + { 0x0000, 0x0000, xbox_init_enable_led, sizeof(xbox_init_enable_led) }, + { 0x0000, 0x0000, xbox_init_security_passed, sizeof(xbox_init_security_passed) }, + { 0x24c6, 0x541a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x24c6, 0x542a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x24c6, 0x543a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x0000, 0x0000, xbox_init_rumble, sizeof(xbox_init_rumble) }, }; typedef enum { - XBOX_ONE_INIT_STATE_START_NEGOTIATING, - XBOX_ONE_INIT_STATE_NEGOTIATING, + XBOX_ONE_INIT_STATE_ANNOUNCED, + XBOX_ONE_INIT_STATE_IDENTIFYING, + XBOX_ONE_INIT_STATE_STARTUP, XBOX_ONE_INIT_STATE_PREPARE_INPUT, XBOX_ONE_INIT_STATE_COMPLETE, } SDL_XboxOneInitState; @@ -116,7 +119,6 @@ typedef struct Uint16 product_id; SDL_bool bluetooth; SDL_XboxOneInitState init_state; - int init_packet; Uint64 start_time; Uint8 sequence; Uint64 send_time; @@ -135,6 +137,8 @@ typedef struct Uint64 rumble_time; SDL_bool rumble_pending; Uint8 last_state[USB_PACKET_LENGTH]; + Uint8 *chunk_buffer; + Uint32 chunk_length; } SDL_DriverXboxOne_Context; static SDL_bool ControllerHasColorLED(Uint16 vendor_id, Uint16 product_id) @@ -210,72 +214,86 @@ static void SetInitState(SDL_DriverXboxOne_Context *ctx, SDL_XboxOneInitState st ctx->init_state = state; } -static void SendAckIfNeeded(SDL_HIDAPI_Device *device, const Uint8 *data, int size) +static Uint8 GetNextPacketSequence(SDL_DriverXboxOne_Context *ctx) { -#if defined(__WIN32__) || defined(__WINGDK__) - /* The Windows driver is taking care of acks */ -#else - if ((data[1] & 0x30) == 0x30) { - Uint8 ack_packet[] = { 0x01, 0x20, 0x00, 0x09, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - ack_packet[2] = data[2]; - ack_packet[5] = data[0]; - ack_packet[7] = data[3]; - - /* The initial ack needs 0x80 added to the response, for some reason */ - if (data[0] == 0x04 && data[1] == 0xF0) { - ack_packet[11] = 0x80; - } - -#ifdef DEBUG_XBOX_PROTOCOL - HIDAPI_DumpPacket("Xbox One sending ACK packet: size = %d", ack_packet, sizeof(ack_packet)); -#endif - if (SDL_HIDAPI_LockRumble() != 0 || - SDL_HIDAPI_SendRumbleAndUnlock(device, ack_packet, sizeof(ack_packet)) != sizeof(ack_packet)) { - SDL_SetError("Couldn't send ack packet"); - } + ++ctx->sequence; + if (!ctx->sequence) { + ctx->sequence = 1; } -#endif /* defined(__WIN32__) || defined(__WINGDK__ */ + return ctx->sequence; } -#if 0 -static SDL_bool SendSerialRequest(SDL_HIDAPI_Device *device, SDL_DriverXboxOne_Context *ctx) +static SDL_bool SendProtocolPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { - Uint8 serial_packet[] = { 0x1E, 0x30, 0x07, 0x01, 0x04 }; +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox One sending packet: size = %d", data, size); +#endif ctx->send_time = SDL_GetTicks(); + if (SDL_HIDAPI_LockRumble() != 0) { + return SDL_FALSE; + } + if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) != size) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +#if 0 +static SDL_bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx) +{ + Uint8 packet[] = { 0x1E, 0x20, 0x00, 0x01, 0x04 }; + + packet[2] = GetNextPacketSequence(ctx); + /* Request the serial number - * Sending this should be done only after the negotiation is complete. + * Sending this should be done only after startup is complete. * It will cancel the announce packet if sent before that, and will be - * ignored if sent during the negotiation. + * ignored if sent during the startup sequence. */ - if (SDL_HIDAPI_LockRumble() != 0 || - SDL_HIDAPI_SendRumbleAndUnlock(device, serial_packet, sizeof(serial_packet)) != sizeof(serial_packet)) { - SDL_SetError("Couldn't send serial packet"); + if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { + SDL_SetError("Couldn't send serial request packet"); return SDL_FALSE; } return SDL_TRUE; } #endif -static SDL_bool ControllerNeedsNegotiation(SDL_DriverXboxOne_Context *ctx) +static SDL_bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id) { - if (ctx->vendor_id == USB_VENDOR_PDP && ctx->product_id == 0x0246) { + if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) { /* The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason */ - return SDL_TRUE; + return SDL_FALSE; } - return SDL_FALSE; + return SDL_TRUE; } -static SDL_bool SendControllerInit(SDL_HIDAPI_Device *device, SDL_DriverXboxOne_Context *ctx) +static SDL_bool SendIdentificationRequest(SDL_DriverXboxOne_Context *ctx) +{ + /* Request identification, sent in response to announce packet */ + Uint8 packet[] = { + 0x04, 0x20, 0x00, 0x00 + }; + + packet[2] = GetNextPacketSequence(ctx); + + if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { + SDL_SetError("Couldn't send identification request packet"); + return SDL_FALSE; + } + return SDL_TRUE; +} + +static SDL_bool SendControllerStartup(SDL_DriverXboxOne_Context *ctx) { Uint16 vendor_id = ctx->vendor_id; Uint16 product_id = ctx->product_id; Uint8 init_packet[USB_PACKET_LENGTH]; + size_t i; - for (; ctx->init_packet < SDL_arraysize(xboxone_init_packets); ++ctx->init_packet) { - const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[ctx->init_packet]; + for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { + const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; if (packet->vendor_id && (vendor_id != packet->vendor_id)) { continue; @@ -285,48 +303,26 @@ static SDL_bool SendControllerInit(SDL_HIDAPI_Device *device, SDL_DriverXboxOne_ continue; } - if (packet->exclude_vendor_id && (vendor_id == packet->exclude_vendor_id)) { - continue; - } - - if (packet->exclude_product_id && (product_id == packet->exclude_product_id)) { - continue; - } - SDL_memcpy(init_packet, packet->data, packet->size); - if (init_packet[0] != 0x01) { - init_packet[2] = ctx->sequence++; - } + init_packet[2] = GetNextPacketSequence(ctx); + if (init_packet[0] == 0x0A) { /* Get the initial brightness value */ int brightness = GetHomeLEDBrightness(SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED)); init_packet[5] = (brightness > 0) ? 0x01 : 0x00; init_packet[6] = (Uint8)brightness; } -#ifdef DEBUG_XBOX_PROTOCOL - HIDAPI_DumpPacket("Xbox One sending INIT packet: size = %d", init_packet, packet->size); -#endif - ctx->send_time = SDL_GetTicks(); - if (SDL_HIDAPI_LockRumble() != 0 || - SDL_HIDAPI_SendRumbleAndUnlock(device, init_packet, packet->size) != packet->size) { - SDL_SetError("Couldn't write Xbox One initialization packet"); + if (!SendProtocolPacket(ctx, init_packet, packet->size)) { + SDL_SetError("Couldn't send initialization packet"); return SDL_FALSE; } - if (packet->response[0]) { - return SDL_TRUE; - } - /* Wait to process the rumble packet */ - if (packet->data == xboxone_powera_rumble_init) { + if (packet->data == xbox_init_powera_rumble) { SDL_Delay(10); } } - - /* All done with the negotiation, prepare for input! */ - SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT); - return SDL_TRUE; } @@ -374,17 +370,17 @@ static SDL_bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) ctx->vendor_id = device->vendor_id; ctx->product_id = device->product_id; - ctx->bluetooth = SDL_IsJoystickBluetoothXboxOne(device->vendor_id, device->product_id); ctx->start_time = SDL_GetTicks(); - ctx->sequence = 1; + ctx->sequence = 0; ctx->has_color_led = ControllerHasColorLED(ctx->vendor_id, ctx->product_id); ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id); ctx->has_trigger_rumble = ControllerHasTriggerRumble(ctx->vendor_id, ctx->product_id); ctx->has_share_button = ControllerHasShareButton(ctx->vendor_id, ctx->product_id); /* Assume that the controller is correctly initialized when we start */ - if (ControllerNeedsNegotiation(ctx)) { - ctx->init_state = XBOX_ONE_INIT_STATE_START_NEGOTIATING; + if (!ControllerSendsAnnouncement(device->vendor_id, device->product_id)) { + /* Jump into the startup sequence for this controller */ + ctx->init_state = XBOX_ONE_INIT_STATE_STARTUP; } else { ctx->init_state = XBOX_ONE_INIT_STATE_COMPLETE; } @@ -432,7 +428,7 @@ static SDL_bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL } joystick->naxes = SDL_GAMEPAD_AXIS_MAX; - if (!ctx->bluetooth) { + if (!device->is_bluetooth) { joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; } @@ -447,10 +443,8 @@ static void HIDAPI_DriverXboxOne_RumbleSent(void *userdata) ctx->rumble_time = SDL_GetTicks(); } -static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) +static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_DriverXboxOne_Context *ctx) { - SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; - if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) { if (ctx->rumble_time) { ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY; @@ -458,7 +452,7 @@ static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) } if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) { - const int RUMBLE_BUSY_TIME_MS = ctx->bluetooth ? 50 : 10; + const int RUMBLE_BUSY_TIME_MS = ctx->device->is_bluetooth ? 50 : 10; if (SDL_GetTicks() >= (ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) { ctx->rumble_time = 0; ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE; @@ -480,7 +474,7 @@ static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) return -1; } - if (ctx->bluetooth) { + if (ctx->device->is_bluetooth) { Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; rumble_packet[2] = ctx->left_trigger_rumble; @@ -488,7 +482,7 @@ static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) rumble_packet[4] = ctx->low_frequency_rumble; rumble_packet[5] = ctx->high_frequency_rumble; - if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { + if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { return SDL_SetError("Couldn't send rumble packet"); } } else { @@ -499,7 +493,7 @@ static int HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) rumble_packet[8] = ctx->low_frequency_rumble; rumble_packet[9] = ctx->high_frequency_rumble; - if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { + if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { return SDL_SetError("Couldn't send rumble packet"); } } @@ -518,7 +512,7 @@ static int HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Jo ctx->high_frequency_rumble = (Uint8)(high_frequency_rumble / 655); ctx->rumble_pending = SDL_TRUE; - return HIDAPI_DriverXboxOne_UpdateRumble(device); + return HIDAPI_DriverXboxOne_UpdateRumble(ctx); } static int HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) @@ -534,7 +528,7 @@ static int HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device ctx->right_trigger_rumble = (Uint8)(right_rumble / 655); ctx->rumble_pending = SDL_TRUE; - return HIDAPI_DriverXboxOne_UpdateRumble(device); + return HIDAPI_DriverXboxOne_UpdateRumble(ctx); } static Uint32 HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) @@ -600,18 +594,18 @@ static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystic SDL_bool paddles_mapped; Uint64 timestamp = SDL_GetTicksNS(); - if (size == 21) { + if (size == 17) { /* XBox One Elite Series 2 */ - paddle_index = 18; + paddle_index = 14; button1_bit = 0x01; button2_bit = 0x02; button3_bit = 0x04; button4_bit = 0x08; - profile = data[19]; + profile = data[15]; if (profile == 0) { paddles_mapped = SDL_FALSE; - } else if (SDL_memcmp(&data[4], &ctx->last_state[4], 14) == 0) { + } else if (SDL_memcmp(&data[0], &ctx->last_state[0], 14) == 0) { /* We're using a profile, but paddles aren't mapped */ paddles_mapped = SDL_FALSE; } else { @@ -653,87 +647,87 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D Sint16 axis; Uint64 timestamp = SDL_GetTicksNS(); - /* Some controllers have larger packets over NDIS, but the real size is in data[3] */ - size = SDL_min(4 + data[3], size); - /* Enable paddles on the Xbox Elite controller when connected over USB */ - if (ctx->has_paddles && !ctx->has_unmapped_state && size == 50) { + if (ctx->has_paddles && !ctx->has_unmapped_state && size == 46) { Uint8 packet[] = { 0x4d, 0x00, 0x00, 0x02, 0x07, 0x00 }; +#ifdef DEBUG_JOYSTICK + SDL_Log("Enabling paddles on XBox Elite 2\n"); +#endif SDL_HIDAPI_SendRumble(ctx->device, packet, sizeof(packet)); } - if (ctx->last_state[4] != data[4]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[4] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[4] & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[0] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (data[0] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_A, (data[0] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_B, (data[0] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_X, (data[0] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_Y, (data[0] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } - if (ctx->last_state[5] != data[5]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[5] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[5] & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[5] & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[5] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, (data[1] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, (data[1] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, (data[1] & 0x08) ? SDL_PRESSED : SDL_RELEASED); if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) { /* The Razer Atrox has the right and left shoulder bits reversed */ - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[5] & 0x20) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[5] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED); } else { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[5] & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[5] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (data[1] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (data[1] & 0x20) ? SDL_PRESSED : SDL_RELEASED); } - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[5] & 0x40) ? SDL_PRESSED : SDL_RELEASED); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[5] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, (data[1] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[1] & 0x80) ? SDL_PRESSED : SDL_RELEASED); } if (ctx->has_share_button) { - /* Xbox Series X firmware version 5.0, report is 36 bytes, share button is in byte 18 - * Xbox Series X firmware version 5.1, report is 44 bytes, share button is in byte 18 - * Xbox Series X firmware version 5.5, report is 48 bytes, share button is in byte 22 - * Victrix Gambit Tournament Controller, report is 50 bytes, share button is in byte 32 - * ThrustMaster eSwap PRO Controller Xbox, report is 64 bytes, share button is in byte 46 + /* Xbox Series X firmware version 5.0, report is 32 bytes, share button is in byte 14 + * Xbox Series X firmware version 5.1, report is 40 bytes, share button is in byte 14 + * Xbox Series X firmware version 5.5, report is 44 bytes, share button is in byte 18 + * Victrix Gambit Tournament Controller, report is 46 bytes, share button is in byte 28 + * ThrustMaster eSwap PRO Controller Xbox, report is 60 bytes, share button is in byte 42 */ - if (size < 48) { + if (size < 44) { + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } + } else if (size == 44) { if (ctx->last_state[18] != data[18]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[18] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } - } else if (size == 48) { - if (ctx->last_state[22] != data[22]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[22] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } else if (size == 46) { + if (ctx->last_state[28] != data[28]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[28] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } - } else if (size == 50) { - if (ctx->last_state[32] != data[32]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[32] & 0x01) ? SDL_PRESSED : SDL_RELEASED); - } - } else if (size == 64) { - if (ctx->last_state[46] != data[46]) { - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[46] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } else if (size == 60) { + if (ctx->last_state[42] != data[42]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, (data[42] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } } } - /* Xbox One S report is 18 bytes - Xbox One Elite Series 1 report is 33 bytes, paddles in data[32], mode in data[32] & 0x10, both modes have mapped paddles by default + /* Xbox One S report is 14 bytes + Xbox One Elite Series 1 report is 29 bytes, paddles in data[28], mode in data[28] & 0x10, both modes have mapped paddles by default Paddle bits: P3: 0x01 (A) P1: 0x02 (B) P4: 0x04 (X) P2: 0x08 (Y) - Xbox One Elite Series 2 4.x firmware report is 38 bytes, paddles in data[18], mode in data[19], mode 0 has no mapped paddles by default + Xbox One Elite Series 2 4.x firmware report is 34 bytes, paddles in data[14], mode in data[15], mode 0 has no mapped paddles by default Paddle bits: P3: 0x04 (A) P1: 0x01 (B) P4: 0x08 (X) P2: 0x02 (Y) - Xbox One Elite Series 2 5.x firmware report is 50 bytes, paddles in data[22], mode in data[23], mode 0 has no mapped paddles by default + Xbox One Elite Series 2 5.x firmware report is 46 bytes, paddles in data[18], mode in data[19], mode 0 has no mapped paddles by default Paddle bits: P3: 0x04 (A) P1: 0x01 (B) P4: 0x08 (X) P2: 0x02 (Y) - Xbox One Elite Series 2 5.17+ firmware report is 51 bytes, paddles in data[18], mode in data[24], mode 0 has no mapped paddles by default + Xbox One Elite Series 2 5.17+ firmware report is 47 bytes, paddles in data[14], mode in data[20], mode 0 has no mapped paddles by default Paddle bits: P3: 0x04 (A) P1: 0x01 (B) P4: 0x08 (X) P2: 0x02 (Y) */ - if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 33 || size == 38 || size == 50 || size == 51)) { + if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 29 || size == 34 || size == 46 || size == 47)) { int paddle_index; int button1_bit; int button2_bit; @@ -741,18 +735,27 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D int button4_bit; SDL_bool paddles_mapped; - if (size == 33) { + if (size == 29) { /* XBox One Elite Series 1 */ - paddle_index = 32; + paddle_index = 28; button1_bit = 0x02; button2_bit = 0x08; button3_bit = 0x01; button4_bit = 0x04; - /* The mapped controller state is at offset 4, the raw state is at offset 18, compare them to see if the paddles are mapped */ - paddles_mapped = (SDL_memcmp(&data[4], &data[18], 2) != 0); + /* The mapped controller state is at offset 0, the raw state is at offset 14, compare them to see if the paddles are mapped */ + paddles_mapped = (SDL_memcmp(&data[0], &data[14], 2) != 0); - } else if (size == 38) { + } else if (size == 34) { + /* XBox One Elite Series 2 */ + paddle_index = 14; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[15] != 0); + + } else if (size == 46) { /* XBox One Elite Series 2 */ paddle_index = 18; button1_bit = 0x01; @@ -760,23 +763,14 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D button3_bit = 0x04; button4_bit = 0x08; paddles_mapped = (data[19] != 0); - - } else if (size == 50) { + } else /* if (size == 47) */ { /* XBox One Elite Series 2 */ - paddle_index = 22; + paddle_index = 14; button1_bit = 0x01; button2_bit = 0x02; button3_bit = 0x04; button4_bit = 0x08; - paddles_mapped = (data[23] != 0); - } else /* if (size == 51) */ { - /* XBox One Elite Series 2 */ - paddle_index = 18; - button1_bit = 0x01; - button2_bit = 0x02; - button3_bit = 0x04; - button4_bit = 0x08; - paddles_mapped = (data[24] != 0); + paddles_mapped = (data[20] != 0); } #ifdef DEBUG_XBOX_PROTOCOL SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s\n", @@ -802,17 +796,17 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } } - axis = ((int)SDL_SwapLE16(*(Sint16 *)(&data[6])) * 64) - 32768; + axis = ((int)SDL_SwapLE16(*(Sint16 *)(&data[2])) * 64) - 32768; if (axis == 32704) { axis = 32767; } - if (axis == -32768 && size == 30 && (data[22] & 0x80)) { + if (axis == -32768 && size == 26 && (data[18] & 0x80)) { axis = 32767; } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); - axis = ((int)SDL_SwapLE16(*(Sint16 *)(&data[8])) * 64) - 32768; - if (axis == -32768 && size == 30 && (data[22] & 0x40)) { + axis = ((int)SDL_SwapLE16(*(Sint16 *)(&data[4])) * 64) - 32768; + if (axis == -32768 && size == 26 && (data[18] & 0x40)) { axis = 32767; } if (axis == 32704) { @@ -820,13 +814,13 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - axis = SDL_SwapLE16(*(Sint16 *)(&data[10])); + axis = SDL_SwapLE16(*(Sint16 *)(&data[6])); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = SDL_SwapLE16(*(Sint16 *)(&data[12])); + axis = SDL_SwapLE16(*(Sint16 *)(&data[8])); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); - axis = SDL_SwapLE16(*(Sint16 *)(&data[14])); + axis = SDL_SwapLE16(*(Sint16 *)(&data[10])); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = SDL_SwapLE16(*(Sint16 *)(&data[16])); + axis = SDL_SwapLE16(*(Sint16 *)(&data[12])); SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); @@ -835,7 +829,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D ctx->has_unmapped_state = SDL_FALSE; } -static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); @@ -846,7 +840,7 @@ static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_Dr { Uint64 timestamp = SDL_GetTicksNS(); - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[0] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } /* @@ -1086,61 +1080,60 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joys } } -#ifdef SET_SERIAL_AFTER_OPEN -static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) { char serial[29]; int i; for (i = 0; i < 14; ++i) { - SDL_uitoa(data[6 + i], &serial[i * 2], 16); + SDL_uitoa(data[2 + i], &serial[i * 2], 16); } serial[i * 2] = '\0'; - if (!joystick->serial || SDL_strcmp(joystick->serial, serial) != 0) { #ifdef DEBUG_JOYSTICK - SDL_Log("Setting serial number to %s\n", serial); + SDL_Log("Setting serial number to %s\n", serial); #endif - joystick->serial = SDL_strdup(serial); - } + HIDAPI_SetDeviceSerial(ctx->device, serial); } -#endif /* SET_SERIAL_AFTER_OPEN */ -static SDL_bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_HIDAPI_Device *device, SDL_DriverXboxOne_Context *ctx) +static SDL_bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context *ctx) { SDL_XboxOneInitState prev_state; do { prev_state = ctx->init_state; switch (ctx->init_state) { - case XBOX_ONE_INIT_STATE_START_NEGOTIATING: -#if defined(__WIN32__) || defined(__WINGDK__) - /* The Windows driver is taking care of negotiation */ - SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); -#else - SetInitState(ctx, XBOX_ONE_INIT_STATE_NEGOTIATING); - ctx->init_packet = 0; - if (!SendControllerInit(device, ctx)) { - return SDL_FALSE; + case XBOX_ONE_INIT_STATE_ANNOUNCED: + if (XBOX_ONE_DRIVER_ACTIVE) { + /* The driver is taking care of identification */ + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } else { + SendIdentificationRequest(ctx); + SetInitState(ctx, XBOX_ONE_INIT_STATE_IDENTIFYING); } -#endif break; - case XBOX_ONE_INIT_STATE_NEGOTIATING: - if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_NEGOTIATION_TIMEOUT_MS)) { + case XBOX_ONE_INIT_STATE_IDENTIFYING: + if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_IDENTIFY_TIMEOUT_MS)) { /* We haven't heard anything, let's move on */ #ifdef DEBUG_JOYSTICK - SDL_Log("Init sequence %d timed out after %u ms\n", ctx->init_packet, (SDL_GetTicks() - ctx->send_time)); + SDL_Log("Identification request timed out after %llu ms\n", (SDL_GetTicks() - ctx->send_time)); #endif - ++ctx->init_packet; - if (!SendControllerInit(device, ctx)) { - return SDL_FALSE; - } + SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); + } + break; + case XBOX_ONE_INIT_STATE_STARTUP: + if (XBOX_ONE_DRIVER_ACTIVE) { + /* The driver is taking care of startup */ + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } else { + SendControllerStartup(ctx); + SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT); } break; case XBOX_ONE_INIT_STATE_PREPARE_INPUT: if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_PREPARE_INPUT_TIMEOUT_MS)) { #ifdef DEBUG_JOYSTICK - SDL_Log("Prepare input complete after %u ms\n", (SDL_GetTicks() - ctx->send_time)); + SDL_Log("Prepare input complete after %llu ms\n", (SDL_GetTicks() - ctx->send_time)); #endif SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); } @@ -1154,6 +1147,408 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_HIDAPI_Device *device, return SDL_TRUE; } +#define GIP_HEADER_MIN_LENGTH 3 + +/* Internal commands */ +#define GIP_CMD_ACKNOWLEDGE 0x01 +#define GIP_CMD_ANNOUNCE 0x02 +#define GIP_CMD_STATUS 0x03 +#define GIP_CMD_IDENTIFY 0x04 +#define GIP_CMD_POWER 0x05 +#define GIP_CMD_AUTHENTICATE 0x06 +#define GIP_CMD_VIRTUAL_KEY 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_SERIAL_NUMBER 0x1E +#define GIP_CMD_AUDIO_SAMPLES 0x60 + +/* External commands */ +#define GIP_CMD_RUMBLE 0x09 +#define GIP_CMD_UNMAPPED_STATE 0x0C +#define GIP_CMD_INPUT 0x20 + +/* Header option flags */ +#define GIP_OPT_ACKNOWLEDGE 0x10 +#define GIP_OPT_INTERNAL 0x20 +#define GIP_OPT_CHUNK_START 0x40 +#define GIP_OPT_CHUNK 0x80 + +#pragma pack(push, 1) + +struct gip_header { + Uint8 command; + Uint8 options; + Uint8 sequence; + Uint32 packet_length; + Uint32 chunk_offset; +}; + +struct gip_pkt_acknowledge { + Uint8 unknown; + Uint8 command; + Uint8 options; + Uint16 length; + Uint8 padding[2]; + Uint16 remaining; +}; + +#pragma pack(pop) + +static int EncodeVariableInt(Uint8 *buf, Uint32 val) +{ + int i; + + for (i = 0; i < sizeof(val); i++) { + buf[i] = val; + if (val > 0x7F) { + buf[i] |= 0x80; + } + + val >>= 7; + if (!val) { + break; + } + } + return i + 1; +} + +static int DecodeVariableInt(const Uint8 *data, int len, Uint32 *val) +{ + int i; + + for (i = 0; i < sizeof(*val) && i < len; i++) { + *val |= (data[i] & 0x7F) << (i * 7); + + if (!(data[i] & 0x80)) { + break; + } + } + return i + 1; +} + +static int HIDAPI_GIP_GetActualHeaderLength(struct gip_header *hdr) +{ + Uint32 pkt_len = hdr->packet_length; + Uint32 chunk_offset = hdr->chunk_offset; + int len = GIP_HEADER_MIN_LENGTH; + + do { + len++; + pkt_len >>= 7; + } while (pkt_len); + + if (hdr->options & GIP_OPT_CHUNK) { + while (chunk_offset) { + len++; + chunk_offset >>= 7; + } + } + + return len; +} + +static int HIDAPI_GIP_GetHeaderLength(struct gip_header *hdr) +{ + int len = HIDAPI_GIP_GetActualHeaderLength(hdr); + + /* Header length must be even */ + return len + (len % 2); +} + +static void HIDAPI_GIP_EncodeHeader(struct gip_header *hdr, Uint8 *buf) +{ + int hdr_len = 0; + + buf[hdr_len++] = hdr->command; + buf[hdr_len++] = hdr->options; + buf[hdr_len++] = hdr->sequence; + + hdr_len += EncodeVariableInt(buf + hdr_len, hdr->packet_length); + + /* Header length must be even */ + if (HIDAPI_GIP_GetActualHeaderLength(hdr) % 2) { + buf[hdr_len - 1] |= 0x80; + buf[hdr_len++] = 0; + } + + if (hdr->options & GIP_OPT_CHUNK) { + EncodeVariableInt(buf + hdr_len, hdr->chunk_offset); + } +} + +static int HIDAPI_GIP_DecodeHeader(struct gip_header *hdr, const Uint8 *data, int len) +{ + int hdr_len = 0; + + hdr->command = data[hdr_len++]; + hdr->options = data[hdr_len++]; + hdr->sequence = data[hdr_len++]; + hdr->packet_length = 0; + hdr->chunk_offset = 0; + + hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->packet_length); + + if (hdr->options & GIP_OPT_CHUNK) { + hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->chunk_offset); + } + return hdr_len; +} + +static SDL_bool HIDAPI_GIP_SendPacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, const void *data) +{ + Uint8 packet[USB_PACKET_LENGTH]; + int hdr_len, size; + + hdr_len = HIDAPI_GIP_GetHeaderLength(hdr); + size = (hdr_len + hdr->packet_length); + if (size > sizeof(packet)) { + SDL_SetError("Couldn't send GIP packet, size (%d) too large\n", size); + return SDL_FALSE; + } + + if (!hdr->sequence) { + hdr->sequence = GetNextPacketSequence(ctx); + } + + HIDAPI_GIP_EncodeHeader(hdr, packet); + if (data) { + SDL_memcpy(&packet[hdr_len], data, hdr->packet_length); + } + + if (!SendProtocolPacket(ctx, packet, size)) { + SDL_SetError("Couldn't send protocol packet"); + return SDL_FALSE; + } + return SDL_TRUE; +} + +static SDL_bool HIDAPI_GIP_AcknowledgePacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *ack) +{ + if (XBOX_ONE_DRIVER_ACTIVE) { + /* The driver is taking care of acks */ + return SDL_TRUE; + } else { + struct gip_header hdr; + struct gip_pkt_acknowledge pkt; + + SDL_zero(hdr); + hdr.command = GIP_CMD_ACKNOWLEDGE; + hdr.options = GIP_OPT_INTERNAL; + hdr.sequence = ack->sequence; + hdr.packet_length = sizeof(pkt); + + SDL_zero(pkt); + pkt.command = ack->command; + pkt.options = GIP_OPT_INTERNAL; + pkt.length = SDL_SwapLE16(ack->chunk_offset + ack->packet_length); + + if ((ack->options & GIP_OPT_CHUNK) && ctx->chunk_buffer) { + pkt.remaining = SDL_SwapLE16(ctx->chunk_length - pkt.length); + } + + return HIDAPI_GIP_SendPacket(ctx, &hdr, &pkt); + } +} + +static SDL_bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data, Uint32 size) +{ + if ((hdr->options & 0x0F) != 0) { + /* This is a packet for a device plugged into the controller, skip it */ + return SDL_TRUE; + } + + if (hdr->options & GIP_OPT_INTERNAL) { + switch (hdr->command) { + case GIP_CMD_ACKNOWLEDGE: + /* Ignore this packet */ + break; + case GIP_CMD_ANNOUNCE: + /* Controller is connected and waiting for initialization */ + /* The data bytes are: + 0x02 0x20 NN 0x1c, where NN is the packet sequence + then 6 bytes of wireless MAC address + then 2 bytes padding + then 16-bit VID + then 16-bit PID + then 16-bit firmware version quartet AA.BB.CC.DD + e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x00 + is firmware version 5.5.2641.0, and product version 0x0505 = 1285 + then 8 bytes of unknown data + */ +#ifdef DEBUG_JOYSTICK + SDL_Log("Controller announce after %llu ms\n", (SDL_GetTicks() - ctx->start_time)); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_ANNOUNCED); + break; + case GIP_CMD_STATUS: + /* Controller status update */ + HIDAPI_DriverXboxOne_HandleStatusPacket(ctx, data, size); + break; + case GIP_CMD_IDENTIFY: +#ifdef DEBUG_JOYSTICK + SDL_Log("Identification request completed after %llu ms\n", (SDL_GetTicks() - ctx->send_time)); +#endif +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox One identification data: size = %d", data, size); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); + break; + case GIP_CMD_POWER: + /* Ignore this packet */ + break; + case GIP_CMD_AUTHENTICATE: + /* Ignore this packet */ + break; + case GIP_CMD_VIRTUAL_KEY: + if (joystick == NULL) { + break; + } + HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size); + break; + case GIP_CMD_SERIAL_NUMBER: + /* If the packet starts with this: + 0x1E 0x30 0x00 0x10 0x04 0x00 + then the next 14 bytes are the controller serial number + e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x36 + is serial number "3039373132333332333534303336" + + The controller sends that in response to this request: + 0x1E 0x20 0x00 0x01 0x04 + */ + HIDAPI_DriverXboxOne_HandleSerialIDPacket(ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x\n", hdr->command); +#endif + break; + } + } else { + switch (hdr->command) { + case GIP_CMD_INPUT: + if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + + /* Ignore the first input, it may be spurious */ +#ifdef DEBUG_JOYSTICK + SDL_Log("Controller ignoring spurious input\n"); +#endif + break; + } + if (joystick == NULL) { + break; + } + HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size); + break; + case GIP_CMD_UNMAPPED_STATE: + if (joystick == NULL) { + break; + } + HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x\n", hdr->command); +#endif + break; + } + } + return SDL_TRUE; +} + +static void HIDAPI_GIP_DestroyChunkBuffer(SDL_DriverXboxOne_Context *ctx) +{ + if (ctx->chunk_buffer) { + SDL_free(ctx->chunk_buffer); + ctx->chunk_buffer = NULL; + ctx->chunk_length = 0; + } +} + +static SDL_bool HIDAPI_GIP_CreateChunkBuffer(SDL_DriverXboxOne_Context *ctx, Uint32 size) +{ + HIDAPI_GIP_DestroyChunkBuffer(ctx); + + ctx->chunk_buffer = (Uint8 *)SDL_malloc(size); + if (ctx->chunk_buffer) { + ctx->chunk_length = size; + return SDL_TRUE; + } else { + return SDL_FALSE; + } +} + +static SDL_bool HIDAPI_GIP_ProcessPacketChunked(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) +{ + SDL_bool result; + + if (!ctx->chunk_buffer) { + return SDL_FALSE; + } + + if ((hdr->chunk_offset + hdr->packet_length) > ctx->chunk_length) { + return SDL_FALSE; + } + + if (hdr->packet_length) { + SDL_memcpy(ctx->chunk_buffer + hdr->chunk_offset, data, hdr->packet_length); + return SDL_TRUE; + } + + result = HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, ctx->chunk_buffer, ctx->chunk_length); + + HIDAPI_GIP_DestroyChunkBuffer(ctx); + + return result; +} + +static SDL_bool HIDAPI_GIP_ProcessPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) +{ + if (hdr->options & GIP_OPT_CHUNK_START) { + if (!HIDAPI_GIP_CreateChunkBuffer(ctx, hdr->chunk_offset)) { + return SDL_FALSE; + } + ctx->chunk_length = hdr->chunk_offset; + + hdr->chunk_offset = 0; + } + + if (hdr->options & GIP_OPT_ACKNOWLEDGE) { + if (!HIDAPI_GIP_AcknowledgePacket(ctx, hdr)) { + return SDL_FALSE; + } + } + + if (hdr->options & GIP_OPT_CHUNK) { + return HIDAPI_GIP_ProcessPacketChunked(joystick, ctx, hdr, data); + } else { + return HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, data, hdr->packet_length); + } +} + +static SDL_bool HIDAPI_GIP_ProcessData(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + struct gip_header hdr; + int hdr_len; + + while (size > GIP_HEADER_MIN_LENGTH) { + hdr_len = HIDAPI_GIP_DecodeHeader(&hdr, data, size); + if ((hdr_len + hdr.packet_length) > (size_t)size) { + return SDL_FALSE; + } + + if (!HIDAPI_GIP_ProcessPacket(joystick, ctx, &hdr, data + hdr_len)) { + return SDL_FALSE; + } + + data += hdr_len + hdr.packet_length; + size -= hdr_len + hdr.packet_length; + } + return SDL_TRUE; +} + static SDL_bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; @@ -1171,7 +1566,7 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) #ifdef DEBUG_XBOX_PROTOCOL HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size); #endif - if (ctx->bluetooth) { + if (device->is_bluetooth) { switch (data[0]) { case 0x01: if (joystick == NULL) { @@ -1204,125 +1599,12 @@ static SDL_bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) break; } } else { - switch (data[0]) { - case 0x01: - /* ACK packet */ - /* The data bytes are: - 0x01 0x20 NN 0x09, where NN is the packet sequence - then 0x00 - then a byte of the sequence being acked - then 0x20 - then 16-bit LE value, the size of the previous packet payload when it's a single packet - then 4 bytes of unknown data, often all zero - */ - break; - case 0x02: - /* Controller is connected and waiting for initialization */ - /* The data bytes are: - 0x02 0x20 NN 0x1c, where NN is the packet sequence - then 6 bytes of wireless MAC address - then 2 bytes padding - then 16-bit VID - then 16-bit PID - then 16-bit firmware version quartet AA.BB.CC.DD - e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x00 - is firmware version 5.5.2641.0, and product version 0x0505 = 1285 - then 8 bytes of unknown data - */ - if (data[1] == 0x20) { -#ifdef DEBUG_JOYSTICK - SDL_Log("Controller announce after %u ms\n", (SDL_GetTicks() - ctx->start_time)); -#endif - SetInitState(ctx, XBOX_ONE_INIT_STATE_START_NEGOTIATING); - } else { - /* Possibly an announce from a device plugged into the controller */ - } - break; - case 0x03: - /* Controller status update */ - if (joystick == NULL) { - /* We actually want to handle this packet any time it arrives */ - /*break;*/ - } - HIDAPI_DriverXboxOne_HandleStatusPacket(joystick, ctx, data, size); - break; - case 0x04: - /* Unknown chatty controller information, sent by both sides */ - break; - case 0x06: - /* Unknown chatty controller information, sent by both sides */ - break; - case 0x07: - if (joystick == NULL) { - break; - } - HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size); - break; - case 0x0C: - if (joystick == NULL) { - break; - } - HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size); - break; - case 0x1E: - /* If the packet starts with this: - 0x1E 0x30 0x07 0x10 0x04 0x00 - then the next 14 bytes are the controller serial number - e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x36 - is serial number "3039373132333332333534303336" - - The controller sends that in response to this request: - 0x1E 0x30 0x07 0x01 0x04 - */ - if (joystick == NULL) { - break; - } -#ifdef SET_SERIAL_AFTER_OPEN - if (size == 20 && data[3] == 0x10) { - HIDAPI_DriverXboxOne_HandleSerialIDPacket(joystick, ctx, data, size); - } -#endif - break; - case 0x20: - if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { - SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); - - /* Ignore the first input, it may be spurious */ -#ifdef DEBUG_JOYSTICK - SDL_Log("Controller ignoring spurious input\n"); -#endif - break; - } - if (joystick == NULL) { - break; - } - HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size); - break; - default: -#ifdef DEBUG_JOYSTICK - SDL_Log("Unknown Xbox One packet: 0x%.2x\n", data[0]); -#endif - break; - } - - SendAckIfNeeded(device, data, size); - - if (ctx->init_state == XBOX_ONE_INIT_STATE_NEGOTIATING) { - const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[ctx->init_packet]; - - if (size >= 4 && data[0] == packet->response[0] && data[1] == packet->response[1]) { -#ifdef DEBUG_JOYSTICK - SDL_Log("Init sequence %d got response after %u ms\n", ctx->init_packet, (SDL_GetTicks() - ctx->send_time)); -#endif - ++ctx->init_packet; - SendControllerInit(device, ctx); - } - } + HIDAPI_GIP_ProcessData(joystick, ctx, data, size); } } - HIDAPI_DriverXboxOne_UpdateInitState(device, ctx); - HIDAPI_DriverXboxOne_UpdateRumble(device); + HIDAPI_DriverXboxOne_UpdateInitState(ctx); + HIDAPI_DriverXboxOne_UpdateRumble(ctx); if (size < 0) { /* Read error, device is disconnected */ @@ -1341,6 +1623,9 @@ static void HIDAPI_DriverXboxOne_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Jo static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device) { + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + HIDAPI_GIP_DestroyChunkBuffer(ctx); } SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = {