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 · 12 comments
Open

Implement OpenGC (GameCube) HID Protocol #1081

mitchellcairns opened this issue Sep 25, 2024 · 12 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.

@darthcloud
Copy link
Owner

@mitchellcairns

Sorry for long delay on this, looking at implementing this now.

I though initially you were going to send the RAW wired GameCube report but looks like you are modifying it to make it look more like a standard HID device. ex HAT switch instead of discrete buttons, axes scaling to max out range etc..

I Assume the Y axis polarity match standard HID device (down is positive and up is negative)? rather than the Nintendo convention. (Up is positive and down is negative).

I would suggest to also modify the buttons bitfield to match the most common mapping using in HID device:
A, B, C, X, Y, Z, LB, RB, L, R, SL, ST, HO, L3, R3

I see two way of mapping this to gamecube, either by name or by position:
By name:
A, B, 0, X, Y, 0, ZL, ZR, L, R, SL, ST, HO, L3, R3, CAP

Or by position:
A, X, 0, B, Y, 0, ZL, ZR, L, R, SL, ST, HO, L3, R3, CAP

Let me know what you think, I can also use your original spec too.

@darthcloud
Copy link
Owner

I guess you going for some sort of WiiU adapter compatibility?
What's the use case? Wired connection to a wiiu/switch?

So I guess you probably can't change it.

@mitchellcairns
Copy link
Author

I guess you going for some sort of WiiU adapter compatibility? What's the use case? Wired connection to a wiiu/switch?

So I guess you probably can't change it.

This specific profile is meant explicitly for bluetooth; In the context of Windows/Android, there really isn't an existing input method to get the unique aspect of the GameCube controller which is the analog + digital nature of the triggers, while also having rumble feedback.

I'm open to having the descriptor/button order be exactly as you have suggested. If you have a revised descriptor, I can use it. VID/PID are open for changing as well, I'm not exactly registered anywhere so something that is unused would be fine.

I'm deep in a rewrite of my entire firmware currently, and this is not an urgent matter, but if you create a descriptor and have the support for it, I can implement it when I get to that stage and test it with your adapter firmware when that's available for an update.

@darthcloud
Copy link
Owner

ok sound good I can likely have something done tonight

@darthcloud
Copy link
Owner

darthcloud commented Dec 21, 2024

The descriptor below should work with this beta of BlueRetro:
v25.01-beta-18-g4499467_hw1

Changes:

  • Made buttons bitfield use all 16bits (See comment in descriptor to see the mapping BlueRetro expect)
  • Split Rumble and LEDs into 2 different byte
  • Use rumble usage 0x97 in a similar way as Stadia controllers and allow for value 0-255 (8bitdo modkit does allow variable speed on the GC rumble motor)
  • Use standard Player LEDs usage.

Note: BlueRetro doesn't support Players LEDs for generic controller, but I'll add it soon.

IMHO you should use a unique VID, PID, this will allow if needed Drivers and application to recognise the controller without guessing. Since you use a ESP32 you can get a unique PID under the Espressif VID:
https://github.com/espressif/usb-pids
(I did that for my OG Xbox adapter rather than spoof MS one)

/**** 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 (A, X, R, B, Y, L, ZL, ZR, 0, 0, Select, Start, Home, L3, R3, Capture)
    0x05, 0x09, // Usage Page (Button)
    0x19, 0x01, // Usage Minimum (Button 1)
    0x29, 0x10, // Usage Maximum (Button 16)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x75, 0x01, // Report Size (1)
    0x95, 0x10, // Report Count (16)
    0x81, 0x02, // Input (Data,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)
              
    // Rumble
    0x05, 0x0F,       // Usage Page (PID Page)
    0x09, 0x97,       // Usage (DC Enable Actuators)
    0x15, 0x00,       // Logical Minimum (0)
    0x26, 0xFF, 0x00, // Logical Maximum (255)
    0x75, 0x08,       // Report Size (8)
    0x95, 0x01,       // Report Count (1)
    0x91, 0x02,       // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
              
    // Player LEDs
    0x05, 0x08, // Usage Page (LEDs)
    0x19, 0x61, // Usage Minimum (Player 1)
    0x29, 0x68, // Usage Maximum (Player 8)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x75, 0x01, // Report Size (1)
    0x95, 0x08, // Report Count (8)
    0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)

    0xC0 // End Collection
};

@darthcloud
Copy link
Owner

My players LEDs usage were wrong, I edited my previous post to fix it.

@mitchellcairns
Copy link
Author

@darthcloud I've submitted a PID pull request.

I will go ahead and start testing the new descriptor now and getting that moving. Thank you for the collaboration on this ^^

@darthcloud
Copy link
Owner

@mitchellcairns That's great!

Are you planing to use that PID for different products?

I think it's better to make that PID specific to OpenGC.

If you make a new product you can request another PID. Even if the underlying code is the same, the physical product might be different enough that a different PID would be useful in some way.

@mitchellcairns
Copy link
Author

The goal is to have one shared PID for now that would use the same basic report structure, because it's based on my gamepad library which presumes the gamepad will handle any device differences. I think in the future it could be worthwhile having different PIDs per device but this is what I'm going with for now. I suppose you are still right that having different PIDs for different devices would be useful down the line, but for now I would consider this base PID for the 'library' as a generic thing.

@mitchellcairns
Copy link
Author

oh.. and adding one thing-- it's the idea that I want to be able to encourage people to make their own gamepads without the hassle of having to define a PID etc. That's the main reason for having a 'library' PID rather than a per-device at this point

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