| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807 |
- #include "mode_xbox_og.h"
- #include "triton.h"
- #include "gamepad_util.h"
- #include "config.h"
- #include "haptics.h"
- #include "bonds.h"
- #include "usb_tx.h"
- #include "usb_app_drivers.h"
- #include <Adafruit_TinyUSB.h>
- #include <Arduino.h>
- #include <string.h>
- #define XBOX_OG_DESC_LEN (9 + 7 + 7)
- #define XBOX_OG_PACKET_SIZE 32
- #define XBOX_OG_CONN_MS 1200u
- enum {
- XBOX_OG_DUP = 0x01,
- XBOX_OG_DDOWN = 0x02,
- XBOX_OG_DLEFT = 0x04,
- XBOX_OG_DRIGHT = 0x08,
- XBOX_OG_START = 0x10,
- XBOX_OG_BACK = 0x20,
- XBOX_OG_L3 = 0x40,
- XBOX_OG_R3 = 0x80,
- };
- #pragma pack(push, 1)
- struct XboxOgInputReport {
- uint8_t reserved;
- uint8_t length;
- uint8_t buttons;
- uint8_t reserved2;
- uint8_t a;
- uint8_t b;
- uint8_t x;
- uint8_t y;
- uint8_t black;
- uint8_t white;
- uint8_t left_trigger;
- uint8_t right_trigger;
- int16_t left_x;
- int16_t left_y;
- int16_t right_x;
- int16_t right_y;
- };
- struct XboxOgOutputReport {
- uint8_t reserved;
- uint8_t length;
- uint16_t left_motor;
- uint16_t right_motor;
- };
- #pragma pack(pop)
- static const uint8_t XID_DESCRIPTOR[] = {
- 0x10, 0x42, 0x00, 0x01, 0x01, 0x02, 0x14, 0x06,
- 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- };
- static const uint8_t XID_CAPABILITIES_IN[] = {
- 0x00, 0x14, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- };
- static const uint8_t XID_CAPABILITIES_OUT[] = {
- 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF,
- };
- static_assert(sizeof(XboxOgInputReport) == 20,
- "Original Xbox input report must be 20 bytes");
- static_assert(sizeof(XboxOgOutputReport) == 6,
- "Original Xbox output report must be 6 bytes");
- static_assert(sizeof(XID_DESCRIPTOR) == 16, "XID descriptor must be 16 bytes");
- static_assert(sizeof(XID_CAPABILITIES_IN) == 20,
- "XID input capabilities must be 20 bytes");
- static_assert(sizeof(XID_CAPABILITIES_OUT) == 6,
- "XID output capabilities must be 6 bytes");
- struct XboxOgSlot {
- uint8_t interface_number;
- uint8_t endpoint_in;
- uint8_t endpoint_out;
- // The loop owns bond selection and is its only reader -- it snapshots
- // bond_slot when relaying rumble; lifecycle callbacks only clear it.
- volatile int8_t bond_slot;
- // TinyUSB callbacks publish endpoint readiness from the usbd task; the
- // loop owns pending input state.
- volatile bool in_use;
- volatile bool tx_pending;
- // TinyUSB ignores xfer_cb's return value, so the loop drain must remember
- // and recover a failed runtime OUT rearm.
- volatile bool output_rearm_pending;
- // The loop publishes complete reports under PRIMASK; USB only snapshots it.
- XboxOgInputReport latest_report;
- // These four are owned by the USB peripheral, not by a lock, so the
- // rules below hold only while the endpoints are live -- bus reset and
- // deinit clear them wholesale. See xboxOgClearSlot().
- // EP0 IN owns this snapshot until the control transfer completes.
- uint8_t control_in_buffer[sizeof(XboxOgInputReport)];
- // The loop may replace this only while the interrupt IN endpoint is idle.
- CFG_TUD_MEM_ALIGN uint8_t tx_buffer[XBOX_OG_PACKET_SIZE];
- // Interrupt OUT DMA owns this buffer between arm and completion.
- CFG_TUD_MEM_ALIGN uint8_t output_buffer[XBOX_OG_PACKET_SIZE];
- // EP0 OUT owns this buffer from SET_REPORT setup through its data stage.
- uint8_t control_out_buffer[sizeof(XboxOgOutputReport)];
- // USB output callbacks publish this mailbox; the loop consumes it and
- // relays to whichever slot is currently bonded when it does.
- volatile uint16_t rumble_low;
- volatile uint16_t rumble_high;
- volatile bool rumble_pending;
- };
- static XboxOgSlot g_xboxOgSlot;
- static volatile uint32_t g_xboxOgGeneration;
- static unsigned long g_xboxOgNeutralMs;
- static void xboxOgNeutralReport(XboxOgInputReport &report)
- {
- memset(&report, 0, sizeof report);
- report.length = sizeof report;
- }
- // Wholesale clear, DMA buffers included. Every caller runs at a point where
- // no transfer is armed against them: mode setup runs before attach, init /
- // deinit / bus reset arrive after the peripheral has dropped the endpoints,
- // and xboxOgOpen clears before it arms the OUT endpoint (or after closing
- // both on the failure path).
- static void xboxOgClearSlot(void)
- {
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- g_xboxOgGeneration++;
- memset(&g_xboxOgSlot, 0, sizeof g_xboxOgSlot);
- g_xboxOgSlot.bond_slot = -1;
- xboxOgNeutralReport(g_xboxOgSlot.latest_report);
- __set_PRIMASK(primask);
- }
- static void xboxOgSnapshotReport(uint8_t *destination)
- {
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- memcpy(destination, &g_xboxOgSlot.latest_report,
- sizeof g_xboxOgSlot.latest_report);
- __set_PRIMASK(primask);
- }
- static void xboxOgPublishReport(const XboxOgInputReport &report)
- {
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- memcpy(&g_xboxOgSlot.latest_report, &report, sizeof report);
- g_xboxOgSlot.tx_pending = true;
- __set_PRIMASK(primask);
- }
- static bool xboxOgPublishBondReport(const XboxOgInputReport &report, int slot,
- uint32_t generation)
- {
- uint32_t primask = __get_PRIMASK();
- bool published = false;
- __disable_irq();
- if (g_xboxOgGeneration == generation &&
- g_xboxOgSlot.bond_slot == slot) {
- memcpy(&g_xboxOgSlot.latest_report, &report, sizeof report);
- g_xboxOgSlot.tx_pending = true;
- published = true;
- }
- __set_PRIMASK(primask);
- return published;
- }
- static bool xboxOgApplyRumble(XboxOgSlot &slot, const uint8_t *data,
- uint16_t length)
- {
- if (!data || length < sizeof(XboxOgOutputReport) || data[0] != 0x00 ||
- data[1] != sizeof(XboxOgOutputReport))
- return false;
- uint16_t low = (uint16_t)data[2] | ((uint16_t)data[3] << 8);
- uint16_t high = (uint16_t)data[4] | ((uint16_t)data[5] << 8);
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- slot.rumble_low = low;
- slot.rumble_high = high;
- slot.rumble_pending = true;
- __set_PRIMASK(primask);
- return true;
- }
- static void xboxOgRetryOutputRearm(void)
- {
- XboxOgSlot &slot = g_xboxOgSlot;
- uint32_t generation = g_xboxOgGeneration;
- uint8_t endpoint = slot.endpoint_out;
- if (!tud_mounted() || !slot.in_use || !slot.output_rearm_pending ||
- endpoint == 0 || usbd_edpt_busy(0, endpoint) ||
- !usbd_edpt_claim(0, endpoint))
- return;
- if (usbd_edpt_xfer(0, endpoint, slot.output_buffer,
- sizeof slot.output_buffer)) {
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- if (g_xboxOgGeneration == generation && slot.in_use &&
- slot.endpoint_out == endpoint)
- slot.output_rearm_pending = false;
- __set_PRIMASK(primask);
- } else {
- usbd_edpt_release(0, endpoint);
- }
- }
- static void xboxOgTxDrain(void)
- {
- XboxOgSlot &slot = g_xboxOgSlot;
- xboxOgRetryOutputRearm();
- if (!tud_mounted() || !slot.in_use || !slot.tx_pending ||
- slot.endpoint_in == 0 || usbd_edpt_busy(0, slot.endpoint_in))
- return;
- if (!usbd_edpt_claim(0, slot.endpoint_in))
- return;
- xboxOgSnapshotReport(slot.tx_buffer);
- if (usbd_edpt_xfer(0, slot.endpoint_in, slot.tx_buffer,
- sizeof(XboxOgInputReport))) {
- slot.tx_pending = false;
- } else {
- usbd_edpt_release(0, slot.endpoint_in);
- }
- }
- // The console asks for these three before it accepts the controller: 0x06
- // with wValue 0x4200 is the XID descriptor, 0x01 with 0x0100/0x0200 the
- // input and output capabilities. Stalling them makes it refuse the pad.
- static bool xboxOgVendorRequest(const tusb_control_request_t *request)
- {
- if (!g_xboxOgSlot.in_use || request->bmRequestType != 0xC1 ||
- request->wIndex != g_xboxOgSlot.interface_number)
- return false;
- if (request->bRequest == 0x06 && request->wValue == 0x4200)
- return true;
- if (request->bRequest != 0x01)
- return false;
- return request->wValue == 0x0100 || request->wValue == 0x0200;
- }
- bool xboxOgVendorControlXfer(uint8_t rhport, uint8_t stage,
- const tusb_control_request_t *request)
- {
- if (!request || !xboxOgVendorRequest(request))
- return false;
- if (stage != CONTROL_STAGE_SETUP)
- return true;
- if (request->bRequest == 0x06)
- return tud_control_xfer(rhport, request,
- const_cast<uint8_t *>(XID_DESCRIPTOR),
- sizeof XID_DESCRIPTOR);
- if (request->wValue == 0x0100)
- return tud_control_xfer(
- rhport, request,
- const_cast<uint8_t *>(XID_CAPABILITIES_IN),
- sizeof XID_CAPABILITIES_IN);
- return tud_control_xfer(rhport, request,
- const_cast<uint8_t *>(XID_CAPABILITIES_OUT),
- sizeof XID_CAPABILITIES_OUT);
- }
- // Controller S also carries input and rumble over EP0, in parallel with the
- // interrupt endpoints: GET_REPORT (0xA1/0x01, wValue 0x0100) and SET_REPORT
- // (0x21/0x09, wValue 0x0200).
- static bool xboxOgClassControlXfer(uint8_t rhport, uint8_t stage,
- const tusb_control_request_t *request)
- {
- if (!request || !g_xboxOgSlot.in_use ||
- request->wIndex != g_xboxOgSlot.interface_number)
- return false;
- if (request->bmRequestType == 0xA1 && request->bRequest == 0x01 &&
- request->wValue == 0x0100) {
- if (stage == CONTROL_STAGE_SETUP) {
- xboxOgSnapshotReport(g_xboxOgSlot.control_in_buffer);
- return tud_control_xfer(
- rhport, request, g_xboxOgSlot.control_in_buffer,
- sizeof g_xboxOgSlot.control_in_buffer);
- }
- return true;
- }
- if (request->bmRequestType != 0x21 || request->bRequest != 0x09 ||
- request->wValue != 0x0200 ||
- request->wLength != sizeof(XboxOgOutputReport))
- return false;
- if (stage == CONTROL_STAGE_SETUP) {
- memset(g_xboxOgSlot.control_out_buffer, 0,
- sizeof g_xboxOgSlot.control_out_buffer);
- return tud_control_xfer(rhport, request,
- g_xboxOgSlot.control_out_buffer,
- sizeof g_xboxOgSlot.control_out_buffer);
- }
- if (stage == CONTROL_STAGE_DATA)
- return xboxOgApplyRumble(
- g_xboxOgSlot, g_xboxOgSlot.control_out_buffer,
- sizeof g_xboxOgSlot.control_out_buffer);
- return true;
- }
- static void xboxOgInit(void)
- {
- xboxOgClearSlot();
- }
- static bool xboxOgDeinit(void)
- {
- xboxOgClearSlot();
- return true;
- }
- static void xboxOgReset(uint8_t rhport)
- {
- (void)rhport;
- xboxOgClearSlot();
- }
- static bool xboxOgEndpointValid(const tusb_desc_endpoint_t *endpoint)
- {
- return endpoint->bLength == sizeof(tusb_desc_endpoint_t) &&
- endpoint->bDescriptorType == TUSB_DESC_ENDPOINT &&
- endpoint->bmAttributes.xfer == TUSB_XFER_INTERRUPT &&
- endpoint->bmAttributes.sync == 0 &&
- endpoint->bmAttributes.usage == 0 &&
- (endpoint->bEndpointAddress & 0x70) == 0 &&
- tu_edpt_number(endpoint->bEndpointAddress) != 0 &&
- tu_le16toh(endpoint->wMaxPacketSize) == XBOX_OG_PACKET_SIZE &&
- endpoint->bInterval == 4;
- }
- static uint16_t xboxOgOpen(uint8_t rhport,
- const tusb_desc_interface_t *interface,
- uint16_t max_length)
- {
- if (!interface || g_xboxOgSlot.in_use ||
- max_length < sizeof(tusb_desc_interface_t) ||
- interface->bLength != sizeof(tusb_desc_interface_t) ||
- interface->bDescriptorType != TUSB_DESC_INTERFACE ||
- interface->bAlternateSetting != 0 || interface->iInterface != 0 ||
- interface->bNumEndpoints != 2 ||
- interface->bInterfaceClass != 0x58 ||
- interface->bInterfaceSubClass != 0x42 ||
- interface->bInterfaceProtocol != 0x00)
- return 0;
- const uint8_t *start = reinterpret_cast<const uint8_t *>(interface);
- const uint8_t *cursor = start + interface->bLength;
- const uint8_t *end = start + max_length;
- const tusb_desc_endpoint_t *endpoint_in = nullptr;
- const tusb_desc_endpoint_t *endpoint_out = nullptr;
- uint8_t endpoint_count = 0;
- while (cursor < end) {
- uint16_t remaining = (uint16_t)(end - cursor);
- if (remaining < 2 || cursor[0] < 2 || cursor[0] > remaining)
- return 0;
- if (cursor[1] == TUSB_DESC_INTERFACE) {
- if (cursor[0] < sizeof(tusb_desc_interface_t))
- return 0;
- break;
- }
- if (cursor[1] == TUSB_DESC_INTERFACE_ASSOCIATION) {
- if (cursor[0] < sizeof(tusb_desc_interface_assoc_t))
- return 0;
- break;
- }
- if (cursor[1] == TUSB_DESC_ENDPOINT) {
- if (cursor[0] < sizeof(tusb_desc_endpoint_t))
- return 0;
- const tusb_desc_endpoint_t *endpoint =
- reinterpret_cast<const tusb_desc_endpoint_t *>(
- cursor);
- if (!xboxOgEndpointValid(endpoint) ||
- ++endpoint_count > interface->bNumEndpoints)
- return 0;
- if (tu_edpt_dir(endpoint->bEndpointAddress) ==
- TUSB_DIR_IN) {
- if (endpoint_in)
- return 0;
- endpoint_in = endpoint;
- } else {
- if (endpoint_out)
- return 0;
- endpoint_out = endpoint;
- }
- }
- cursor += cursor[0];
- }
- if (endpoint_count != 2 || !endpoint_in || !endpoint_out)
- return 0;
- if (!usbd_edpt_open(rhport, endpoint_in))
- return 0;
- if (!usbd_edpt_open(rhport, endpoint_out)) {
- usbd_edpt_close(rhport, endpoint_in->bEndpointAddress);
- return 0;
- }
- xboxOgClearSlot();
- g_xboxOgSlot.interface_number = interface->bInterfaceNumber;
- g_xboxOgSlot.endpoint_in = endpoint_in->bEndpointAddress;
- g_xboxOgSlot.endpoint_out = endpoint_out->bEndpointAddress;
- g_xboxOgSlot.in_use = true;
- if (!usbd_edpt_xfer(rhport, g_xboxOgSlot.endpoint_out,
- g_xboxOgSlot.output_buffer,
- sizeof g_xboxOgSlot.output_buffer)) {
- usbd_edpt_close(rhport, g_xboxOgSlot.endpoint_in);
- usbd_edpt_close(rhport, g_xboxOgSlot.endpoint_out);
- xboxOgClearSlot();
- return 0;
- }
- return (uint16_t)(cursor - start);
- }
- static bool xboxOgXfer(uint8_t rhport, uint8_t endpoint, xfer_result_t result,
- uint32_t transferred)
- {
- if (!g_xboxOgSlot.in_use)
- return false;
- if (endpoint == g_xboxOgSlot.endpoint_in)
- return true;
- if (endpoint != g_xboxOgSlot.endpoint_out)
- return false;
- if (result == XFER_RESULT_SUCCESS)
- xboxOgApplyRumble(g_xboxOgSlot, g_xboxOgSlot.output_buffer,
- (uint16_t)transferred);
- if (!g_xboxOgSlot.in_use)
- return true;
- if (!usbd_edpt_xfer(rhport, g_xboxOgSlot.endpoint_out,
- g_xboxOgSlot.output_buffer,
- sizeof g_xboxOgSlot.output_buffer)) {
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- if (g_xboxOgSlot.in_use &&
- endpoint == g_xboxOgSlot.endpoint_out)
- g_xboxOgSlot.output_rearm_pending = true;
- __set_PRIMASK(primask);
- }
- return true;
- }
- static const usbd_class_driver_t g_xboxOgDriver = {
- #if CFG_TUSB_DEBUG >= 2
- .name = "XID",
- #endif
- .init = xboxOgInit,
- .deinit = xboxOgDeinit,
- .reset = xboxOgReset,
- .open = xboxOgOpen,
- .control_xfer_cb = xboxOgClassControlXfer,
- .xfer_cb = xboxOgXfer,
- .sof = nullptr,
- };
- const usbd_class_driver_t *xboxOgClassDriver(void)
- {
- return &g_xboxOgDriver;
- }
- class Adafruit_USBD_XboxOg : public Adafruit_USBD_Interface {
- public:
- uint16_t getInterfaceDescriptor(uint8_t, uint8_t *buffer,
- uint16_t buffer_size) override
- {
- if (!buffer)
- return XBOX_OG_DESC_LEN;
- if (buffer_size < XBOX_OG_DESC_LEN)
- return 0;
- uint8_t interface_number = TinyUSBDevice.allocInterface(1);
- uint8_t endpoint_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN);
- uint8_t endpoint_out =
- TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT);
- const uint8_t descriptor[XBOX_OG_DESC_LEN] = {
- 9,
- TUSB_DESC_INTERFACE,
- interface_number,
- 0,
- 2,
- 0x58,
- 0x42,
- 0x00,
- 0,
- 7,
- TUSB_DESC_ENDPOINT,
- endpoint_in,
- TUSB_XFER_INTERRUPT,
- U16_TO_U8S_LE(XBOX_OG_PACKET_SIZE),
- 4,
- 7,
- TUSB_DESC_ENDPOINT,
- endpoint_out,
- TUSB_XFER_INTERRUPT,
- U16_TO_U8S_LE(XBOX_OG_PACKET_SIZE),
- 4,
- };
- memcpy(buffer, descriptor, sizeof descriptor);
- return sizeof descriptor;
- }
- bool begin()
- {
- return TinyUSBDevice.addInterface(*this);
- }
- };
- static Adafruit_USBD_XboxOg g_xboxOgInterface;
- XboxOgController g_xboxOgCtl;
- enum XboxOgRemapTarget : uint8_t {
- XBOX_OG_TARGET_NONE,
- XBOX_OG_TARGET_A,
- XBOX_OG_TARGET_B,
- XBOX_OG_TARGET_X,
- XBOX_OG_TARGET_Y,
- XBOX_OG_TARGET_WHITE,
- XBOX_OG_TARGET_BLACK,
- XBOX_OG_TARGET_L3,
- XBOX_OG_TARGET_R3,
- XBOX_OG_TARGET_BACK,
- XBOX_OG_TARGET_START,
- XBOX_OG_TARGET_DUP,
- XBOX_OG_TARGET_DDOWN,
- XBOX_OG_TARGET_DLEFT,
- XBOX_OG_TARGET_DRIGHT,
- };
- // Remaps target Controller S outputs independently of the RF button masks.
- // Codes come from the shared remap code space: 5/6 (LB/RB) drive White/Black,
- // 9/10 are semantic Back/Start, and 11 (Guide) has no Controller S target.
- static constexpr XboxOgRemapTarget XBOX_OG_REMAP[] = {
- XBOX_OG_TARGET_NONE, XBOX_OG_TARGET_A, XBOX_OG_TARGET_B,
- XBOX_OG_TARGET_X, XBOX_OG_TARGET_Y, XBOX_OG_TARGET_WHITE,
- XBOX_OG_TARGET_BLACK, XBOX_OG_TARGET_L3, XBOX_OG_TARGET_R3,
- XBOX_OG_TARGET_BACK, XBOX_OG_TARGET_START, XBOX_OG_TARGET_NONE,
- XBOX_OG_TARGET_DUP, XBOX_OG_TARGET_DDOWN, XBOX_OG_TARGET_DLEFT,
- XBOX_OG_TARGET_DRIGHT,
- };
- static_assert(sizeof XBOX_OG_REMAP / sizeof XBOX_OG_REMAP[0] == 16,
- "Xbox remap table must cover codes 0 through 15");
- static void xboxOgApplyRemap(XboxOgInputReport &report, uint8_t code)
- {
- uint32_t mapped = tritonFromCode(code);
- // LT/RT are codes 19/20, outside the table -- these two must stay above
- // the bounds check or a paddle mapped to a trigger does nothing.
- if (mapped & TB_L2)
- report.left_trigger = 0xFF;
- if (mapped & TB_R2)
- report.right_trigger = 0xFF;
- if (code >= sizeof XBOX_OG_REMAP / sizeof XBOX_OG_REMAP[0])
- return;
- switch (XBOX_OG_REMAP[code]) {
- case XBOX_OG_TARGET_A:
- report.a = 0xFF;
- break;
- case XBOX_OG_TARGET_B:
- report.b = 0xFF;
- break;
- case XBOX_OG_TARGET_X:
- report.x = 0xFF;
- break;
- case XBOX_OG_TARGET_Y:
- report.y = 0xFF;
- break;
- case XBOX_OG_TARGET_WHITE:
- report.white = 0xFF;
- break;
- case XBOX_OG_TARGET_BLACK:
- report.black = 0xFF;
- break;
- case XBOX_OG_TARGET_L3:
- report.buttons |= XBOX_OG_L3;
- break;
- case XBOX_OG_TARGET_R3:
- report.buttons |= XBOX_OG_R3;
- break;
- case XBOX_OG_TARGET_BACK:
- report.buttons |= XBOX_OG_BACK;
- break;
- case XBOX_OG_TARGET_START:
- report.buttons |= XBOX_OG_START;
- break;
- case XBOX_OG_TARGET_DUP:
- report.buttons |= XBOX_OG_DUP;
- break;
- case XBOX_OG_TARGET_DDOWN:
- report.buttons |= XBOX_OG_DDOWN;
- break;
- case XBOX_OG_TARGET_DLEFT:
- report.buttons |= XBOX_OG_DLEFT;
- break;
- case XBOX_OG_TARGET_DRIGHT:
- report.buttons |= XBOX_OG_DRIGHT;
- break;
- case XBOX_OG_TARGET_NONE:
- default:
- break;
- }
- }
- static void xboxOgBuildReport(XboxOgInputReport &report, const uint8_t *raw)
- {
- uint32_t buttons = btnsOf(raw);
- xboxOgNeutralReport(report);
- if (buttons & TB_DUP)
- report.buttons |= XBOX_OG_DUP;
- if (buttons & TB_DDN)
- report.buttons |= XBOX_OG_DDOWN;
- if (buttons & TB_DLF)
- report.buttons |= XBOX_OG_DLEFT;
- if (buttons & TB_DRT)
- report.buttons |= XBOX_OG_DRIGHT;
- // The bit names are the reverse of the physical labels: the left
- // button (View) arrives as TB_MENU and the right one as TB_VIEW.
- // Back is the left button on an Original Xbox, so this is positional.
- if (buttons & TB_VIEW)
- report.buttons |= XBOX_OG_START;
- if (buttons & TB_MENU)
- report.buttons |= XBOX_OG_BACK;
- if (buttons & TB_L3)
- report.buttons |= XBOX_OG_L3;
- if (buttons & TB_R3)
- report.buttons |= XBOX_OG_R3;
- // A/B/X/Y and White/Black are analog pressure bytes, so a digital press
- // writes full scale; the shoulders have no button bit at all.
- if (buttons & TB_A)
- (g_abSwap ? report.b : report.a) = 0xFF;
- if (buttons & TB_B)
- (g_abSwap ? report.a : report.b) = 0xFF;
- if (buttons & TB_X)
- (g_abSwap ? report.y : report.x) = 0xFF;
- if (buttons & TB_Y)
- (g_abSwap ? report.x : report.y) = 0xFF;
- if (buttons & TB_LB)
- report.white = 0xFF;
- if (buttons & TB_RB)
- report.black = 0xFF;
- report.left_trigger = trigU8(u16off(raw, 4));
- report.right_trigger = trigU8(u16off(raw, 6));
- if (buttons & TB_L2)
- report.left_trigger = 0xFF;
- if (buttons & TB_R2)
- report.right_trigger = 0xFF;
- const uint32_t paddle_buttons[] = { TB_L4, TB_R4, TB_L5, TB_R5 };
- for (int i = 0; i < 4; i++)
- if (buttons & paddle_buttons[i])
- xboxOgApplyRemap(report, g_back[i]);
- if ((buttons & TB_QAM) && g_qamMap)
- xboxOgApplyRemap(report, g_qamMap);
- // Raw-report offsets, not slotSticks(): like mode_xinput this mode decodes 0x45 in place and never
- // touches g_in. 16/18 = left pad X/Y, 22/24 = right pad X/Y.
- int16_t lx = (int16_t)s16off(raw, 8), ly = (int16_t)s16off(raw, 10),
- rx = (int16_t)s16off(raw, 12), ry = (int16_t)s16off(raw, 14);
- padStickBlend(buttons, (int16_t)s16off(raw, 16),
- (int16_t)s16off(raw, 18), (int16_t)s16off(raw, 22),
- (int16_t)s16off(raw, 24), &lx, &ly, &rx, &ry);
- le16(reinterpret_cast<uint8_t *>(&report.left_x), lx);
- le16(reinterpret_cast<uint8_t *>(&report.left_y), ly);
- le16(reinterpret_cast<uint8_t *>(&report.right_x), rx);
- le16(reinterpret_cast<uint8_t *>(&report.right_y), ry);
- }
- static bool xboxOgBondRecent(int slot, unsigned long now)
- {
- return slot >= 0 && slot < NSLOT && g_slot[slot].used &&
- g_connReplyMs[slot] != 0 &&
- (unsigned long)(now - g_connReplyMs[slot]) < XBOX_OG_CONN_MS;
- }
- static void xboxOgUpdateBond(void)
- {
- XboxOgSlot &state = g_xboxOgSlot;
- unsigned long now = millis();
- int current = state.bond_slot;
- if (xboxOgBondRecent(current, now))
- return;
- if (current >= 0) {
- XboxOgInputReport neutral;
- state.bond_slot = -1;
- xboxOgNeutralReport(neutral);
- xboxOgPublishReport(neutral);
- g_xboxOgNeutralMs = now;
- }
- for (int slot = 0; slot < NSLOT; slot++)
- if (xboxOgBondRecent(slot, now)) {
- state.bond_slot = (int8_t)slot;
- break;
- }
- }
- // Fire-and-forget: no stop is tracked or sent here. The shared relay in
- // haptics.cpp (burst, reconnect scrub, per-slot watchdog) stops the motor.
- static void xboxOgHandleRumble(void)
- {
- XboxOgSlot &state = g_xboxOgSlot;
- uint16_t low, high;
- bool pending;
- int8_t target;
- uint32_t primask = __get_PRIMASK();
- __disable_irq();
- pending = state.rumble_pending;
- low = state.rumble_low;
- high = state.rumble_high;
- target = state.bond_slot;
- state.rumble_pending = false;
- __set_PRIMASK(primask);
- if (!pending || target < 0 || !g_rumble)
- return;
- hapticSteamRumble(low, high, (uint8_t)target);
- }
- void XboxOgController::begin()
- {
- xboxOgClearSlot();
- g_xboxOgNeutralMs = 0;
- usbIdentity();
- USBDevice.setConfigurationMaxPower(100);
- g_xboxOgInterface.begin();
- usbTxRegisterDrain(xboxOgTxDrain);
- }
- void XboxOgController::onReport45(int slot, const uint8_t *rep, bool fresh,
- uint8_t bodyTlen)
- {
- (void)fresh;
- if (!rep || slot < 0 || slot >= NSLOT || bodyTlen < 18 ||
- (rep[0] != 0x45 && rep[0] != 0x42) ||
- g_xboxOgSlot.bond_slot != slot)
- return;
- uint32_t generation = g_xboxOgGeneration;
- XboxOgInputReport report;
- xboxOgBuildReport(report, rep);
- xboxOgPublishBondReport(report, slot, generation);
- }
- void XboxOgController::task()
- {
- xboxOgUpdateBond();
- xboxOgHandleRumble();
- if (g_xboxOgSlot.bond_slot >= 0 ||
- (unsigned long)(millis() - g_xboxOgNeutralMs) < USB_STREAM_MS)
- return;
- XboxOgInputReport neutral;
- g_xboxOgNeutralMs = millis();
- xboxOgNeutralReport(neutral);
- xboxOgPublishReport(neutral);
- }
- void XboxOgController::usbIdentity()
- {
- USBDevice.setID(0x045E, 0x0289);
- // A real Controller S is a full-speed USB 1.1 device (bcdUSB 1.10,
- // bcdDevice 1.21). Set it explicitly.
- USBDevice.setVersion(0x0110);
- USBDevice.setDeviceVersion(0x0121);
- // A real Controller S sends no string descriptors (iManufacturer /
- // iProduct / iSerial = 0); hosts name it from usb.ids. TinyUSB pins
- // those indices non-zero, so mirror what usb.ids shows for 045E:0289.
- USBDevice.setManufacturerDescriptor("Microsoft Corp.");
- USBDevice.setProductDescriptor("Xbox Controller S");
- }
|