diff --git a/Firmware/RP2040/CMakeLists.txt b/Firmware/RP2040/CMakeLists.txt index 291dbf50..36d45e1a 100644 --- a/Firmware/RP2040/CMakeLists.txt +++ b/Firmware/RP2040/CMakeLists.txt @@ -63,6 +63,7 @@ set(SOURCES_BOARD ${SRC}/USBDevice/DeviceDriver/PSClassic/PSClassic.cpp ${SRC}/USBDevice/DeviceDriver/PS3/PS3.cpp ${SRC}/USBDevice/DeviceDriver/Switch/Switch.cpp + ${SRC}/USBDevice/DeviceDriver/WiiU/WiiU.cpp ${SRC}/USBDevice/DeviceDriver/XInput/XInput.cpp ${SRC}/USBDevice/DeviceDriver/XboxOG/XboxOG_GP.cpp ${SRC}/USBDevice/DeviceDriver/XboxOG/XboxOG_SB.cpp @@ -325,6 +326,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "UART port: ${UART_PORT}, TX: ${TX_PIN}, RX: ${RX_PIN}") + pico_enable_stdio_usb(${FW_NAME} 0) pico_enable_stdio_uart(${FW_NAME} 1) target_compile_definitions(${FW_NAME} PRIVATE PICO_DEFAULT_UART=${UART_PORT} diff --git a/Firmware/RP2040/src/Bluepad32/Bluepad32.cpp b/Firmware/RP2040/src/Bluepad32/Bluepad32.cpp index 836811e5..02cdc520 100644 --- a/Firmware/RP2040/src/Bluepad32/Bluepad32.cpp +++ b/Firmware/RP2040/src/Bluepad32/Bluepad32.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -46,6 +47,27 @@ bool any_connected() return false; } +bool is_wii_controller_connected(uint8_t idx) +{ + if (idx >= MAX_GAMEPADS) { + return false; + } + uni_hid_device_t* bp_device = uni_hid_device_get_instance_for_idx(idx); + return (bp_device != nullptr && bp_device->controller_type == CONTROLLER_TYPE_WiiController); +} + +bool any_wii_controller_connected() +{ + for (uint8_t i = 0; i < MAX_GAMEPADS; ++i) + { + if (bt_devices_[i].connected && is_wii_controller_connected(i)) + { + return true; + } + } + return false; +} + //This solves a function pointer/crash issue with bluepad32 void set_rumble(uni_hid_device_t* bp_device, uint16_t length, uint8_t rumble_l, uint8_t rumble_r) { @@ -132,9 +154,14 @@ static void init_complete_cb(void) { } static uni_error_t device_discovered_cb(bd_addr_t addr, const char* name, uint16_t cod, uint8_t rssi) { - if (!((cod & UNI_BT_COD_MINOR_MASK) & UNI_BT_COD_MINOR_GAMEPAD)) { + uint8_t minor = cod & UNI_BT_COD_MINOR_MASK; + + if (!(minor & (UNI_BT_COD_MINOR_GAMEPAD | + UNI_BT_COD_MINOR_JOYSTICK | + UNI_BT_COD_MINOR_REMOTE_CONTROL))) { return UNI_ERROR_IGNORE_DEVICE; } + return UNI_ERROR_SUCCESS; } @@ -190,6 +217,11 @@ static void oob_event_cb(uni_platform_oob_event_t event, void* data) { return; } +// Set to 1 to print all Bluepad32 controller inputs to UART (only when state changes) +#ifndef BLUEPAD32_UART_LOG_INPUT +#define BLUEPAD32_UART_LOG_INPUT 1 +#endif + static void controller_data_cb(uni_hid_device_t* device, uni_controller_t* controller) { static uni_gamepad_t prev_uni_gp[MAX_GAMEPADS] = {}; @@ -199,6 +231,19 @@ static void controller_data_cb(uni_hid_device_t* device, uni_controller_t* contr uni_gamepad_t *uni_gp = &controller->gamepad; int idx = uni_hid_device_get_idx_for_instance(device); + +#if BLUEPAD32_UART_LOG_INPUT + { + bool changed = std::memcmp(uni_gp, &prev_uni_gp[idx], sizeof(uni_gamepad_t)) != 0; + if (changed) { + printf("[BP32 idx=%d] dpad=0x%02x btns=0x%04x misc=0x%02x brake=%u throttle=%u " + "Lx=%d Ly=%d Rx=%d Ry=%d\n", + idx, (unsigned)uni_gp->dpad, (unsigned)uni_gp->buttons, (unsigned)uni_gp->misc_buttons, + (unsigned)uni_gp->brake, (unsigned)uni_gp->throttle, + (int)uni_gp->axis_x, (int)uni_gp->axis_y, (int)uni_gp->axis_rx, (int)uni_gp->axis_ry); + } + } +#endif Gamepad* gamepad = bt_devices_[idx].gamepad; Gamepad::PadIn gp_in; @@ -233,25 +278,82 @@ static void controller_data_cb(uni_hid_device_t* device, uni_controller_t* contr break; } - if (uni_gp->buttons & BUTTON_A) gp_in.buttons |= gamepad->MAP_BUTTON_A; - if (uni_gp->buttons & BUTTON_B) gp_in.buttons |= gamepad->MAP_BUTTON_B; - if (uni_gp->buttons & BUTTON_X) gp_in.buttons |= gamepad->MAP_BUTTON_X; - if (uni_gp->buttons & BUTTON_Y) gp_in.buttons |= gamepad->MAP_BUTTON_Y; - if (uni_gp->buttons & BUTTON_SHOULDER_L) gp_in.buttons |= gamepad->MAP_BUTTON_LB; - if (uni_gp->buttons & BUTTON_SHOULDER_R) gp_in.buttons |= gamepad->MAP_BUTTON_RB; - if (uni_gp->buttons & BUTTON_THUMB_L) gp_in.buttons |= gamepad->MAP_BUTTON_L3; - if (uni_gp->buttons & BUTTON_THUMB_R) gp_in.buttons |= gamepad->MAP_BUTTON_R3; - if (uni_gp->misc_buttons & MISC_BUTTON_BACK) gp_in.buttons |= gamepad->MAP_BUTTON_BACK; - if (uni_gp->misc_buttons & MISC_BUTTON_START) gp_in.buttons |= gamepad->MAP_BUTTON_START; - if (uni_gp->misc_buttons & MISC_BUTTON_SYSTEM) gp_in.buttons |= gamepad->MAP_BUTTON_SYS; - - gp_in.trigger_l = gamepad->scale_trigger_l<10>(static_cast(uni_gp->brake)); - gp_in.trigger_r = gamepad->scale_trigger_r<10>(static_cast(uni_gp->throttle)); + if (is_wii_controller_connected(idx)) { + if (uni_gp->buttons & BUTTON_A) gp_in.buttons |= gamepad->MAP_BUTTON_A; + if (uni_gp->buttons & BUTTON_B) gp_in.buttons |= gamepad->MAP_BUTTON_B; + if (uni_gp->buttons & BUTTON_X) gp_in.buttons |= gamepad->MAP_BUTTON_X; + if (uni_gp->buttons & BUTTON_Y) gp_in.buttons |= gamepad->MAP_BUTTON_Y; + if (uni_gp->buttons & BUTTON_SHOULDER_L) gp_in.buttons |= gamepad->MAP_BUTTON_LB; + if (uni_gp->buttons & BUTTON_SHOULDER_R) gp_in.buttons |= gamepad->MAP_BUTTON_RB; + //if (uni_gp->buttons & BUTTON_THUMB_L) gp_in.buttons |= gamepad->MAP_BUTTON_L3; + //if (uni_gp->buttons & BUTTON_THUMB_R) gp_in.buttons |= gamepad->MAP_BUTTON_R3; + if (uni_gp->misc_buttons & MISC_BUTTON_BACK) gp_in.buttons |= gamepad->MAP_BUTTON_BACK; + if (uni_gp->misc_buttons & MISC_BUTTON_START) gp_in.buttons |= gamepad->MAP_BUTTON_START; + if (uni_gp->misc_buttons & MISC_BUTTON_SYSTEM) gp_in.buttons |= gamepad->MAP_BUTTON_SYS; + } + else { + if (uni_gp->buttons & BUTTON_A) gp_in.buttons |= gamepad->MAP_BUTTON_A; + if (uni_gp->buttons & BUTTON_B) gp_in.buttons |= gamepad->MAP_BUTTON_B; + if (uni_gp->buttons & BUTTON_X) gp_in.buttons |= gamepad->MAP_BUTTON_X; + if (uni_gp->buttons & BUTTON_Y) gp_in.buttons |= gamepad->MAP_BUTTON_Y; + if (uni_gp->buttons & BUTTON_SHOULDER_L) gp_in.buttons |= gamepad->MAP_BUTTON_LB; + if (uni_gp->buttons & BUTTON_SHOULDER_R) gp_in.buttons |= gamepad->MAP_BUTTON_RB; + if (uni_gp->buttons & BUTTON_THUMB_L) gp_in.buttons |= gamepad->MAP_BUTTON_L3; + if (uni_gp->buttons & BUTTON_THUMB_R) gp_in.buttons |= gamepad->MAP_BUTTON_R3; + if (uni_gp->misc_buttons & MISC_BUTTON_BACK) gp_in.buttons |= gamepad->MAP_BUTTON_BACK; + if (uni_gp->misc_buttons & MISC_BUTTON_START) gp_in.buttons |= gamepad->MAP_BUTTON_START; + if (uni_gp->misc_buttons & MISC_BUTTON_SYSTEM) gp_in.buttons |= gamepad->MAP_BUTTON_SYS; + } + + // Check for disconnect combo: Start+Select for most controllers, L3+R3 for OUYA (no Start/Select) + static uint32_t disconnect_combo_hold_time[MAX_GAMEPADS] = {0}; + bool is_ouya = (device->controller_type == CONTROLLER_TYPE_OUYAController); + bool combo_pressed = is_ouya + ? ((uni_gp->buttons & BUTTON_THUMB_L) && (uni_gp->buttons & BUTTON_THUMB_R)) + : ((uni_gp->misc_buttons & MISC_BUTTON_START) && (uni_gp->misc_buttons & MISC_BUTTON_BACK)); + + if (combo_pressed) { + disconnect_combo_hold_time[idx]++; + // Require combo to be held for ~500ms (assuming ~60Hz callback rate, ~30 frames) + if (disconnect_combo_hold_time[idx] >= 30) { + printf("[BP32] Disconnect combo detected, disconnecting controller %d\n", idx); + uni_hid_device_disconnect(device); + disconnect_combo_hold_time[idx] = 0; + return; // Don't process further input after disconnect + } + } else { + disconnect_combo_hold_time[idx] = 0; + } + + // Prefer analog triggers (brake / throttle) when present, but fall back to + // digital trigger buttons (e.g. Wii U LT / RT) when analog value is zero. + // For Wii controllers: Z button (shoulder) reports brake/throttle, but we only want it to map to LB/RB, not triggers + // So skip trigger mapping when shoulder buttons are pressed on Wii controllers + bool wii_shoulder_pressed = is_wii_controller_connected(idx) && + ((uni_gp->buttons & BUTTON_SHOULDER_L) || (uni_gp->buttons & BUTTON_SHOULDER_R)); + + if (!wii_shoulder_pressed) { + gp_in.trigger_l = gamepad->scale_trigger_l<10>(static_cast(uni_gp->brake)); + gp_in.trigger_r = gamepad->scale_trigger_r<10>(static_cast(uni_gp->throttle)); + + if (gp_in.trigger_l == 0 && (uni_gp->buttons & BUTTON_TRIGGER_L)) { + gp_in.trigger_l = 0xFF; + } + if (gp_in.trigger_r == 0 && (uni_gp->buttons & BUTTON_TRIGGER_R)) { + gp_in.trigger_r = 0xFF; + } + } std::tie(gp_in.joystick_lx, gp_in.joystick_ly) = gamepad->scale_joystick_l<10>(uni_gp->axis_x, uni_gp->axis_y); std::tie(gp_in.joystick_rx, gp_in.joystick_ry) = gamepad->scale_joystick_r<10>(uni_gp->axis_rx, uni_gp->axis_ry); gamepad->set_pad_in(gp_in); + +#if BLUEPAD32_UART_LOG_INPUT + if (idx >= 0 && idx < static_cast(MAX_GAMEPADS)) { + std::memcpy(&prev_uni_gp[idx], uni_gp, sizeof(uni_gamepad_t)); + } +#endif } const uni_property_t* get_property_cb(uni_property_idx_t idx) @@ -279,7 +381,7 @@ uni_platform* get_driver() //Public API -void run_task(Gamepad(&gamepads)[MAX_GAMEPADS]) +void init(Gamepad(&gamepads)[MAX_GAMEPADS]) { for (uint8_t i = 0; i < MAX_GAMEPADS; ++i) { @@ -294,7 +396,11 @@ void run_task(Gamepad(&gamepads)[MAX_GAMEPADS]) led_timer_.context = nullptr; btstack_run_loop_set_timer(&led_timer_, LED_CHECK_TIME_MS); btstack_run_loop_add_timer(&led_timer_); +} +void run_task(Gamepad(&gamepads)[MAX_GAMEPADS]) +{ + init(gamepads); btstack_run_loop_execute(); } diff --git a/Firmware/RP2040/src/Bluepad32/Bluepad32.h b/Firmware/RP2040/src/Bluepad32/Bluepad32.h index 19b13a21..c823ed92 100644 --- a/Firmware/RP2040/src/Bluepad32/Bluepad32.h +++ b/Firmware/RP2040/src/Bluepad32/Bluepad32.h @@ -11,4 +11,5 @@ namespace bluepad32 { void run_task(Gamepad(&gamepads)[MAX_GAMEPADS]); + void init(Gamepad(&gamepads)[MAX_GAMEPADS]); } \ No newline at end of file diff --git a/Firmware/RP2040/src/Board/ogxm_log.cpp b/Firmware/RP2040/src/Board/ogxm_log.cpp index 6bb55c5f..bd2d6b18 100644 --- a/Firmware/RP2040/src/Board/ogxm_log.cpp +++ b/Firmware/RP2040/src/Board/ogxm_log.cpp @@ -24,6 +24,7 @@ std::ostream& operator<<(std::ostream& os, DeviceDriverType type) { case DeviceDriverType::DINPUT: os << "DINPUT"; break; case DeviceDriverType::PSCLASSIC: os << "PSCLASSIC"; break; case DeviceDriverType::SWITCH: os << "SWITCH"; break; + case DeviceDriverType::WIIU: os << "WIIU"; break; case DeviceDriverType::WEBAPP: os << "WEBAPP"; break; case DeviceDriverType::UART_BRIDGE: os << "UART_BRIDGE"; break; default: os << "UNKNOWN"; break; diff --git a/Firmware/RP2040/src/Descriptors/WiiU.h b/Firmware/RP2040/src/Descriptors/WiiU.h new file mode 100644 index 00000000..5c97fd18 --- /dev/null +++ b/Firmware/RP2040/src/Descriptors/WiiU.h @@ -0,0 +1,168 @@ +#ifndef _WIIU_DESCRIPTORS_H_ +#define _WIIU_DESCRIPTORS_H_ + +#include + +#include "tusb.h" + +namespace WiiU +{ + // Wii U GameCube Adapter protocol (same as Dolphin / gc_adapter_read.py) + static constexpr uint8_t HID_REPORT_ID = 0x21; + static constexpr uint8_t INIT_PAYLOAD = 0x13; + static constexpr uint8_t CONTROLLER_PAYLOAD_SIZE = 37; // report_id + 4 * 9 bytes per port + + // Controller type in first byte of each 9-byte port block + static constexpr uint8_t PORT_WIRED = (1 << 4); + static constexpr uint8_t PORT_WIRELESS = (1 << 5); + + // GameCube button bits (bytes 1 and 2 of each port block) + namespace GCButtons + { + // Byte 1 + static constexpr uint8_t A = (1 << 0); + static constexpr uint8_t B = (1 << 1); + static constexpr uint8_t X = (1 << 2); + static constexpr uint8_t Y = (1 << 3); + static constexpr uint8_t LEFT = (1 << 4); + static constexpr uint8_t RIGHT = (1 << 5); + static constexpr uint8_t DOWN = (1 << 6); + static constexpr uint8_t UP = (1 << 7); + // Byte 2 + static constexpr uint8_t START = (1 << 0); + static constexpr uint8_t Z = (1 << 1); + static constexpr uint8_t R = (1 << 2); + static constexpr uint8_t L = (1 << 3); + } + + static constexpr uint8_t STICK_CENTER = 128; + + #pragma pack(push, 1) + // Full adapter input report: report ID 0x21 + 4 ports × 9 bytes + struct InReport + { + uint8_t report_id{ HID_REPORT_ID }; + uint8_t port_data[4][9]; // [port][type, b1, b2, stickX, stickY, substickX, substickY, triggerL, triggerR] + }; + static_assert(sizeof(InReport) == CONTROLLER_PAYLOAD_SIZE, "WiiU::InReport must be 37 bytes"); + #pragma pack(pop) + + static const uint8_t STRING_LANGUAGE[] = { 0x09, 0x04 }; + static const uint8_t STRING_MANUFACTURER[] = "Nintendo"; + static const uint8_t STRING_PRODUCT[] = "GameCube For Switch"; + static const uint8_t STRING_SERIAL[] = "GH-GC-001 T8"; + static const uint8_t STRING_VERSION[] = "1.0"; + + static const uint8_t *STRING_DESCRIPTORS[] __attribute__((unused)) = + { + STRING_LANGUAGE, + STRING_MANUFACTURER, + STRING_PRODUCT, + STRING_SERIAL, + STRING_VERSION + }; + + // Nintendo Wii U GameCube Adapter + static const uint8_t DEVICE_DESCRIPTORS[] = + { + 0x12, // bLength + 0x01, // bDescriptorType (Device) + 0x00, 0x02, // bcdUSB 2.00 + 0x00, // bDeviceClass (Use class information in the Interface Descriptors) + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + 0x40, // bMaxPacketSize0 64 + 0x7E, 0x05, // idVendor 0x057E (Nintendo) + 0x37, 0x03, // idProduct 0x0337 (Wii U GameCube Adapter) + 0x00, 0x01, // bcdDevice 1.00 + 0x01, // iManufacturer (String Index) + 0x02, // iProduct (String Index) + 0x03, // iSerialNumber (String Index) + 0x01, // bNumConfigurations 1 + }; + + // HID report descriptor (exact dump from real Nintendo Wii U GameCube Adapter 057e:0337) + static const uint8_t REPORT_DESCRIPTORS[] = + { + 0x05, 0x05, 0x09, 0x00, 0xA1, 0x01, 0x85, 0x11, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, + 0xFF, 0x00, 0x75, 0x08, 0x95, 0x05, 0x91, 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x21, 0x19, 0x00, 0x2A, + 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x25, 0x81, 0x00, 0xC0, 0xA1, 0x01, + 0x85, 0x12, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x01, + 0x91, 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x22, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, + 0x00, 0x75, 0x08, 0x95, 0x19, 0x81, 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x13, 0x19, 0x00, 0x2A, 0xFF, + 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x01, 0x91, 0x00, 0xC0, 0xA1, 0x01, 0x85, + 0x23, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, + 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x14, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, + 0x75, 0x08, 0x95, 0x01, 0x91, 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x24, 0x19, 0x00, 0x2A, 0xFF, 0x00, + 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x00, 0xC0, 0xA1, 0x01, 0x85, 0x15, + 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x01, 0x91, 0x00, + 0xC0, 0xA1, 0x01, 0x85, 0x25, 0x19, 0x00, 0x2A, 0xFF, 0x00, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, + 0x08, 0x95, 0x02, 0x81, 0x00, 0xC0, + }; + static_assert(sizeof(REPORT_DESCRIPTORS) == 214, "WiiU report descriptor must be 214 bytes"); + + // HID class descriptor (matches real adapter: bcdHID 1.10, report len 214) + static const uint8_t HID_DESCRIPTORS[] = + { + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x10, 0x01, // bcdHID 1.10 + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType[0] (Report) + 0xD6, 0x00, // wDescriptorLength 214 + }; + + // Raw configuration descriptor (matches real adapter: IN 37 bytes, OUT 5 bytes, bInterval 8, bmAttributes 0xe0) + static const uint8_t CONFIGURATION_DESCRIPTORS[] = + { + // Configuration descriptor + 0x09, // bLength + 0x02, // bDescriptorType (Configuration) + 0x29, 0x00, // wTotalLength 41 + 0x01, // bNumInterfaces 1 + 0x01, // bConfigurationValue 1 + 0x00, // iConfiguration (String Index) + 0xE0, // bmAttributes (Self Powered | Remote Wakeup) + 0xFA, // bMaxPower 500mA (0xFA * 2mA) + + // Interface descriptor (HID) + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x00, // bInterfaceNumber 0 + 0x00, // bAlternateSetting 0 + 0x02, // bNumEndpoints 2 + 0x03, // bInterfaceClass (HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + // HID descriptor + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x10, 0x01, // bcdHID 1.10 + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType[0] (Report) + 0xD6, 0x00, // wDescriptorLength 214 + + // Endpoint descriptor (OUT 0x02, 5 bytes, interval 8) + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x02, // bEndpointAddress (OUT) + 0x03, // bmAttributes (Interrupt) + 0x05, 0x00, // wMaxPacketSize 5 + 0x08, // bInterval 8 + + // Endpoint descriptor (IN 0x81, 37 bytes, interval 8) + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x81, // bEndpointAddress (IN) + 0x03, // bmAttributes (Interrupt) + 0x25, 0x00, // wMaxPacketSize 37 + 0x08, // bInterval 8 + }; + +}; // namespace WiiU + +#endif // _WIIU_DESCRIPTORS_H_ diff --git a/Firmware/RP2040/src/Descriptors/XInput.h b/Firmware/RP2040/src/Descriptors/XInput.h index 4f72b306..c625d889 100644 --- a/Firmware/RP2040/src/Descriptors/XInput.h +++ b/Firmware/RP2040/src/Descriptors/XInput.h @@ -172,7 +172,7 @@ namespace XInput static const uint8_t STRING_LANGUAGE[] = { 0x09, 0x04 }; static const uint8_t STRING_MANUFACTURER[] = "Microsoft"; - static const uint8_t STRING_PRODUCT[] = "XInput STANDARD GAMEPAD"; + static const uint8_t STRING_PRODUCT[] = "Xbox 360 Controller"; static const uint8_t STRING_VERSION[] = "1.0"; static const uint8_t *DESC_STRING[] __attribute__((unused)) = @@ -180,7 +180,7 @@ namespace XInput STRING_LANGUAGE, STRING_MANUFACTURER, STRING_PRODUCT, - STRING_VERSION + STRING_VERSION, }; static const uint8_t DESC_DEVICE[] = @@ -188,68 +188,44 @@ namespace XInput 0x12, // bLength 0x01, // bDescriptorType (Device) 0x00, 0x02, // bcdUSB 2.00 - 0xFF, // bDeviceClass - 0xFF, // bDeviceSubClass - 0xFF, // bDeviceProtocol - 0x40, // bMaxPacketSize0 64 - 0x5E, 0x04, // idVendor 0x045E - 0x8E, 0x02, // idProduct 0x028E - 0x14, 0x01, // bcdDevice 2.14 + 0xFF, // bDeviceClass + 0xFF, // bDeviceSubClass + 0xFF, // bDeviceProtocol + 0x40, // bMaxPacketSize0 64 + 0x5E, 0x04, // idVendor 0x045E (Microsoft) + 0x8E, 0x02, // idProduct 0x028E (Xbox 360 Controller) + 0x14, 0x01, // bcdDevice 1.14 0x01, // iManufacturer (String Index) 0x02, // iProduct (String Index) 0x03, // iSerialNumber (String Index) 0x01, // bNumConfigurations 1 }; + // Single-interface XInput config (48 bytes) for PC + static constexpr uint16_t CONFIG_TOTAL_LENGTH = 48; + static const uint8_t DESC_CONFIGURATION[] = { - 0x09, // bLength - 0x02, // bDescriptorType (Configuration) - 0x30, 0x00, // wTotalLength 48 + // Configuration descriptor (9 bytes) + 0x09, 0x02, + 0x30, 0x00, // wTotalLength 48 (LE) 0x01, // bNumInterfaces 1 0x01, // bConfigurationValue - 0x00, // iConfiguration (String Index) - 0x80, // bmAttributes + 0x00, // iConfiguration + 0x80, // bmAttributes (bus powered) 0xFA, // bMaxPower 500mA - 0x09, // bLength - 0x04, // bDescriptorType (Interface) - 0x00, // bInterfaceNumber 0 - 0x00, // bAlternateSetting - 0x02, // bNumEndpoints 2 - 0xFF, // bInterfaceClass - 0x5D, // bInterfaceSubClass - 0x01, // bInterfaceProtocol - 0x00, // iInterface (String Index) - - 0x10, // bLength - 0x21, // bDescriptorType (HID) - // 0x10, 0x01, // bcdHID 1.10 - 0x00, 0x01, // bcdHID 1.00 - 0x01, // bCountryCode - 0x24, // bNumDescriptors - 0x81, // bDescriptorType[0] (Unknown 0x81) - 0x14, 0x03, // wDescriptorLength[0] 788 - 0x00, // bDescriptorType[1] (Unknown 0x00) - 0x03, 0x13, // wDescriptorLength[1] 4867 - 0x01, // bDescriptorType[2] (Unknown 0x02) - 0x00, 0x03, // wDescriptorLength[2] 768 - 0x00, // bDescriptorType[3] (Unknown 0x00) - - 0x07, // bLength - 0x05, // bDescriptorType (Endpoint) - 0x81, // bEndpointAddress (IN/D2H) - 0x03, // bmAttributes (Interrupt) - 0x20, 0x00, // wMaxPacketSize 32 - 0x01, // bInterval 1 (unit depends on device speed) - - 0x07, // bLength - 0x05, // bDescriptorType (Endpoint) - 0x01, // bEndpointAddress (OUT/H2D) - 0x03, // bmAttributes (Interrupt) - 0x20, 0x00, // wMaxPacketSize 32 - 0x08, // bInterval 8 (unit depends on device speed) + // Interface 0: Gamepad (0xFF/0x5D/0x01) + 0x09, 0x04, 0x00, 0x00, 0x02, 0xFF, 0x5D, 0x01, 0x00, + // Vendor descriptor (type 0x21, 16 bytes) + 0x10, 0x21, 0x00, 0x01, 0x01, 0x25, 0x81, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x02, 0x08, 0x00, + // EP 0x81 IN - Interrupt, 32 bytes, 1ms + 0x07, 0x05, 0x81, 0x03, 0x20, 0x00, 0x01, + // EP 0x01 OUT - Interrupt, 32 bytes, 8ms + 0x07, 0x05, 0x01, 0x03, 0x20, 0x00, 0x08, }; + static_assert(sizeof(DESC_CONFIGURATION) == CONFIG_TOTAL_LENGTH, "XInput config descriptor size"); }; #endif // _XINPUT_DESCRIPTORS_H_ \ No newline at end of file diff --git a/Firmware/RP2040/src/Descriptors/XboxOne.h b/Firmware/RP2040/src/Descriptors/XboxOne.h index cda84eb7..2517b1ef 100644 --- a/Firmware/RP2040/src/Descriptors/XboxOne.h +++ b/Firmware/RP2040/src/Descriptors/XboxOne.h @@ -28,36 +28,302 @@ namespace XboxOne static constexpr uint8_t R3 = (1 << 7); }; + // GIP Command IDs + static constexpr uint8_t GIP_CMD_DEVICE_ARRIVAL = 0x02; + static constexpr uint8_t GIP_CMD_DEVICE_STATUS = 0x03; + static constexpr uint8_t GIP_CMD_DEVICE_DESCRIPTOR = 0x04; + static constexpr uint8_t GIP_CMD_VIRTUAL_KEY = 0x07; // Guide/Home; payload byte 4: 01=pressed, 00=released + static constexpr uint8_t GIP_CMD_SET_DEVICE_STATE = 0x09; + static constexpr uint8_t GIP_CMD_INPUT = 0x20; + static constexpr uint8_t GIP_CMD_SYSTEM_FOCUS_CHANGE = 0xE0; + + // GIP Header Flags + namespace GipFlags + { + static constexpr uint8_t NONE = 0x00; + static constexpr uint8_t INTERNAL = 0x20; // Core protocol message (request from host) + static constexpr uint8_t RESPONSE_FIRST = 0xF0; // First chunk of response (from device) + static constexpr uint8_t RESPONSE_CONTINUE = 0xA0; // Continuation chunk of response (from device) + } + + // GIP Battery Types + namespace BatteryType + { + static constexpr uint8_t WIRED = 0; + static constexpr uint8_t STANDARD = 1; + static constexpr uint8_t CHARGE_KIT = 2; + } + + // GIP Battery Levels + namespace BatteryLevel + { + static constexpr uint8_t LOW = 0; + static constexpr uint8_t MEDIUM = 1; + static constexpr uint8_t HIGH = 2; + static constexpr uint8_t FULL = 3; + } + + // GIP Interface GUIDs (for descriptor) + static constexpr uint8_t GUID_GAMEPAD[16] = { + 0x08, 0x2E, 0x40, 0x2C, 0x07, 0xDF, 0x45, 0xE1, + 0xA5, 0xAB, 0xA3, 0x12, 0x7A, 0xF1, 0x97, 0xB5 + }; + #pragma pack(push, 1) - struct InReport + + // GIP Message Header (8 bytes for USB GIP) + struct GipMessageHeader { - struct GipHeader - { - uint8_t command; - uint8_t client : 4; - uint8_t needsAck : 1; - uint8_t internal : 1; - uint8_t chunkStart : 1; - uint8_t chunked : 1; - uint8_t sequence; - uint8_t length; - } header; + uint8_t command; + uint8_t client : 4; + uint8_t needsAck : 1; + uint8_t internal : 1; + uint8_t chunkStart : 1; + uint8_t chunked : 1; + uint8_t sequence; + uint8_t length; + }; + static_assert(sizeof(GipMessageHeader) == 4, "GipMessageHeader must be 4 bytes"); - uint8_t buttons[2]; + // Device Arrival Message (GIP CMD 0x02) + struct DeviceArrivalMessage + { + GipMessageHeader header; + uint64_t deviceId; // Unique device identifier + uint16_t vendorId; // 0x045E + uint16_t productId; // 0x02D1 + struct { + uint16_t major; + uint16_t minor; + uint16_t build; + uint16_t revision; + } firmwareVersion; + uint8_t unknown[8]; // Padding to make total 32 bytes + }; + static_assert(sizeof(DeviceArrivalMessage) == 32, "DeviceArrivalMessage must be 32 bytes"); + + // Device Status Message (GIP CMD 0x03) + struct DeviceStatusMessage + { + GipMessageHeader header; + uint8_t batteryLevel : 2; + uint8_t batteryType : 2; + uint8_t unknown : 3; + uint8_t connected : 1; + uint8_t unknown2[3]; + }; + static_assert(sizeof(DeviceStatusMessage) == 8, "DeviceStatusMessage must be 8 bytes"); - uint16_t trigger_l; - uint16_t trigger_r; + // Device Descriptor Message (GIP CMD 0x04) + // Windows requests this to identify the device type (gamepad, chatpad, etc.) + // According to GIP spec, this is a variable-size message (typically 200-500 bytes) + // It contains a descriptor header with offsets to various metadata blocks + // NOTE: Vendor/product IDs are NOT in the descriptor header - they're only in Device Arrival message + struct DeviceDescriptorHeader + { + uint16_t unknown1; // 0x0293 (real controller value) + uint16_t unknown2; // 0x0010 (real controller value) + uint16_t unknown3; // 0x0001 (real controller value) + uint8_t padding[8]; // 8 bytes of zeros + uint16_t offset_externalCommands; + uint16_t offset_firmwareVersions; + uint16_t offset_audioFormats; + uint16_t offset_inputCommands; + uint16_t offset_outputCommands; + uint16_t offset_classNames; + uint16_t offset_interfaceGuids; + uint16_t offset_hidDescriptor; + uint16_t unknown4; // 0x004C (real controller value) + }; + // Note: Size may be 32 or 36 bytes depending on compiler alignment + // We'll use sizeof() in the code rather than hardcoding + + // Minimal Device Descriptor Message structure + // We'll build this dynamically to include at least the interface GUID + struct DeviceDescriptorMessage + { + GipMessageHeader header; // 4 bytes + DeviceDescriptorHeader descHeader; // 32 bytes + // Following buffer will contain metadata blocks + // For a minimal gamepad descriptor, we need at least: + // - Interface GUID block (16 bytes + 1 byte count = 17 bytes minimum) + uint8_t buffer[64]; // Buffer for metadata (can be expanded) + }; + + // 48-byte input report (4-byte header + 44-byte payload). Header: 0x20, 0x00, sequence, 0x2C. + // We put gamepad data at payload offset 0 (report bytes 4-17) for Linux xpad/GamepadTester. + // Real controller uses payload offset 6 for buttons; both formats are 48 bytes total. + static constexpr uint16_t GIP_INPUT_REPORT_SIZE = 48; + static constexpr uint8_t GIP_INPUT_PAYLOAD_SIZE = 44; // 0x2C + + // Input Report (GIP CMD 0x20) - logical gamepad state (we build 48-byte wire format from this) + struct InReport + { + GipMessageHeader header; + uint16_t buttons; // Bitfield: Sync, Menu, View, A, B, X, Y, Dpad, Shoulders, Thumbs + uint16_t trigger_l; // 0x000 to 0x3FF (0-1023) + uint16_t trigger_r; // 0x000 to 0x3FF (0-1023) int16_t joystick_lx; int16_t joystick_ly; int16_t joystick_rx; int16_t joystick_ry; + uint8_t guide_pressed; // Home/Guide: when set, report bytes 12-13 = 0xF5FE (real controller encoding) + }; + static_assert(sizeof(InReport) == 19, "XboxOne::InReport must be 19 bytes"); + + // Button bitfield for InReport.buttons (uint16_t) + // Order matches Linux evdev / GamepadTester / official Xbox One controller: + // B0=A, B1=B, B2=X, B3=Y, B4=LB, B5=RB, B6=LT, B7=RT, B8=Select, B9=Start, + // B10=L3, B11=R3, B12=DPAD_UP, B13=DPAD_DOWN, B14=DPAD_LEFT, B15=DPAD_RIGHT. (B16=Home separate.) + namespace GamepadButtons + { + static constexpr uint16_t A = 0x0001; // B0 + static constexpr uint16_t B = 0x0002; // B1 + static constexpr uint16_t X = 0x0004; // B2 + static constexpr uint16_t Y = 0x0008; // B3 + static constexpr uint16_t LEFT_SHOULDER = 0x0010; // B4 LB + static constexpr uint16_t RIGHT_SHOULDER = 0x0020; // B5 RB + static constexpr uint16_t LEFT_TRIGGER_BTN = 0x0040; // B6 LT (from trigger value) + static constexpr uint16_t RIGHT_TRIGGER_BTN = 0x0080; // B7 RT + static constexpr uint16_t VIEW = 0x0100; // B8 Select/Back + static constexpr uint16_t MENU = 0x0200; // B9 Start + static constexpr uint16_t LEFT_THUMB = 0x0400; // B10 L3 + static constexpr uint16_t RIGHT_THUMB = 0x0800; // B11 R3 + static constexpr uint16_t DPAD_UP = 0x1000; // B12 + static constexpr uint16_t DPAD_DOWN = 0x2000; // B13 + static constexpr uint16_t DPAD_LEFT = 0x4000; // B14 + static constexpr uint16_t DPAD_RIGHT = 0x8000; // B15 + // B16 = Home/Guide (evdev often as separate key; we can't fit in 16 bits here) + static constexpr uint16_t SYNC = 0x0000; // unused in 16-bit; map to KEY_PROG1 etc. if needed + } - uint8_t reserved[18]; // 18-byte padding at the end + // Set Device State / Force Feedback (GIP CMD 0x09) + struct OutReport + { + GipMessageHeader header; + uint8_t unknown1; + uint8_t flags; // Force feedback flags + uint8_t leftTrigger; // Left trigger vibration (0-255) + uint8_t rightTrigger; // Right trigger vibration (0-255) + uint8_t leftMotor; // Left motor (0-255) + uint8_t rightMotor; // Right motor (0-255) + uint8_t duration; // Duration in 10ms units + uint8_t delay; // Delay before starting + uint8_t repeat; // Repeat count }; - static_assert(sizeof(InReport) == 36, "XboxOne::InReport is not the correct size"); + static_assert(sizeof(OutReport) == 13, "XboxOne::OutReport must be 13 bytes"); + + // Force Feedback Flags + namespace ForceFeedbackFlags + { + static constexpr uint8_t RIGHT_MOTOR = 0x01; + static constexpr uint8_t LEFT_MOTOR = 0x02; + static constexpr uint8_t RIGHT_TRIGGER = 0x04; + static constexpr uint8_t LEFT_TRIGGER = 0x08; + } #pragma pack(pop) + // USB Descriptors + static const uint8_t STRING_LANGUAGE[] = { 0x09, 0x04 }; + static const uint8_t STRING_MANUFACTURER[] = "Microsoft"; + static const uint8_t STRING_PRODUCT[] = "Xbox One Controller"; + static const uint8_t STRING_SERIAL[] = "000000000000"; + static const uint8_t STRING_VERSION[] = "1.0"; + + static const uint8_t *STRING_DESCRIPTORS[] __attribute__((unused)) = + { + STRING_LANGUAGE, + STRING_MANUFACTURER, + STRING_PRODUCT, + STRING_SERIAL, + STRING_VERSION + }; + + // Device descriptor - Windows loads xb1usb for VID_045E&PID_02D1 (Xbox One Controller) + static const uint8_t DEVICE_DESCRIPTORS[] = + { + 0x12, // bLength + 0x01, // bDescriptorType (Device) + 0x00, 0x02, // bcdUSB 2.00 + 0xFF, // bDeviceClass (Vendor Specific) + 0x47, // bDeviceSubClass (71) + 0xD0, // bDeviceProtocol (208) + 0x40, // bMaxPacketSize0 64 + 0x5E, 0x04, // idVendor 0x045E (Microsoft) + 0xD1, 0x02, // idProduct 0x02D1 (Xbox One Controller - Windows compatible) + 0x16, 0x05, // bcdDevice 5.16 (match capture) + 0x01, // iManufacturer (String Index) + 0x02, // iProduct (String Index) + 0x03, // iSerialNumber (String Index) + 0x01, // bNumConfigurations 1 + }; + + // Configuration descriptor - match real controller: Interface 0 has BOTH GIP endpoints (0x02 OUT, 0x82 IN) + static const uint8_t CONFIGURATION_DESCRIPTORS[] = + { + // Configuration descriptor + 0x09, // bLength + 0x02, // bDescriptorType (Configuration) + 0x32, 0x00, // wTotalLength 50 bytes + 0x03, // bNumInterfaces 3 + 0x01, // bConfigurationValue 1 + 0x00, // iConfiguration (String Index) + 0xA0, // bmAttributes (Bus Powered, Remote Wakeup supported) + 0xFA, // bMaxPower 500mA + + // Interface 0 - GIP data (both IN and OUT on this interface, like real controller) + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x00, // bInterfaceNumber 0 + 0x00, // bAlternateSetting 0 + 0x02, // bNumEndpoints 2 + 0xFF, // bInterfaceClass (Vendor Specific) + 0x47, // bInterfaceSubClass (71) + 0xD0, // bInterfaceProtocol (208) + 0x00, // iInterface (String Index) + + // Endpoint 0x02 OUT (first EP in descriptor = 0x02) + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x02, // bEndpointAddress (OUT) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x04, // bInterval 4 + + // Endpoint 0x82 IN (second EP = 0x82) + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x82, // bEndpointAddress (IN) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x04, // bInterval 4 + + // Interface 1 - no endpoints (placeholder for 3-interface layout) + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x01, // bInterfaceNumber 1 + 0x00, // bAlternateSetting 0 + 0x00, // bNumEndpoints 0 + 0xFF, // bInterfaceClass (Vendor Specific) + 0x47, // bInterfaceSubClass (71) + 0xD0, // bInterfaceProtocol (208) + 0x00, // iInterface (String Index) + + // Interface 2 - no endpoints (placeholder for 3-interface layout) + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x02, // bInterfaceNumber 2 + 0x00, // bAlternateSetting 0 + 0x00, // bNumEndpoints 0 + 0xFF, // bInterfaceClass (Vendor Specific) + 0x47, // bInterfaceSubClass (71) + 0xD0, // bInterfaceProtocol (208) + 0x00, // iInterface (String Index) + }; + + static constexpr uint16_t DRIVER_LEN = sizeof(CONFIGURATION_DESCRIPTORS); + }; // namespace XboxOne #endif // _XBOX_ONE_DESCRIPTORS_H_ \ No newline at end of file diff --git a/Firmware/RP2040/src/Gamepad/Gamepad.h b/Firmware/RP2040/src/Gamepad/Gamepad.h index 9f8cca5e..d223aad9 100644 --- a/Firmware/RP2040/src/Gamepad/Gamepad.h +++ b/Firmware/RP2040/src/Gamepad/Gamepad.h @@ -21,8 +21,8 @@ class Gamepad { public: + //Defaults used by device to get buttons - static constexpr uint8_t DPAD_UP = 0x01; static constexpr uint8_t DPAD_DOWN = 0x02; static constexpr uint8_t DPAD_LEFT = 0x04; @@ -224,6 +224,10 @@ class Gamepad mutex_exit(&chatpad_in_mutex_); } + // Wii U GC adapter: set by host when controller uses positive Y for physical up (e.g. Xbox One/360) + void set_stick_y_positive_is_up(bool v) { stick_y_positive_is_up_ = v; } + bool stick_y_positive_is_up() const { return stick_y_positive_is_up_; } + inline void reset_pad_in() { mutex_enter_blocking(&pad_in_mutex_); @@ -359,6 +363,8 @@ class Gamepad bool profile_analog_enabled_{false}; + bool stick_y_positive_is_up_{false}; // true for Xbox (positive Y = up), false for Wii U/Nintendo + JoystickSettings joy_settings_l_; JoystickSettings joy_settings_r_; TriggerSettings trig_settings_l_; diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/DeviceDriverTypes.h b/Firmware/RP2040/src/USBDevice/DeviceDriver/DeviceDriverTypes.h index 1fae44ec..5cfd7443 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceDriver/DeviceDriverTypes.h +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/DeviceDriverTypes.h @@ -14,6 +14,7 @@ enum class DeviceDriverType : uint8_t DINPUT, PSCLASSIC, SWITCH, + WIIU, WEBAPP = 100, UART_BRIDGE }; diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/Switch/Switch.cpp b/Firmware/RP2040/src/USBDevice/DeviceDriver/Switch/Switch.cpp index df1dddde..2604e347 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceDriver/Switch/Switch.cpp +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/Switch/Switch.cpp @@ -72,10 +72,10 @@ void SwitchDevice::process(const uint8_t idx, Gamepad& gamepad) if (gp_in.buttons & Gamepad::BUTTON_R3) in_report.buttons |= SwitchWired::Buttons::R3; if (gp_in.buttons & Gamepad::BUTTON_SYS) in_report.buttons |= SwitchWired::Buttons::HOME; if (gp_in.buttons & Gamepad::BUTTON_MISC) in_report.buttons |= SwitchWired::Buttons::CAPTURE; - + if (gp_in.trigger_l) in_report.buttons |= SwitchWired::Buttons::ZL; if (gp_in.trigger_r) in_report.buttons |= SwitchWired::Buttons::ZR; - + in_report.joystick_lx = Scale::int16_to_uint8(gp_in.joystick_lx); in_report.joystick_ly = Scale::int16_to_uint8(gp_in.joystick_ly); in_report.joystick_rx = Scale::int16_to_uint8(gp_in.joystick_rx); diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.cpp b/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.cpp new file mode 100644 index 00000000..6ae32692 --- /dev/null +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.cpp @@ -0,0 +1,186 @@ +#include + +#include "Board/ogxm_log.h" +#include "USBDevice/DeviceDriver/WiiU/WiiU.h" + +// Set to 1 to bypass init gate: always send reports for testing (Dolphin/Linux may not send 0x13). +#ifndef WIIU_SKIP_INIT_GATE +#define WIIU_SKIP_INIT_GATE 1 +#endif + +void WiiUDevice::initialize() +{ + class_driver_ = + { + .name = TUD_DRV_NAME("WIIU"), + .init = hidd_init, + .deinit = hidd_deinit, + .reset = hidd_reset, + .open = hidd_open, + .control_xfer_cb = hidd_control_xfer_cb, + .xfer_cb = hidd_xfer_cb, + .sof = NULL + }; + + in_report_.report_id = WiiU::HID_REPORT_ID; + std::memset(in_report_.port_data, 0, sizeof(in_report_.port_data)); + init_received_ = false; +} + +// GC stick deadzone: values near center map to 128 to avoid spurious LEFT/RIGHT/UP/DOWN +static constexpr int16_t STICK_DEADZONE = 4096; // ~12.5% of -32768..32767 + +// Dolphin GameCube test reported max stick range: Left ±111, C-stick ~±110..112 (center 0). +// Our bytes 0–255 map to that via host; Y uses 1..254 to avoid full-up→down misread. + +static uint8_t stick_to_gc(int16_t value) +{ + if (value > -STICK_DEADZONE && value < STICK_DEADZONE) + return WiiU::STICK_CENTER; + return Scale::int16_to_uint8(value); +} + +// Y axis only: map to 1..254 so 0/255 are never sent (some hosts treat extremes as wrong direction). +static uint8_t stick_y_to_gc(int16_t value) +{ + if (value > -STICK_DEADZONE && value < STICK_DEADZONE) + return WiiU::STICK_CENTER; + uint8_t u = Scale::int16_to_uint8(value); + if (u <= 1) return 1u; + if (u >= 254) return 254u; + return u; +} + +static void fill_port_block(uint8_t port[9], const Gamepad::PadIn& gp_in, bool stick_y_positive_is_up) +{ + // Byte 0 (type): Bit 4 = Wired. Dolphin: (type & 0x10) != 0 → connected. + port[0] = WiiU::PORT_WIRED; + + // Byte 1 (b1): A, B, X, Y, D-pad Left, Right, Down, Up — direct mapping (A→A, B→B, etc.) + uint8_t b1 = 0; + if (gp_in.buttons & Gamepad::BUTTON_B) b1 |= WiiU::GCButtons::A; + if (gp_in.buttons & Gamepad::BUTTON_A) b1 |= WiiU::GCButtons::B; + if (gp_in.buttons & Gamepad::BUTTON_Y) b1 |= WiiU::GCButtons::X; + if (gp_in.buttons & Gamepad::BUTTON_X) b1 |= WiiU::GCButtons::Y; + if (gp_in.dpad & Gamepad::DPAD_LEFT) b1 |= WiiU::GCButtons::LEFT; + if (gp_in.dpad & Gamepad::DPAD_RIGHT) b1 |= WiiU::GCButtons::RIGHT; + if (gp_in.dpad & Gamepad::DPAD_DOWN) b1 |= WiiU::GCButtons::DOWN; + if (gp_in.dpad & Gamepad::DPAD_UP) b1 |= WiiU::GCButtons::UP; + port[1] = b1; + + // Byte 2 (b2): Start, Z, R (digital), L (digital) + // L/R come from triggers at 255; Z from RB; LB maps to nothing + uint8_t b2 = 0; + if (gp_in.buttons & Gamepad::BUTTON_START) b2 |= WiiU::GCButtons::START; + if (gp_in.buttons & Gamepad::BUTTON_RB) b2 |= WiiU::GCButtons::Z; + if (gp_in.trigger_l >= 255) b2 |= WiiU::GCButtons::L; + if (gp_in.trigger_r >= 255) b2 |= WiiU::GCButtons::R; + port[2] = b2; + + // Bytes 3–4: main stick X, Y (0–255, 128 = center). + // Y: set by host — Xbox uses positive=up (send value), Wii U/Nintendo use negative=up (send 255-value). + port[3] = stick_to_gc(gp_in.joystick_lx); + { + uint8_t ly = stick_y_to_gc(gp_in.joystick_ly); + port[4] = stick_y_positive_is_up ? ly : static_cast(255 - ly); + } + // Bytes 5–6: C-stick (substick) X, Y + port[5] = stick_to_gc(gp_in.joystick_rx); + { + uint8_t ry = stick_y_to_gc(gp_in.joystick_ry); + port[6] = stick_y_positive_is_up ? ry : static_cast(255 - ry); + } + // Bytes 7–8: L/R trigger analog (0–255) + port[7] = gp_in.trigger_l; + port[8] = gp_in.trigger_r; +} + +void WiiUDevice::process(const uint8_t idx, Gamepad& gamepad) +{ + if (idx >= 4) + return; + + // Always update from current gamepad state so late controller connections are reflected immediately. + // Dolphin may stop re-checking after seeing initial zeros; filling every frame avoids that. + // Combo check (check_for_driver_change) also uses get_pad_in(); both see the same current state. + Gamepad::PadIn gp_in = gamepad.get_pad_in(); + fill_port_block(in_report_.port_data[idx], gp_in, gamepad.stick_y_positive_is_up()); + + if (tud_suspended()) + tud_remote_wakeup(); + + // Send once per frame after the last port, only when init received (or WIIU_SKIP_INIT_GATE) and endpoint ready. + // Real adapter does not send reports until host sends Start Polling (0x13). + constexpr uint8_t last_port = (MAX_GAMEPADS >= 4) ? 3 : (MAX_GAMEPADS - 1); + if (idx == last_port && (WIIU_SKIP_INIT_GATE || init_received_) && tud_hid_n_ready(0)) + { + // TinyUSB prepends report_id; pass only port_data (36 bytes) to avoid double report ID + tud_hid_n_report(0, WiiU::HID_REPORT_ID, in_report_.port_data, sizeof(in_report_.port_data)); +#if defined(CONFIG_OGXM_DEBUG) + static uint32_t send_count = 0; + if (++send_count % 500 == 0) + OGXM_LOG("WiiU: IN report sent (count " + std::to_string(send_count) + ")\n"); +#endif + } +} + +uint16_t WiiUDevice::get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) +{ + if (report_type != HID_REPORT_TYPE_INPUT || report_id != WiiU::HID_REPORT_ID || itf != 0) + return 0; + if (reqlen < sizeof(WiiU::InReport)) + return 0; + std::memcpy(buffer, &in_report_, sizeof(WiiU::InReport)); + return sizeof(WiiU::InReport); +} + +void WiiUDevice::set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) +{ + // Host sends Start Polling (0x13) via Output report. May be [0x13] or [0x21, 0x13] if report ID prepended. + bool is_init = (bufsize >= 1 && buffer[0] == WiiU::INIT_PAYLOAD) || + (bufsize >= 2 && buffer[1] == WiiU::INIT_PAYLOAD); + if (is_init) + { + init_received_ = true; +#if defined(CONFIG_OGXM_DEBUG) + OGXM_LOG("WiiU: init 0x13 received from host\n"); +#endif + } + (void)itf; + (void)report_id; + (void)report_type; +} + +bool WiiUDevice::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + (void)rhport; + (void)stage; + (void)request; + return false; +} + +const uint16_t* WiiUDevice::get_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + const char *value = reinterpret_cast(WiiU::STRING_DESCRIPTORS[index]); + return get_string_descriptor(value, index); +} + +const uint8_t* WiiUDevice::get_descriptor_device_cb() +{ + return WiiU::DEVICE_DESCRIPTORS; +} + +const uint8_t* WiiUDevice::get_hid_descriptor_report_cb(uint8_t itf) +{ + return WiiU::REPORT_DESCRIPTORS; +} + +const uint8_t* WiiUDevice::get_descriptor_configuration_cb(uint8_t index) +{ + return WiiU::CONFIGURATION_DESCRIPTORS; +} + +const uint8_t* WiiUDevice::get_descriptor_device_qualifier_cb() +{ + return nullptr; +} diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.h b/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.h new file mode 100644 index 00000000..537a12e3 --- /dev/null +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/WiiU/WiiU.h @@ -0,0 +1,28 @@ +#ifndef _WIIU_DEVICE_H_ +#define _WIIU_DEVICE_H_ + +#include + +#include "USBDevice/DeviceDriver/DeviceDriver.h" +#include "Descriptors/WiiU.h" + +class WiiUDevice : public DeviceDriver +{ +public: + void initialize() override; + void process(const uint8_t idx, Gamepad& gamepad) override; + uint16_t get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) override; + void set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) override; + bool vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) override; + const uint16_t* get_descriptor_string_cb(uint8_t index, uint16_t langid) override; + const uint8_t* get_descriptor_device_cb() override; + const uint8_t* get_hid_descriptor_report_cb(uint8_t itf) override; + const uint8_t* get_descriptor_configuration_cb(uint8_t index) override; + const uint8_t* get_descriptor_device_qualifier_cb() override; + +private: + WiiU::InReport in_report_{}; + bool init_received_{ false }; // true after host sends Start Polling (0x13) +}; + +#endif // _WIIU_DEVICE_H_ diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/XInput.cpp b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/XInput.cpp index 43d1465d..89e47d89 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/XInput.cpp +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/XInput.cpp @@ -3,7 +3,7 @@ #include "USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.h" #include "USBDevice/DeviceDriver/XInput/XInput.h" -void XInputDevice::initialize() +void XInputDevice::initialize() { class_driver_ = *tud_xinput::class_driver(); } @@ -94,8 +94,11 @@ uint16_t XInputDevice::get_report_cb(uint8_t itf, uint8_t report_id, hid_report_ void XInputDevice::set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {} -bool XInputDevice::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +bool XInputDevice::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + (void)rhport; + (void)stage; + (void)request; return false; } diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.cpp b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.cpp index 26f38f1d..0f6b07e6 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.cpp +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.cpp @@ -13,14 +13,13 @@ namespace tud_xinput { static constexpr uint16_t ENDPOINT_SIZE = 32; +static constexpr uint8_t DESC_TYPE_VENDOR = 0x21; uint8_t endpoint_in_ = 0xFF; uint8_t endpoint_out_ = 0xFF; uint8_t ep_in_buffer_[ENDPOINT_SIZE]; uint8_t ep_out_buffer_[ENDPOINT_SIZE]; -//Class Driver - static void init(void) { endpoint_in_ = 0xFF; @@ -37,66 +36,75 @@ static bool deinit(void) static void reset(uint8_t rhport) { + (void)rhport; init(); } +static inline uint16_t tu_desc_len(uint8_t const* desc) { return desc[0]; } + static uint16_t open(uint8_t rhport, tusb_desc_interface_t const *itf_descriptor, uint16_t max_length) { - uint16_t driver_length = sizeof(tusb_desc_interface_t) + (itf_descriptor->bNumEndpoints * sizeof(tusb_desc_endpoint_t)) + 16; - - TU_VERIFY(max_length >= driver_length, 0); - - uint8_t const *current_descriptor = tu_desc_next(itf_descriptor); - uint8_t found_endpoints = 0; - while ((found_endpoints < itf_descriptor->bNumEndpoints) && (driver_length <= max_length)) - { - tusb_desc_endpoint_t const *endpoint_descriptor = (tusb_desc_endpoint_t const *)current_descriptor; - if (TUSB_DESC_ENDPOINT == tu_desc_type(endpoint_descriptor)) - { - TU_ASSERT(usbd_edpt_open(rhport, endpoint_descriptor)); - - if (tu_edpt_dir(endpoint_descriptor->bEndpointAddress) == TUSB_DIR_IN) - { - endpoint_in_ = endpoint_descriptor->bEndpointAddress; - } - else - { - endpoint_out_ = endpoint_descriptor->bEndpointAddress; - } - - ++found_endpoints; - } - - current_descriptor = tu_desc_next(current_descriptor); - } - return driver_length; + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const *p_desc = tu_desc_next((uint8_t const*)itf_descriptor); + + if (itf_descriptor->bInterfaceClass != 0xFF || + itf_descriptor->bInterfaceSubClass != 0x5D || + itf_descriptor->bInterfaceProtocol != 0x01) + { + return 0; + } + + if (p_desc[1] == DESC_TYPE_VENDOR) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + for (uint8_t i = 0; i < itf_descriptor->bNumEndpoints; i++) { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *)p_desc; + if (TUSB_DESC_ENDPOINT != tu_desc_type(ep_desc)) break; + TU_VERIFY(usbd_edpt_open(rhport, ep_desc), 0); + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) + endpoint_in_ = ep_desc->bEndpointAddress; + else + endpoint_out_ = ep_desc->bEndpointAddress; + drv_len += sizeof(tusb_desc_endpoint_t); + p_desc = tu_desc_next(p_desc); + } + + if (endpoint_out_ != 0xFF) + usbd_edpt_xfer(rhport, endpoint_out_, ep_out_buffer_, ENDPOINT_SIZE); + + TU_VERIFY(max_length >= drv_len, 0); + return drv_len; } static bool control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { - return true; + (void)rhport; + (void)stage; + (void)request; + return true; } static bool xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { - if (ep_addr == endpoint_out_) - { - usbd_edpt_xfer(BOARD_TUD_RHPORT, endpoint_out_, ep_out_buffer_, ENDPOINT_SIZE); + (void)result; + (void)xferred_bytes; + if (ep_addr == endpoint_out_) { + usbd_edpt_xfer(rhport, endpoint_out_, ep_out_buffer_, ENDPOINT_SIZE); } - return true; + return true; } -// Public API - const usbd_class_driver_t* class_driver() { static const usbd_class_driver_t tud_class_driver_ = { - #if CFG_TUSB_DEBUG >= 2 +#if CFG_TUSB_DEBUG >= 2 .name = "XINPUT", - #else +#else .name = NULL, - #endif +#endif .init = init, .deinit = deinit, .reset = reset, @@ -110,31 +118,23 @@ const usbd_class_driver_t* class_driver() bool send_report_ready() { - if (tud_ready() && - (endpoint_in_ != 0xFF) && - (!usbd_edpt_busy(BOARD_TUD_RHPORT, endpoint_in_))) - { - return true; - } - return false; + return tud_ready() && + (endpoint_in_ != 0xFF) && + (!usbd_edpt_busy(BOARD_TUD_RHPORT, endpoint_in_)); } bool receive_report_ready() { - if (tud_ready() && - (endpoint_out_ != 0xFF) && - (!usbd_edpt_busy(BOARD_TUD_RHPORT, endpoint_out_))) - { - return true; - } - return false; + return tud_ready() && + (endpoint_out_ != 0xFF) && + (!usbd_edpt_busy(BOARD_TUD_RHPORT, endpoint_out_)); } bool send_report(const uint8_t *report, uint16_t len) { if (send_report_ready()) { - std::memcpy(ep_in_buffer_, report, std::min(len, ENDPOINT_SIZE)); + std::memcpy(ep_in_buffer_, report, std::min(len, (uint16_t)ENDPOINT_SIZE)); usbd_edpt_claim(BOARD_TUD_RHPORT, endpoint_in_); usbd_edpt_xfer(BOARD_TUD_RHPORT, endpoint_in_, ep_in_buffer_, sizeof(XInput::InReport)); usbd_edpt_release(BOARD_TUD_RHPORT, endpoint_in_); @@ -151,11 +151,10 @@ bool receive_report(uint8_t *report, uint16_t len) usbd_edpt_xfer(BOARD_TUD_RHPORT, endpoint_out_, ep_out_buffer_, ENDPOINT_SIZE); usbd_edpt_release(BOARD_TUD_RHPORT, endpoint_out_); } - - std::memcpy(report, ep_out_buffer_, std::min(len, ENDPOINT_SIZE)); + std::memcpy(report, ep_out_buffer_, std::min(len, (uint16_t)ENDPOINT_SIZE)); return true; } } // namespace tud_xinput -#endif // (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_XINPUT) \ No newline at end of file +#endif // (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_XINPUT) diff --git a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.h b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.h index 74a87473..9da42428 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.h +++ b/Firmware/RP2040/src/USBDevice/DeviceDriver/XInput/tud_xinput/tud_xinput.h @@ -6,13 +6,12 @@ #include "tusb.h" #include "device/usbd_pvt.h" -namespace tud_xinput +namespace tud_xinput { bool send_report_ready(); bool send_report(const uint8_t *report, uint16_t len); bool receive_report(uint8_t *report, uint16_t len); const usbd_class_driver_t* class_driver(); - -} // namespace tud_xinput +} #endif // _TUD_XINPUT_H_ \ No newline at end of file diff --git a/Firmware/RP2040/src/USBDevice/DeviceManager.cpp b/Firmware/RP2040/src/USBDevice/DeviceManager.cpp index ef660535..76e8e6c4 100644 --- a/Firmware/RP2040/src/USBDevice/DeviceManager.cpp +++ b/Firmware/RP2040/src/USBDevice/DeviceManager.cpp @@ -4,6 +4,7 @@ #include "USBDevice/DeviceDriver/PSClassic/PSClassic.h" #include "USBDevice/DeviceDriver/XInput/XInput.h" #include "USBDevice/DeviceDriver/Switch/Switch.h" +#include "USBDevice/DeviceDriver/WiiU/WiiU.h" #include "USBDevice/DeviceDriver/DInput/DInput.h" #include "USBDevice/DeviceDriver/PS3/PS3.h" #include "USBDevice/DeviceDriver/XboxOG/XboxOG_GP.h" @@ -20,40 +21,56 @@ void DeviceManager::initialize_driver( DeviceDriverType driver_type, Gamepad(&gamepads)[MAX_GAMEPADS]) { //TODO: Put gamepad setup in the drivers themselves bool has_analog = false; + + printf("Attempting to allocate driver\n"); switch (driver_type) { case DeviceDriverType::DINPUT: + printf("DINPUT Loaded\n"); has_analog = true; device_driver_ = std::make_unique(); break; case DeviceDriverType::PS3: + printf("PS3 Loaded\n"); has_analog = true; device_driver_ = std::make_unique(); break; case DeviceDriverType::PSCLASSIC: + printf("PSCLASSIC Loaded\n"); device_driver_ = std::make_unique(); break; case DeviceDriverType::SWITCH: + printf("SWITCH Loaded\n"); device_driver_ = std::make_unique(); break; + case DeviceDriverType::WIIU: + printf("WIIU Loaded\n"); + device_driver_ = std::make_unique(); + break; case DeviceDriverType::XINPUT: + printf("XINPUT Loaded\n"); device_driver_ = std::make_unique(); break; case DeviceDriverType::XBOXOG: + printf("XBOXOG Loaded\n"); has_analog = true; device_driver_ = std::make_unique(); break; case DeviceDriverType::XBOXOG_SB: + printf("XBOXOG SB Loaded\n"); device_driver_ = std::make_unique(); break; case DeviceDriverType::XBOXOG_XR: + printf("XBOXOG XR Loaded\n"); device_driver_ = std::make_unique(); break; case DeviceDriverType::WEBAPP: + printf("WEBAPP Loaded\n"); device_driver_ = std::make_unique(); break; #if defined(CONFIG_EN_UART_BRIDGE) case DeviceDriverType::UART_BRIDGE: + printf("UART Loaded\n"); device_driver_ = std::make_unique(); break; #endif //defined(CONFIG_EN_UART_BRIDGE) diff --git a/Firmware/RP2040/src/USBDevice/tud_callbacks.cpp b/Firmware/RP2040/src/USBDevice/tud_callbacks.cpp index c463ff98..b6ee0d4d 100644 --- a/Firmware/RP2040/src/USBDevice/tud_callbacks.cpp +++ b/Firmware/RP2040/src/USBDevice/tud_callbacks.cpp @@ -20,7 +20,8 @@ uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { DeviceManager::get_instance().get_driver()->set_report_cb(itf, report_id, report_type, buffer, bufsize); - tud_hid_report(report_id, buffer, bufsize); + // Do not echo received output back as input report (e.g. Wii U GC adapter init 0x13 must only be received; + // TinyUSB re-arms the OUT endpoint after this returns so the host can keep sending 0x13). } bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) diff --git a/Firmware/RP2040/src/USBHost/HardwareIDs.h b/Firmware/RP2040/src/USBHost/HardwareIDs.h index b02b7941..faff4776 100644 --- a/Firmware/RP2040/src/USBHost/HardwareIDs.h +++ b/Firmware/RP2040/src/USBHost/HardwareIDs.h @@ -18,7 +18,7 @@ static const HardwareID DINPUT_IDS[] = {0x0810, 0x0003}, // Personal Communication Systems, Inc. Generic {0x146B, 0x0902}, // BigBen Interactive Wired Mini PS3 Game Controller {0x2563, 0x0575}, // SHANWAN 2In1 USB Joystick - {0x046D, 0xC218} // Logitech RumblePad 2 + {0x046D, 0xC218}, // Logitech RumblePad 2 }; static const HardwareID PS3_IDS[] = @@ -51,6 +51,7 @@ static const HardwareID PSCLASSIC_IDS[] = static const HardwareID SWITCH_PRO_IDS[] = { {0x057E, 0x2009}, // Switch Pro + {0x057E, 0x0330} // Wii U Pro // {0x20D6, 0xA711}, // OpenSteamController, emulated pro controller }; diff --git a/Firmware/RP2040/src/USBHost/HostDriver/SwitchWired/SwitchWired.cpp b/Firmware/RP2040/src/USBHost/HostDriver/SwitchWired/SwitchWired.cpp index b5c6210a..5f821c92 100644 --- a/Firmware/RP2040/src/USBHost/HostDriver/SwitchWired/SwitchWired.cpp +++ b/Firmware/RP2040/src/USBHost/HostDriver/SwitchWired/SwitchWired.cpp @@ -7,6 +7,7 @@ void SwitchWiredHost::initialize(Gamepad& gamepad, uint8_t address, uint8_t instance, const uint8_t* report_desc, uint16_t desc_len) { + tuh_hid_receive_report(address, instance); } @@ -64,9 +65,9 @@ void SwitchWiredHost::process_report(Gamepad& gamepad, uint8_t address, uint8_t if (in_report->buttons & SwitchWired::Buttons::L3) gp_in.buttons |= gamepad.MAP_BUTTON_L3; if (in_report->buttons & SwitchWired::Buttons::R3) gp_in.buttons |= gamepad.MAP_BUTTON_R3; - gp_in.trigger_l = (in_report->buttons & SwitchWired::Buttons::ZL) ? Range::MAX : Range::MIN; - gp_in.trigger_r = (in_report->buttons & SwitchWired::Buttons::ZR) ? Range::MAX : Range::MIN; - + if (in_report->buttons & SwitchWired::Buttons::ZR) gp_in.trigger_r = Range::MAX; + if (in_report->buttons & SwitchWired::Buttons::ZL) gp_in.trigger_l = Range::MAX; + std::tie(gp_in.joystick_lx, gp_in.joystick_ly) = gamepad.scale_joystick_l(in_report->joystick_lx, in_report->joystick_ly); std::tie(gp_in.joystick_rx, gp_in.joystick_ry) = gamepad.scale_joystick_r(in_report->joystick_rx, in_report->joystick_ry); diff --git a/Firmware/RP2040/src/USBHost/HostDriver/XInput/XboxOne.cpp b/Firmware/RP2040/src/USBHost/HostDriver/XInput/XboxOne.cpp index 6dab8113..14ff9024 100644 --- a/Firmware/RP2040/src/USBHost/HostDriver/XInput/XboxOne.cpp +++ b/Firmware/RP2040/src/USBHost/HostDriver/XInput/XboxOne.cpp @@ -21,23 +21,24 @@ void XboxOneHost::process_report(Gamepad& gamepad, uint8_t address, uint8_t inst Gamepad::PadIn gp_in; - if (in_report->buttons[1] & XboxOne::Buttons1::DPAD_UP) gp_in.dpad |= gamepad.MAP_DPAD_UP; - if (in_report->buttons[1] & XboxOne::Buttons1::DPAD_DOWN) gp_in.dpad |= gamepad.MAP_DPAD_DOWN; - if (in_report->buttons[1] & XboxOne::Buttons1::DPAD_LEFT) gp_in.dpad |= gamepad.MAP_DPAD_LEFT; - if (in_report->buttons[1] & XboxOne::Buttons1::DPAD_RIGHT) gp_in.dpad |= gamepad.MAP_DPAD_RIGHT; - - if (in_report->buttons[1] & XboxOne::Buttons1::L3) gp_in.buttons |= gamepad.MAP_BUTTON_L3; - if (in_report->buttons[1] & XboxOne::Buttons1::R3) gp_in.buttons |= gamepad.MAP_BUTTON_R3; - if (in_report->buttons[1] & XboxOne::Buttons1::LB) gp_in.buttons |= gamepad.MAP_BUTTON_LB; - if (in_report->buttons[1] & XboxOne::Buttons1::RB) gp_in.buttons |= gamepad.MAP_BUTTON_RB; - if (in_report->buttons[0] & XboxOne::Buttons0::BACK) gp_in.buttons |= gamepad.MAP_BUTTON_BACK; - if (in_report->buttons[0] & XboxOne::Buttons0::START) gp_in.buttons |= gamepad.MAP_BUTTON_START; - if (in_report->buttons[0] & XboxOne::Buttons0::SYNC) gp_in.buttons |= gamepad.MAP_BUTTON_MISC; - if (in_report->buttons[0] & XboxOne::Buttons0::GUIDE) gp_in.buttons |= gamepad.MAP_BUTTON_SYS; - if (in_report->buttons[0] & XboxOne::Buttons0::A) gp_in.buttons |= gamepad.MAP_BUTTON_A; - if (in_report->buttons[0] & XboxOne::Buttons0::B) gp_in.buttons |= gamepad.MAP_BUTTON_B; - if (in_report->buttons[0] & XboxOne::Buttons0::X) gp_in.buttons |= gamepad.MAP_BUTTON_X; - if (in_report->buttons[0] & XboxOne::Buttons0::Y) gp_in.buttons |= gamepad.MAP_BUTTON_Y; + const uint16_t b = in_report->buttons; + if (b & XboxOne::GamepadButtons::DPAD_UP) gp_in.dpad |= gamepad.MAP_DPAD_UP; + if (b & XboxOne::GamepadButtons::DPAD_DOWN) gp_in.dpad |= gamepad.MAP_DPAD_DOWN; + if (b & XboxOne::GamepadButtons::DPAD_LEFT) gp_in.dpad |= gamepad.MAP_DPAD_LEFT; + if (b & XboxOne::GamepadButtons::DPAD_RIGHT) gp_in.dpad |= gamepad.MAP_DPAD_RIGHT; + + if (b & XboxOne::GamepadButtons::LEFT_THUMB) gp_in.buttons |= gamepad.MAP_BUTTON_L3; + if (b & XboxOne::GamepadButtons::RIGHT_THUMB) gp_in.buttons |= gamepad.MAP_BUTTON_R3; + if (b & XboxOne::GamepadButtons::LEFT_SHOULDER) gp_in.buttons |= gamepad.MAP_BUTTON_LB; + if (b & XboxOne::GamepadButtons::RIGHT_SHOULDER) gp_in.buttons |= gamepad.MAP_BUTTON_RB; + if (b & XboxOne::GamepadButtons::VIEW) gp_in.buttons |= gamepad.MAP_BUTTON_BACK; + if (b & XboxOne::GamepadButtons::MENU) gp_in.buttons |= gamepad.MAP_BUTTON_START; + if (b & XboxOne::GamepadButtons::SYNC) gp_in.buttons |= gamepad.MAP_BUTTON_MISC; + if (in_report->guide_pressed) gp_in.buttons |= gamepad.MAP_BUTTON_SYS; + if (b & XboxOne::GamepadButtons::A) gp_in.buttons |= gamepad.MAP_BUTTON_A; + if (b & XboxOne::GamepadButtons::B) gp_in.buttons |= gamepad.MAP_BUTTON_B; + if (b & XboxOne::GamepadButtons::X) gp_in.buttons |= gamepad.MAP_BUTTON_X; + if (b & XboxOne::GamepadButtons::Y) gp_in.buttons |= gamepad.MAP_BUTTON_Y; gp_in.trigger_l = gamepad.scale_trigger_l(static_cast(in_report->trigger_l >> 2)); gp_in.trigger_r = gamepad.scale_trigger_r(static_cast(in_report->trigger_r >> 2)); diff --git a/Firmware/RP2040/src/USBHost/HostManager.h b/Firmware/RP2040/src/USBHost/HostManager.h index e8638fb2..18b8ca99 100644 --- a/Firmware/RP2040/src/USBHost/HostManager.h +++ b/Firmware/RP2040/src/USBHost/HostManager.h @@ -9,7 +9,14 @@ #include #include "Board/Config.h" +#include "Board/ogxm_log.h" #include "USBHost/HardwareIDs.h" + +#if defined(CONFIG_OGXM_DEBUG) +#define debug_printf OGXM_LOG +#else +#define debug_printf(...) ((void)0) +#endif #include "USBHost/HostDriver/XInput/tuh_xinput/tuh_xinput.h" #include "USBHost/HostDriver/HostDriver.h" #include "USBHost/HostDriver/PS5/PS5.h" @@ -70,48 +77,63 @@ class HostManager Device& device_slot = device_slots_[dev_idx]; Interface& interface = device_slot.interfaces[instance]; + debug_printf("Attempting to allocate driver for index %d\n", gp_idx); + switch (driver_type) { case HostDriverType::PS5: + debug_printf("PS5 Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::PS4: + debug_printf("PS4 Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::PS3: + debug_printf("PS3 Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::DINPUT: + debug_printf("DINPUT Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::SWITCH: + debug_printf("SWITCH Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::SWITCH_PRO: + debug_printf("SWITCH PRO Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::N64: + debug_printf("N64 Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::PSCLASSIC: + debug_printf("PSCLASSIC Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::XBOXOG: + debug_printf("XBOXOG Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::XBOXONE: + debug_printf("XBOXONE Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::XBOX360: + debug_printf("XBOX360 Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; case HostDriverType::XBOX360W: //Composite device, takes up all 4 gamepads when mounted + debug_printf("XBOX360W Loaded\n"); fflush(stdout); interface.driver = std::make_unique(gp_idx); break; default: if (is_hid_gamepad(report_desc, desc_len)) { interface.driver = std::make_unique(gp_idx); + debug_printf("HIDHOST Loaded\n"); fflush(stdout); } else { @@ -123,6 +145,10 @@ class HostManager device_slot.address = address; interface.gamepad_idx = gp_idx; interface.gamepad = gamepads_[gp_idx]; + // Wii U GC adapter: Xbox controllers report positive Y for up; Nintendo use negative + const bool xbox_stick_y = (driver_type == HostDriverType::XBOXONE || driver_type == HostDriverType::XBOX360 + || driver_type == HostDriverType::XBOX360W || driver_type == HostDriverType::XBOXOG); + interface.gamepad->set_stick_y_positive_is_up(xbox_stick_y); interface.driver->initialize(*interface.gamepad, device_slot.address, instance, report_desc, desc_len); return true; diff --git a/Firmware/RP2040/src/UserSettings/UserSettings.cpp b/Firmware/RP2040/src/UserSettings/UserSettings.cpp index b0cd1805..ec75d608 100644 --- a/Firmware/RP2040/src/UserSettings/UserSettings.cpp +++ b/Firmware/RP2040/src/UserSettings/UserSettings.cpp @@ -18,6 +18,7 @@ namespace ButtonCombo { static constexpr uint32_t DINPUT = BUTTON_COMBO(Gamepad::BUTTON_START | Gamepad::BUTTON_RB, Gamepad::DPAD_LEFT); static constexpr uint32_t XINPUT = BUTTON_COMBO(Gamepad::BUTTON_START, Gamepad::DPAD_UP); static constexpr uint32_t SWITCH = BUTTON_COMBO(Gamepad::BUTTON_START, Gamepad::DPAD_DOWN); + static constexpr uint32_t WIIU = BUTTON_COMBO(Gamepad::BUTTON_START | Gamepad::BUTTON_LB, Gamepad::DPAD_DOWN); static constexpr uint32_t XBOXOG = BUTTON_COMBO(Gamepad::BUTTON_START, Gamepad::DPAD_RIGHT); static constexpr uint32_t XBOXOG_SB = BUTTON_COMBO(Gamepad::BUTTON_START | Gamepad::BUTTON_RB, Gamepad::DPAD_RIGHT); static constexpr uint32_t XBOXOG_XR = BUTTON_COMBO(Gamepad::BUTTON_START | Gamepad::BUTTON_LB, Gamepad::DPAD_RIGHT); @@ -32,6 +33,7 @@ static constexpr DeviceDriverType VALID_DRIVER_TYPES[] = { DeviceDriverType::XINPUT, DeviceDriverType::PS3, DeviceDriverType::PSCLASSIC, + DeviceDriverType::WIIU, DeviceDriverType::WEBAPP, #if defined(XREMOTE_ROM_AVAILABLE) DeviceDriverType::XBOXOG_XR, @@ -40,6 +42,7 @@ static constexpr DeviceDriverType VALID_DRIVER_TYPES[] = { #elif MAX_GAMEPADS > 1 DeviceDriverType::DINPUT, DeviceDriverType::SWITCH, + DeviceDriverType::WIIU, DeviceDriverType::WEBAPP, #else // MAX_GAMEPADS == 1 @@ -47,6 +50,7 @@ static constexpr DeviceDriverType VALID_DRIVER_TYPES[] = { DeviceDriverType::XBOXOG_SB, DeviceDriverType::DINPUT, DeviceDriverType::SWITCH, + DeviceDriverType::WIIU, DeviceDriverType::WEBAPP, DeviceDriverType::PS3, DeviceDriverType::PSCLASSIC, @@ -63,13 +67,14 @@ struct ComboMap { DeviceDriverType driver; }; -static constexpr std::array BUTTON_COMBO_MAP = {{ +static constexpr std::array BUTTON_COMBO_MAP = {{ { ButtonCombo::XBOXOG, DeviceDriverType::XBOXOG }, { ButtonCombo::XBOXOG_SB, DeviceDriverType::XBOXOG_SB }, { ButtonCombo::XBOXOG_XR, DeviceDriverType::XBOXOG_XR }, { ButtonCombo::WEBAPP, DeviceDriverType::WEBAPP }, { ButtonCombo::DINPUT, DeviceDriverType::DINPUT }, { ButtonCombo::SWITCH, DeviceDriverType::SWITCH }, + { ButtonCombo::WIIU, DeviceDriverType::WIIU }, { ButtonCombo::XINPUT, DeviceDriverType::XINPUT }, { ButtonCombo::PS3, DeviceDriverType::PS3 }, { ButtonCombo::PSCLASSIC, DeviceDriverType::PSCLASSIC } diff --git a/Firmware/RP2040/src/main.cpp b/Firmware/RP2040/src/main.cpp index afdb49d4..51f2254e 100644 --- a/Firmware/RP2040/src/main.cpp +++ b/Firmware/RP2040/src/main.cpp @@ -1,8 +1,19 @@ #include #include "OGXMini/OGXMini.h" +#include "pico/stdlib.h" +#include "pico/flash.h" +#include int main() { + stdio_init_all(); + uart_init(uart0, 115200); // Initialize UART0 at 115200 baud + gpio_set_function(0, GPIO_FUNC_UART); // TX + gpio_set_function(1, GPIO_FUNC_UART); // RX + printf("Debug ready\n"); + + flash_safe_execute_core_init(); + OGXMini::initialize(); OGXMini::run(); return 0; diff --git a/README.md b/README.md index 3aa52f23..74421805 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OGX-Mini +# OGX-Mini 2026 ![OGX-Mini Boards](images/OGX-Mini-github.jpg "OGX-Mini Boards") Firmware for the RP2040, capable of emulating gamepads for several game consoles. The firmware comes in many flavors, supported on the [Adafruit Feather USB Host board](https://www.adafruit.com/product/5723), Pi Pico, Pi Pico 2, Pi Pico W, Pi Pico 2 W, Waveshare RP2040-Zero, Pico/ESP32 hybrid, and a 4-Channel RP2040-Zero setup. @@ -8,10 +8,11 @@ Firmware for the RP2040, capable of emulating gamepads for several game consoles ## Supported platforms - Original Xbox - Playstation 3 -- Nintendo Switch (docked) +- Nintendo Switch 1 & 2 (docked) - XInput (use [**UsbdSecPatch**](https://github.com/InvoxiPlayGames/UsbdSecPatch) for Xbox 360, or select the patch in J-Runner while flashing your NAND) - Playstation Classic - DInput +- Wii U (GameCube Adapter) ## Changing platforms By default the OGX-Mini will emulate an OG Xbox controller, you must hold a button combo for 3 seconds to change which platform you want to play on. Your chosen mode will persist after powering off the device. @@ -32,11 +33,18 @@ Start = Plus (Switch) = Options (Dualsense/DS4) - Start + Dpad Left - PlayStation Classic - Start + A (Cross for PlayStation and B for Switch gamepads) +- Wii U (GameCube Adapter) + - Start + Left Bumper + D-Pad Down - Web Application Mode - Start + Left Bumper + Right Bumper + After a new mode is stored, the RP2040 will reset itself so you don't need to unplug it. +## Disconnecting Controllers +For most controllers pressing and holding Start+Select (+/-, etc) for the controller will disconnect it and restart pairing mode. +For the OUYA controller there is no Start+Select, the disconnection combo has been set to L3+R3. + ## Supported devices ### Wired controllers - Original Xbox Duke and S @@ -67,10 +75,23 @@ Note: There are some third party controllers that can change their VID/PID, thes - Switch Pro - Steam - Stadia -- And more +- Wii U Pro +- Wii Remote + - Supported Extensions: + - Gamepad + - Nunchuck + - GameCube Controller +- 8BitDo Ultimate Wireless (Switch layout) Please visit [**this page**](https://bluepad32.readthedocs.io/en/latest/supported_gamepads/) for a more comprehensive list of supported controllers and Bluetooth pairing instructions. +# Features new to this fork: +Note: These features have been added to the Pico W/ Pico 2 W firmware support, I do not have the other boards to test and implement the same fixes at this time. +- Ability to emulate the GameCube controller adapter for Wii U. (Also tested on Switch 1/ 2) +- Wii U & Switch Pro controllers are supported fully with working LT and RT +- Wii Remotes are supported along with the following controllers connected: Nunchuck/ GameCube/ Wii Gamepad. +- Disconnection Combo has been added: Start+Select for most controllers, L3+R3 for OUYA controllers. + ## Features new to v1.0.0 - Bluetooth functionality for the Pico W, Pico 2 W, and Pico+ESP32. - Web application (connectable via USB or Bluetooth) for configuring deadzones and buttons mappings, supports up to 8 saved profiles. @@ -85,7 +106,7 @@ Please visit [**this page**](https://bluepad32.readthedocs.io/en/latest/supporte - Analog button support on OG Xbox and PS3. - RGB LED support for RP2040-Zero and Adafruit Feather boards. -## Planned additions +## Planned additions from the original creator - More accurate report parser for unknown HID controllers - Hardware design for internal OG Xbox install - Hardware design for 4 channel RP2040-Zero adapter @@ -97,6 +118,16 @@ Please visit [**this page**](https://bluepad32.readthedocs.io/en/latest/supporte - Button macros - Rumble settings (intensity, enabled/disable, etc.) +## Planned additions for this fork +- Output to the following consoles: + - GameCube + - PS2 + - DreamCast + - NES + - SNES + - Genesis + - Master System + ## Hardware For Pi Pico, RP2040-Zero, 4 channel, and ESP32 configurations, please see the hardware folder for diagrams. @@ -104,7 +135,7 @@ I've designed a PCB for the RP2040-Zero so you can make a small form-factor adap OGX-Mini Boards -If you would like a prebuilt unit, you can purchase one, with cable and Xbox adapter included, from my [**Etsy store**](https://www.etsy.com/listing/1426992904/ogx-mini-controller-adapter-for-original). +If you would like a prebuilt unit, you can purchase one, with cable and Xbox adapter included, from the original creators store: [**Etsy store**](https://www.etsy.com/listing/1426992904/ogx-mini-controller-adapter-for-original). ## Adding supported controllers If your third party controller isn't working, but the original version is listed above, send me the device's VID and PID and I'll add it so it's recognized properly. @@ -128,8 +159,8 @@ You can also set ```MAX_GAMEPADS``` which, if greater than one, will only suppor You'll need git, python3, CMake, Ninja and the GCC ARM toolchain installed. CMake scripts will patch some files in Bluepad32 and BTStack and also make sure all git submodules (plus their submodules and dependencies) are downloaded. Here's an example on Windows: ``` -git clone --recursive https://github.com/wiredopposite/OGX-Mini.git -cd OGX-Mini/Firmware/RP2040 +git clone --recursive https://github.com/MegaCadeDev/OGX-Mini-2026.git +cd OGX-Mini-2026/Firmware/RP2040 cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DOGXM_BOARD=PI_PICOW -DMAX_GAMEPADS=1 cmake --build build ``` @@ -141,3 +172,7 @@ Please see the Hardware directory for a diagram showing how to hookup the ESP32 You will need ESP-IDF v5.1, esptool, python3, and git installed. If you use VSCode, you can install the ESP-IDF extension and configure the project for ESP-IDF v5.1, it'll download everything for you and then you just click the build button at the bottom of the window. When you build with ESP-IDF, Cmake will run a python script that copies the necessary BTStack files into the components directory, this is needed since BTStack isn't configured as an ESP-IDF component when you download it with git. + + +# Credit to the original creator [https://wiredopposite.github.io/](https://github.com/wiredopposite/OGX-Mini/tree/master) for the original base of the project! +