From e104bc6f9b6a7b8073145e7a9e45218da4415d46 Mon Sep 17 00:00:00 2001 From: fancymelonhub <30706150+fancythedeveloper@users.noreply.github.com> Date: Mon, 30 Dec 2019 14:45:20 -0500 Subject: [PATCH] Porting SNES9X RX 360 Controller support to GX (#887) Title is self-explanatory. --- source/input.cpp | 10 ++ source/input.h | 1 + source/menu.cpp | 6 +- source/utils/retrode.c | 126 +++++++++++----- source/utils/xbox360.c | 316 +++++++++++++++++++++++++++++++++++++++++ source/utils/xbox360.h | 18 +++ 6 files changed, 438 insertions(+), 39 deletions(-) create mode 100644 source/utils/xbox360.c create mode 100644 source/utils/xbox360.h diff --git a/source/input.cpp b/source/input.cpp index 1456c6c..2e4316e 100644 --- a/source/input.cpp +++ b/source/input.cpp @@ -37,6 +37,7 @@ #ifdef HW_RVL #include "utils/retrode.h" +#include "utils/xbox360.h" #endif #define ANALOG_SENSITIVITY 30 @@ -335,6 +336,7 @@ UpdatePads() #ifdef HW_RVL WiiDRC_ScanPads(); Retrode_ScanPads(); + XBOX360_ScanPads(); WPAD_ScanPads(); #endif @@ -536,6 +538,7 @@ static void decodepad (int chan) u32 wiidrcp = userInput[chan].wiidrcdata.btns_h; jp |= Retrode_ButtonsHeld(chan); + jp |= XBOX360_ButtonsHeld(chan); #endif /*** @@ -896,3 +899,10 @@ void SetDefaultButtonMap () SetControllers(); } + +char* GetUSBControllerInfo() +{ + static char info[50]; + snprintf(info, 50, "Retrode: %s, XBOX360: %s", Retrode_Status(), XBOX360_Status()); + return info; +} diff --git a/source/input.h b/source/input.h index 4ebe25d..095ddb3 100644 --- a/source/input.h +++ b/source/input.h @@ -35,5 +35,6 @@ void SetDefaultButtonMap (); bool MenuRequested(); void SetupPads(); void UpdatePads(); +char* GetUSBControllerInfo(); #endif diff --git a/source/menu.cpp b/source/menu.cpp index 0c1fe40..2df4233 100644 --- a/source/menu.cpp +++ b/source/menu.cpp @@ -868,10 +868,12 @@ static void WindowCredits(void * ptr) txt[i] = new GuiText("under the terms of the GNU General Public License (GPL) Version 2."); txt[i]->SetPosition(0,y); i++; y+=20; - char iosVersion[20]; + //char iosVersion[20]; + char iosVersion[70]; // added 50 for Retrode info #ifdef HW_RVL - sprintf(iosVersion, "IOS: %ld", IOS_GetVersion()); + //sprintf(iosVersion, "IOS: %ld", IOS_GetVersion()); + snprintf(iosVersion, 70, "IOS: %ld / %s", IOS_GetVersion(), GetUSBControllerInfo()); #endif txt[i] = new GuiText(iosVersion, 18, (GXColor){0, 0, 0, 255}); diff --git a/source/utils/retrode.c b/source/utils/retrode.c index d9ae6bf..0966a72 100644 --- a/source/utils/retrode.c +++ b/source/utils/retrode.c @@ -1,10 +1,11 @@ #ifdef HW_RVL #include -static bool retrodeSetup = false; -static s32 deviceIdRetrode = 0; -static u8 endpointRetrode = 0; -static u8 bMaxPacketSizeRetrode = 0; +static bool setup = false; +static bool replugRequired = false; +static s32 deviceId = 0; +static u8 endpoint = 0; +static u8 bMaxPacketSize = 0; static u32 jpRetrode[4]; @@ -29,8 +30,23 @@ static u8 getEndpoint(usb_devdesc devdesc) return devdesc.configurations->interfaces->endpoints->bEndpointAddress; } -static void openRetrode() +static int removal_cb(int result, void *usrdata) { + s32 fd = (s32) usrdata; + if (fd == deviceId) + { + deviceId = 0; + } + return 1; +} + +static void open() +{ + if (deviceId != 0) + { + return; + } + usb_device_entry dev_entry[8]; u8 dev_count; if (USB_GetDeviceList(dev_entry, 8, USB_CLASS_HID, &dev_count) < 0) @@ -39,7 +55,8 @@ static void openRetrode() } // Retrode has two entries in USB_GetDeviceList(), one for gamepads and one for SNES mouse - for (int i = 0; i < dev_count; ++i) + int i; + for (i = 0; i < dev_count; ++i) { s32 fd; if (USB_OpenDevice(dev_entry[i].device_id, dev_entry[i].vid, dev_entry[i].pid, &fd) < 0) @@ -48,33 +65,43 @@ static void openRetrode() } usb_devdesc devdesc; - if (USB_GetDescriptors(fd, &devdesc) < 0 || !isRetrodeGamepad(devdesc)) + if (USB_GetDescriptors(fd, &devdesc) < 0) { + // You have to replug the Retrode controller! + replugRequired = true; USB_CloseDevice(&fd); - continue; + break; + } + + if (isRetrodeGamepad(devdesc)) + { + deviceId = fd; + replugRequired = false; + endpoint = getEndpoint(devdesc); + bMaxPacketSize = devdesc.bMaxPacketSize0; + USB_DeviceRemovalNotifyAsync(fd, &removal_cb, (void*) fd); + break; + } + else + { + USB_CloseDevice(&fd); } - deviceIdRetrode = fd; - endpointRetrode = getEndpoint(devdesc); - bMaxPacketSizeRetrode = devdesc.bMaxPacketSize0; } + + setup = true; } void Retrode_ScanPads() { - if(!retrodeSetup) - { - retrodeSetup = true; - openRetrode(); - } - - if (deviceIdRetrode == 0) + if (deviceId == 0) { return; } - uint8_t ATTRIBUTE_ALIGN(32) buf[bMaxPacketSizeRetrode]; + uint8_t ATTRIBUTE_ALIGN(32) buf[bMaxPacketSize]; - if (USB_ReadIntrMsg(deviceIdRetrode, endpointRetrode, sizeof(buf), buf) != 5) + // Retrode gamepad endpoint returns 5 bytes with gamepad events + if (USB_ReadIntrMsg(deviceId, endpoint, sizeof(buf), buf) != 5) { return; } @@ -87,36 +114,61 @@ void Retrode_ScanPads() // 3 = left Genesis/MD // 4 = right Genesis/MD - // Retrode gamepad endpoint returns 5 bytes with gamepad events - u32 jp12 = 0; - jp12 |= ((buf[2] & 0x9C) == 0x9C) ? PAD_BUTTON_UP : 0; - jp12 |= ((buf[2] & 0x64) == 0x64) ? PAD_BUTTON_DOWN : 0; - jp12 |= ((buf[1] & 0x9C) == 0x9C) ? PAD_BUTTON_LEFT : 0; - jp12 |= ((buf[1] & 0x64) == 0x64) ? PAD_BUTTON_RIGHT : 0; + // Button layout + // A=3,10 + // B=3,01 + // X=3,20 + // Y=3,02 + // L=3,40 + // R=3,80 + // Up=2,9C + // Down=2,64 + // Left=1,9C + // Right=1,64 + // Start=3,08 + // Select=3,04 - jp12 |= (buf[3] & 0x10) ? PAD_BUTTON_A : 0; - jp12 |= (buf[3] & 0x01) ? PAD_BUTTON_B : 0; - jp12 |= (buf[3] & 0x20) ? PAD_BUTTON_X : 0; - jp12 |= (buf[3] & 0x02) ? PAD_BUTTON_Y : 0; + u32 jp = 0; + jp |= ((buf[2] & 0x9C) == 0x9C) ? PAD_BUTTON_UP : 0; + jp |= ((buf[2] & 0x64) == 0x64) ? PAD_BUTTON_DOWN : 0; + jp |= ((buf[1] & 0x9C) == 0x9C) ? PAD_BUTTON_LEFT : 0; + jp |= ((buf[1] & 0x64) == 0x64) ? PAD_BUTTON_RIGHT : 0; - jp12 |= (buf[3] & 0x40) ? PAD_TRIGGER_L : 0; - jp12 |= (buf[3] & 0x80) ? PAD_TRIGGER_R : 0; + jp |= (buf[3] & 0x10) ? PAD_BUTTON_A : 0; + jp |= (buf[3] & 0x01) ? PAD_BUTTON_B : 0; + jp |= (buf[3] & 0x20) ? PAD_BUTTON_X : 0; + jp |= (buf[3] & 0x02) ? PAD_BUTTON_Y : 0; - jp12 |= (buf[3] & 0x08) ? PAD_BUTTON_START : 0; - jp12 |= (buf[3] & 0x04) ? PAD_TRIGGER_Z : 0; // SNES select button maps to Z + jp |= (buf[3] & 0x40) ? PAD_TRIGGER_L : 0; + jp |= (buf[3] & 0x80) ? PAD_TRIGGER_R : 0; + + jp |= (buf[3] & 0x08) ? PAD_BUTTON_START : 0; + jp |= (buf[3] & 0x04) ? PAD_TRIGGER_Z : 0; // SNES select button maps to Z // Required, otherwise if the returned port isn't the one we are looking for, jp will be set to zero, // and held buttons are not possible w/o saving the state. - jpRetrode[buf[0] - 1] = jp12; + jpRetrode[buf[0] - 1] = jp; } u32 Retrode_ButtonsHeld(int chan) { - if (deviceIdRetrode == 0) + if(!setup) + { + open(); + } + if (deviceId == 0) { return 0; } return jpRetrode[chan]; } -#endif +char* Retrode_Status() +{ + open(); + if (replugRequired) + return "please replug"; + return deviceId ? "connected" : "not found"; +} + +#endif \ No newline at end of file diff --git a/source/utils/xbox360.c b/source/utils/xbox360.c new file mode 100644 index 0000000..6ff3416 --- /dev/null +++ b/source/utils/xbox360.c @@ -0,0 +1,316 @@ +#ifdef HW_RVL +#include +#include + +#define USB_CLASS_XBOX360 0xFF + +static bool setup = false; +static bool replugRequired = false; +static s32 deviceId = 0; +static u8 endpoint_in = 0x81; +static u8 endpoint_out = 0x01; // some controllers require 0x02 for LED & rumble (updated below) +static u8 bMaxPacketSize = 20; +static u8 bConfigurationValue = 1; +static u8 ATTRIBUTE_ALIGN(32) buf[20]; +static bool isReading = false; +static u32 jp = 0; +static u8 player = 0; +static u32 xboxButtonCount = 0; +static bool nextPlayer = false; + +static u8 getEndpoint(usb_devdesc devdesc) +{ + if (devdesc.configurations == NULL || devdesc.configurations->interfaces == NULL || + devdesc.configurations->interfaces->endpoints == NULL) + { + return -1; + } + return devdesc.configurations->interfaces->endpoints->bEndpointAddress; +} + +static bool isXBOX360(usb_devdesc devdesc) +{ + return (devdesc.idVendor == 0x045e && devdesc.idProduct == 0x028e && getEndpoint(devdesc) == endpoint_in); +} + +static int read(s32 device_id, u8 endpoint, u8 bMaxPacketSize0); + +static void start_reading(s32 device_id, u8 endpoint, u8 bMaxPacketSize0) +{ + if (isReading) + { + // already reading + return; + } + isReading = true; + read(deviceId, endpoint_in, bMaxPacketSize0); +} + +static void stop_reading() +{ + isReading = false; +} + +static int read_cb(int res, void *usrdata) +{ + if (!isReading) + { + // stop reading + return 1; + } + + // NOTE: The four startup messages have res = 3 and can be ignored + + // Button layout + // A=3,10 + // B=3,20 + // X=3,40 + // Y=3,80 + // Up=2,01 + // Right=2,08 + // Left=2,04 + // Down=2,02 + // L=3,01 + // R=3,02 + // L2=4,FF ; analog trigger + // R3=5,FF ; analog trigger + // L3=2,40 ; left hat button + // L3=2,80 ; right hat button + // XBOX=3,04 + // Start=2,10 + // Back=2,20 + // LStickX=6/7,FF ; , + // LStickY=8/9,FF + // RStickX=10/11,FF + // RStickY=12/13,FF + + if (res == 20) + { + jp = 0; + jp |= ((buf[2] & 0x01) == 0x01) ? PAD_BUTTON_UP : 0; + jp |= ((buf[2] & 0x02) == 0x02) ? PAD_BUTTON_DOWN : 0; + jp |= ((buf[2] & 0x04) == 0x04) ? PAD_BUTTON_LEFT : 0; + jp |= ((buf[2] & 0x08) == 0x08) ? PAD_BUTTON_RIGHT : 0; + + jp |= ((buf[3] & 0x10) == 0x10) ? PAD_BUTTON_B : 0; // XBOX360 A button maps to B + jp |= ((buf[3] & 0x20) == 0x20) ? PAD_BUTTON_A : 0; // XBOX360 B button maps to A + jp |= ((buf[3] & 0x40) == 0x40) ? PAD_BUTTON_Y : 0; // XBOX360 X button maps to Y + jp |= ((buf[3] & 0x80) == 0x80) ? PAD_BUTTON_X : 0; // XBOX360 Y button maps to X + + jp |= ((buf[3] & 0x01) == 0x01) ? PAD_TRIGGER_L : 0; + jp |= ((buf[3] & 0x02) == 0x02) ? PAD_TRIGGER_R : 0; + + jp |= ((buf[2] & 0x10) == 0x10) ? PAD_BUTTON_START : 0; + jp |= ((buf[2] & 0x20) == 0x20) ? PAD_TRIGGER_Z : 0; // XBOX360 back button maps to Z + + // triggers + jp |= (buf[4] > 128) ? PAD_TRIGGER_L : 0; + jp |= (buf[5] > 128) ? PAD_TRIGGER_R : 0; + + // left stick + int16_t lx = (buf[7] << 8) | buf[6]; // [-32768, 32767] + int16_t ly = (buf[9] << 8) | buf[8]; // [-32768, 32767] + jp |= (ly > 16384) ? PAD_BUTTON_UP : 0; + jp |= (ly < -16384) ? PAD_BUTTON_DOWN : 0; + jp |= (lx < -16384) ? PAD_BUTTON_LEFT : 0; + jp |= (lx > 16384) ? PAD_BUTTON_RIGHT : 0; + + // right stick + int16_t rx = (buf[11] << 8) | buf[10]; // [-32768, 32767] + int16_t ry = (buf[13] << 8) | buf[12]; // [-32768, 32767] + jp |= (ry > 16384) ? PAD_BUTTON_X : 0; // XBOX360 Y button maps to X + jp |= (ry < -16384) ? PAD_BUTTON_B : 0; // XBOX360 A button maps to B + jp |= (rx < -16384) ? PAD_BUTTON_Y : 0; // XBOX360 X button maps to Y + jp |= (rx > 16384) ? PAD_BUTTON_A : 0; // XBOX360 B button maps to A + + // XBOX button to switch to next player + if ((buf[3] & 0x04) == 0x04) + { + xboxButtonCount++; + // count = 2 means you have to push the button 1x to switch players + // count = 10 means you have to push the button 5x to switch players + if (xboxButtonCount >= 2) + { + nextPlayer = true; + xboxButtonCount = 0; + } + } + } + + // read again + read(deviceId, endpoint_in, bMaxPacketSize); + + return 1; +} + +// never call directly +static int read(s32 device_id, u8 endpoint, u8 bMaxPacketSize0) +{ + // need to use async, because USB_ReadIntrMsg() blocks until a button is pressed + return USB_ReadIntrMsgAsync(device_id, endpoint, sizeof(buf), buf, &read_cb, NULL); +} + +static void turnOnLED() +{ + uint8_t ATTRIBUTE_ALIGN(32) buf[] = { 0x01, 0x03, 0x06 + player }; + USB_WriteIntrMsg(deviceId, endpoint_out, sizeof(buf), buf); +} + +static void increasePlayer() +{ + player++; + if (player > 3) + { + player = 0; + } + turnOnLED(); +} + +void rumble(s32 device_id, u8 left, u8 right) +{ + uint8_t ATTRIBUTE_ALIGN(32) buf[] = { 0x00, 0x08, 0x00, left, right, 0x00, 0x00, 0x00 }; + USB_WriteIntrMsg(deviceId, endpoint_out, sizeof(buf), buf); +} + +static int removal_cb(int result, void *usrdata) +{ + s32 fd = (s32) usrdata; + if (fd == deviceId) + { + stop_reading(); + deviceId = 0; + } + return 1; +} + +// adapted from RetroArch input/drivers_hid/wiiusb_hid.c#wiiusb_get_description() +void wiiusb_get_description(usb_device_entry *device, usb_devdesc *devdesc) +{ + unsigned char c; + unsigned i, k; + + for (c = 0; c < devdesc->bNumConfigurations; c++) + { + const usb_configurationdesc *config = &devdesc->configurations[c]; + + for (i = 0; i < (int)config->bNumInterfaces; i++) + { + const usb_interfacedesc *inter = &config->interfaces[i]; + + for (k = 0; k < (int)inter->bNumEndpoints; k++) + { + const usb_endpointdesc *epdesc = &inter->endpoints[k]; + bool is_int = (epdesc->bmAttributes & 0x03) == USB_ENDPOINT_INTERRUPT; + bool is_out = (epdesc->bEndpointAddress & 0x80) == USB_ENDPOINT_OUT; + bool is_in = (epdesc->bEndpointAddress & 0x80) == USB_ENDPOINT_IN; + + if (is_int) + { + if (is_in) + { + //endpoint_in = epdesc->bEndpointAddress; + //endpoint_in_max_size = epdesc->wMaxPacketSize; + } + if (is_out) + { + endpoint_out = epdesc->bEndpointAddress; + //endpoint_out_max_size = epdesc->wMaxPacketSize; + } + } + } + break; + } + } +} + +static void open() +{ + if (deviceId != 0) + { + return; + } + + usb_device_entry dev_entry[8]; + u8 dev_count; + if (USB_GetDeviceList(dev_entry, 8, USB_CLASS_XBOX360, &dev_count) < 0) + { + return; + } + + int i; + for (i = 0; i < dev_count; ++i) + { + s32 fd; + if (USB_OpenDevice(dev_entry[i].device_id, dev_entry[i].vid, dev_entry[i].pid, &fd) < 0) + { + continue; + } + + usb_devdesc devdesc; + if (USB_GetDescriptors(fd, &devdesc) < 0) + { + // You have to replug the XBOX360 controller! + replugRequired = true; + USB_CloseDevice(&fd); + break; + } + + if (isXBOX360(devdesc) && USB_SetConfiguration(fd, bConfigurationValue) >= 0) + { + deviceId = fd; + replugRequired = false; + wiiusb_get_description(&dev_entry[i], &devdesc); + turnOnLED(); + USB_DeviceRemovalNotifyAsync(fd, &removal_cb, (void*) fd); + break; + } + else + { + USB_CloseDevice(&fd); + } + } + + setup = true; +} + +void XBOX360_ScanPads() +{ + if (deviceId == 0) + { + return; + } + + start_reading(deviceId, endpoint_in, bMaxPacketSize); +} + +u32 XBOX360_ButtonsHeld(int chan) +{ + if(!setup) + { + open(); + } + if (deviceId == 0) + { + return 0; + } + if (nextPlayer) + { + nextPlayer = false; + increasePlayer(); + } + if (chan != player) + { + return 0; + } + return jp; +} + +char* XBOX360_Status() +{ + open(); + if (replugRequired) + return "please replug"; + return deviceId ? "connected" : "not found"; +} + +#endif diff --git a/source/utils/xbox360.h b/source/utils/xbox360.h new file mode 100644 index 0000000..fe5f19f --- /dev/null +++ b/source/utils/xbox360.h @@ -0,0 +1,18 @@ +#ifndef _XBOX360_H_ +#define _XBOX360_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool XBOX360_ScanPads(); +u32 XBOX360_ButtonsHeld(int chan); +char* XBOX360_Status(); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file