Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Firmware/RP2040/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
138 changes: 122 additions & 16 deletions Firmware/RP2040/src/Bluepad32/Bluepad32.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <atomic>
#include <cstdio>
#include <cstring>
#include <functional>
#include <pico/mutex.h>
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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] = {};

Expand All @@ -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;
Expand Down Expand Up @@ -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<uint16_t>(uni_gp->brake));
gp_in.trigger_r = gamepad->scale_trigger_r<10>(static_cast<uint16_t>(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<uint16_t>(uni_gp->brake));
gp_in.trigger_r = gamepad->scale_trigger_r<10>(static_cast<uint16_t>(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<int>(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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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();
}

Expand Down
1 change: 1 addition & 0 deletions Firmware/RP2040/src/Bluepad32/Bluepad32.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@

namespace bluepad32 {
void run_task(Gamepad(&gamepads)[MAX_GAMEPADS]);
void init(Gamepad(&gamepads)[MAX_GAMEPADS]);
}
1 change: 1 addition & 0 deletions Firmware/RP2040/src/Board/ogxm_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
168 changes: 168 additions & 0 deletions Firmware/RP2040/src/Descriptors/WiiU.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#ifndef _WIIU_DESCRIPTORS_H_
#define _WIIU_DESCRIPTORS_H_

#include <stdint.h>

#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_
Loading