Implement OpenGC (GameCube) HID Protocol
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
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?
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.
@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.
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.
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.
ok sound good I can likely have something done tonight
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
};
My players LEDs usage were wrong, I edited my previous post to fix it.
@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 ^^
@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.
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.
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
@darthcloud PID has been merged.