darthcloud / BlueRetro

Multiplayer Bluetooth controllers adapter for retro video game consoles
https://blueretro.io
Apache License 2.0
1.36k stars 114 forks source link

Implement OpenGC (GameCube) HID Protocol #1081

Open mitchellcairns opened 1 month ago

mitchellcairns commented 1 month ago

BlueRetro firmware version

24.04

BlueRetro firmware specification

HW2

BlueRetro firmware variant

Universal

BlueRetro hardware type

External adapter dongle (1 port only)

Manufacturer

N/A

System used

Nintendo GameCube

Bluetooth controller brand & name

N/A

What is problem? (only list ONE problem per report)

This is a request to implement a specific HID descriptor specification to support future Bluetooth gamepads that can utilize the full GameCube pad functionality. This includes dual-stage triggers with a separate analog/digital press. This descriptor also includes other button inputs to add compatibility with other consoles (Stick click, Home/Select/4 triggers).

This also supports an output report to set Rumble and the current Player number (Shared output report)

The device name is OpenGC BT Gamepad

I'm using Vendor ID: 0x057E Product ID: 0x0337 (Equal to the GC adapter for Wii U/Switch)

Here's the HID descriptor (Size 145 bytes):

/**** GameCube HID Report Descriptor ****/
const uint8_t gc_hid_report_descriptor[] = {
    0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
    0x09, 0x05, // Usage (Game Pad)
    0xA1, 0x01, // Collection (Application)

    // Report ID for input
    0x85, 0x01, // Report ID (1)

    // Left Joystick X and Y
    0x09, 0x01,       // Usage (Pointer)
    0xA1, 0x00,       // Collection (Physical)
    0x09, 0x30,       //   Usage (X)
    0x09, 0x31,       //   Usage (Y)
    0x15, 0x00,       //   Logical Minimum (0)
    0x26, 0xFF, 0x00, //   Logical Maximum (255)
    0x75, 0x08,       //   Report Size (8)
    0x95, 0x02,       //   Report Count (2)
    0x81, 0x02,       //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,             // End Collection

    // Right Joystick X and Y
    0x09, 0x01,       // Usage (Pointer)
    0xA1, 0x00,       // Collection (Physical)
    0x09, 0x33,       //   Usage (Rx)
    0x09, 0x34,       //   Usage (Ry)
    0x15, 0x00,       //   Logical Minimum (0)
    0x26, 0xFF, 0x00, //   Logical Maximum (255)
    0x75, 0x08,       //   Report Size (8)
    0x95, 0x02,       //   Report Count (2)
    0x81, 0x02,       //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,             // End Collection

    // Left and Right Triggers
    0x09, 0x32,       // Usage (Z) - Left Trigger
    0x09, 0x35,       // Usage (Rz) - Right Trigger
    0x15, 0x00,       // Logical Minimum (0)
    0x26, 0xFF, 0x00, // Logical Maximum (255)
    0x75, 0x08,       // Report Size (8)
    0x95, 0x02,       // Report Count (2)
    0x81, 0x02,       // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

    // Buttons (ABXY, L3/R3, L, R, ZL, ZR, Start, Select, Home, Capture)
    0x05, 0x09, // Usage Page (Button)
    0x19, 0x01, // Usage Minimum (Button 1)
    0x29, 0x0E, // Usage Maximum (Button 14)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x75, 0x01, // Report Size (1)
    0x95, 0x0E, // Report Count (14)
    0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

    // Padding (to align to a full byte after 14 buttons)
    0x75, 0x02, // Report Size (2)
    0x95, 0x01, // Report Count (1)
    0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

    // D-Pad (as a Hat switch)
    0x05, 0x01,       // Usage Page (Generic Desktop Ctrls)
    0x09, 0x39,       // Usage (Hat switch)
    0x15, 0x00,       // Logical Minimum (0)
    0x25, 0x07,       // Logical Maximum (7)
    0x35, 0x00,       // Physical Minimum (0)
    0x46, 0x3B, 0x01, // Physical Maximum (315)
    0x65, 0x14,       // Unit (Degrees)
    0x75, 0x04,       // Report Size (4)
    0x95, 0x01,       // Report Count (1)
    0x81, 0x42,       // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)

    // Padding (to align the D-Pad to a full byte)
    0x75, 0x04, // Report Size (4)
    0x95, 0x01, // Report Count (1)
    0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

    // Output report (for rumble and player number)
    0x85, 0x02, // Report ID (2)
    0x09, 0x21, // Usage (Pwr)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x75, 0x01, // Report Size (1)
    0x95, 0x01, // Report Count (1)
    0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)

    0x09, 0x37, // Usage (Dial)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x04, // Logical Maximum (4)
    0x75, 0x03, // Report Size (3)
    0x95, 0x01, // Report Count (1)
    0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)

    0x75, 0x04, // Report Size (4) - Padding to make it a full byte
    0x95, 0x01, // Report Count (1)
    0x91, 0x03, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)

    0xC0 // End Collection
};

Here's some struct definitions to help along the way

typedef struct {
    //uint8_t report_id Should be set to 0x01
    uint8_t left_x;        // Left joystick X axis
    uint8_t left_y;        // Left joystick Y axis
    uint8_t right_x;       // Right joystick X axis
    uint8_t right_y;       // Right joystick Y axis
    uint8_t left_trigger;  // Left analog trigger
    uint8_t right_trigger; // Right analog trigger
    struct {
        uint8_t a : 1;
        uint8_t b : 1;
        uint8_t x : 1;
        uint8_t y : 1;
        uint8_t l3 : 1;
        uint8_t r3 : 1;
        uint8_t l : 1; // Mirrored Z button/Switch L Button
        uint8_t r : 1; // GameCube Z Button/Switch R Button
    } buttons1;
    struct {
        uint8_t zl : 1; // GameCube L trigger 
        uint8_t zr : 1; // GameCube R trigger
        uint8_t start : 1;
        uint8_t select : 1;
        uint8_t home : 1;
        uint8_t capture : 1;
        uint8_t reserved : 2;  // Padding bits
    } buttons2;

    struct {
        uint8_t dpad : 4;     // D-pad as hat switch
        uint8_t padding : 4;  // Padding to complete the byte
    } dpad;

} gc_input_s;

typedef struct {
    // uint8_t report_id is 0x02
    struct {
        uint8_t rumble : 1;        // Rumble on/off
        uint8_t player_number : 3; // Player number (0-4)
        uint8_t padding : 4;
    } feedback;
} gc_output_s;

What did you expect to happen?

N/A

Attach files like logs or Bluetooth traces here

No response

darthcloud commented 1 month ago

Awesome thanks, I'll likely create a pytest script to simulate it and test my code change this way.

Once that part done can you personally test a BlueRetro FW with your pad?

mitchellcairns commented 1 month ago

Sounds good to me!

I should also mention, the output data is full-scale (0-255). This is to allow compatibility with other consoles without losing resolution. Scaling is recommended for GameCube and N64 to meet the appropriate original controller output range/sensitivity.