Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement OpenGC (GameCube) HID Protocol #1081

Open
mitchellcairns opened this issue Sep 25, 2024 · 2 comments
Open

Implement OpenGC (GameCube) HID Protocol #1081

mitchellcairns opened this issue Sep 25, 2024 · 2 comments

Comments

@mitchellcairns
Copy link

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
Copy link
Owner

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
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants