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

Bounty #40: Custom VID/PID for XInput and Generic HID #1253

Merged
merged 6 commits into from
Jan 2, 2025
Merged
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
7 changes: 7 additions & 0 deletions proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ message GamepadOptions
optional InputModeAuthType ps5AuthType = 22;
optional InputModeAuthType xinputAuthType = 23;
optional PS4ControllerIDMode ps4ControllerIDMode = 24;
optional bool usbDescOverride = 25;
optional string usbDescProduct = 26 [(nanopb).max_length = 32];
optional string usbDescManufacturer = 27 [(nanopb).max_length = 32];
optional string usbDescVersion = 28 [(nanopb).max_length = 8];
optional bool usbOverrideID = 29;
optional uint32 usbProductID = 30;
optional uint32 usbVendorID = 31;
}

message KeyboardMapping
Expand Down
7 changes: 7 additions & 0 deletions src/config_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config)
INIT_UNSET_PROPERTY(config.gamepadOptions, ps5AuthType, DEFAULT_PS5AUTHENTICATION_TYPE);
INIT_UNSET_PROPERTY(config.gamepadOptions, xinputAuthType, DEFAULT_XINPUTAUTHENTICATION_TYPE);
INIT_UNSET_PROPERTY(config.gamepadOptions, ps4ControllerIDMode, DEFAULT_PS4_ID_MODE);
INIT_UNSET_PROPERTY(config.gamepadOptions, usbDescOverride, false);
INIT_UNSET_PROPERTY_STR(config.gamepadOptions, usbDescProduct, "GP2040-CE (Custom)");
INIT_UNSET_PROPERTY_STR(config.gamepadOptions, usbDescManufacturer, "Open Stick Community");
INIT_UNSET_PROPERTY_STR(config.gamepadOptions, usbDescVersion, "1.0");
INIT_UNSET_PROPERTY(config.gamepadOptions, usbOverrideID, false);
INIT_UNSET_PROPERTY(config.gamepadOptions, usbVendorID, 0x10C4);
INIT_UNSET_PROPERTY(config.gamepadOptions, usbProductID, 0x82C0);

// hotkeyOptions
HotkeyOptions& hotkeyOptions = config.hotkeyOptions;
Expand Down
27 changes: 26 additions & 1 deletion src/configs/webconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,20 @@ std::string setGamepadOptions()
readDoc(gamepadOptions.ps5AuthType, doc, "ps5AuthType");
readDoc(gamepadOptions.xinputAuthType, doc, "xinputAuthType");
readDoc(gamepadOptions.ps4ControllerIDMode, doc, "ps4ControllerIDMode");
readDoc(gamepadOptions.usbDescOverride, doc, "usbDescOverride");
// Copy USB descriptor strings
size_t strSize = sizeof(gamepadOptions.usbDescManufacturer);
strncpy(gamepadOptions.usbDescManufacturer, doc["usbDescManufacturer"], strSize - 1);
gamepadOptions.usbDescManufacturer[strSize - 1] = '\0';
strSize = sizeof(gamepadOptions.usbDescProduct);
strncpy(gamepadOptions.usbDescProduct, doc["usbDescProduct"], strSize - 1);
gamepadOptions.usbDescProduct[strSize - 1] = '\0';
strSize = sizeof(gamepadOptions.usbDescVersion);
strncpy(gamepadOptions.usbDescVersion, doc["usbDescVersion"], strSize - 1);
gamepadOptions.usbDescVersion[strSize - 1] = '\0';
readDoc(gamepadOptions.usbOverrideID, doc, "usbOverrideID");
readDoc(gamepadOptions.usbVendorID, doc, "usbVendorID");
readDoc(gamepadOptions.usbProductID, doc, "usbProductID");

HotkeyOptions& hotkeyOptions = Storage::getInstance().getHotkeyOptions();
save_hotkey(&hotkeyOptions.hotkey01, doc, "hotkey01");
Expand Down Expand Up @@ -718,7 +732,18 @@ std::string getGamepadOptions()
writeDoc(doc, "ps5AuthType", gamepadOptions.ps5AuthType);
writeDoc(doc, "xinputAuthType", gamepadOptions.xinputAuthType);
writeDoc(doc, "ps4ControllerIDMode", gamepadOptions.ps4ControllerIDMode);

writeDoc(doc, "usbDescOverride", gamepadOptions.usbDescOverride);
writeDoc(doc, "usbDescManufacturer", gamepadOptions.usbDescManufacturer);
writeDoc(doc, "usbDescProduct", gamepadOptions.usbDescProduct);
writeDoc(doc, "usbDescVersion", gamepadOptions.usbDescVersion);
writeDoc(doc, "usbOverrideID", gamepadOptions.usbOverrideID);
// Write USB Vendor ID and Product ID as 4 character hex strings with 0 padding
char usbVendorStr[5];
snprintf(usbVendorStr, 5, "%04X", gamepadOptions.usbVendorID);
writeDoc(doc, "usbVendorID", usbVendorStr);
char usbProductStr[5];
snprintf(usbProductStr, 5, "%04X", gamepadOptions.usbProductID);
writeDoc(doc, "usbProductID", usbProductStr);
writeDoc(doc, "fnButtonPin", -1);
GpioMappingInfo* gpioMappings = Storage::getInstance().getGpioMappings().pins;
for (unsigned int pin = 0; pin < NUM_BANK0_GPIOS; pin++) {
Expand Down
33 changes: 32 additions & 1 deletion src/drivers/hid/HIDDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "drivers/hid/HIDDriver.h"
#include "drivers/hid/HIDDescriptors.h"
#include "drivers/shared/driverhelper.h"
#include "storagemanager.h"

static bool hid_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
{
Expand Down Expand Up @@ -122,11 +123,41 @@ bool HIDDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_contr
}

const uint16_t * HIDDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) {
const char *value = (const char *)hid_string_descriptors[index];
char *value;
// Check for override settings
GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions();
if ( gamepadOptions.usbDescOverride == true ) {
switch(index) {
case 1:
value = gamepadOptions.usbDescManufacturer;
break;
case 2:
value = gamepadOptions.usbDescProduct;
break;
case 3:
value = gamepadOptions.usbDescVersion;
default:
value = (char *)hid_string_descriptors[index];
break;
}
} else {
value = (char *)hid_string_descriptors[index];
}

return getStringDescriptor(value, index); // getStringDescriptor returns a static array
}

const uint8_t * HIDDriver::get_descriptor_device_cb() {
// Check for override settings
GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions();
if ( gamepadOptions.usbOverrideID == true ) {
static uint8_t modified_device_descriptor[18];
memcpy(modified_device_descriptor, hid_device_descriptor, sizeof(hid_device_descriptor));
memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.usbVendorID, sizeof(uint16_t)); // Vendor ID
memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.usbProductID, sizeof(uint16_t)); // Product ID
return (const uint8_t*)modified_device_descriptor;
}

return hid_device_descriptor;
}

Expand Down
32 changes: 30 additions & 2 deletions src/drivers/xinput/XInputDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,39 @@ bool XInputDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_co
}

const uint16_t * XInputDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) {
const char *value = (const char *)xinput_get_string_descriptor(index);
return getStringDescriptor(value, index); // getStringDescriptor returns a static array
char *value;
// Check for override settings
GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions();
if ( gamepadOptions.usbDescOverride == true ) {
switch(index) {
case 1:
value = gamepadOptions.usbDescManufacturer;
break;
case 2:
value = gamepadOptions.usbDescProduct;
break;
case 3:
value = gamepadOptions.usbDescVersion;
default:
value = (char *)xinput_get_string_descriptor(index);
break;
}
} else {
value = (char *)xinput_get_string_descriptor(index);
}
return getStringDescriptor((const char*)value, index); // getStringDescriptor returns a static array
}

const uint8_t * XInputDriver::get_descriptor_device_cb() {
// Check for override settings
GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions();
if ( gamepadOptions.usbOverrideID == true ) {
static uint8_t modified_device_descriptor[18];
memcpy(modified_device_descriptor, xinput_device_descriptor, sizeof(xinput_device_descriptor));
memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.usbVendorID, sizeof(uint16_t)); // Vendor ID
memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.usbProductID, sizeof(uint16_t)); // Product ID
return (const uint8_t*)modified_device_descriptor;
}
return xinput_device_descriptor;
}

Expand Down
7 changes: 7 additions & 0 deletions www/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ app.get('/api/getGamepadOptions', (req, res) => {
ps5AuthType: 0,
xinputAuthType: 0,
ps4ControllerIDMode: 0,
usbDescOverride: 0,
usbDescProduct: 'GP2040-CE (Custom)',
usbDescManufacturer: 'Open Stick Community',
usbDescVersion: '1.0',
usbOverrideID: 0,
usbVendorID: '10C4',
usbProductID: '82C0',
hotkey01: {
auxMask: 32768,
buttonsMask: 66304,
Expand Down
4 changes: 2 additions & 2 deletions www/src/Addons/Rotary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const rotaryScheme = {
.label('Encoder One Reset After')
.required(),
encoderOneAllowWrapAround: yup
.number()
.boolean()
.required()
.label('Encoder One Allow Wrap Around'),
encoderOneMultiplier: yup.number().label('Encoder One Multiplier').required(),
Expand All @@ -83,7 +83,7 @@ export const rotaryScheme = {
.label('Encoder Two Reset After')
.required(),
encoderTwoAllowWrapAround: yup
.number()
.boolean()
.required()
.label('Encoder Two Allow Wrap Around'),
encoderTwoMultiplier: yup.number().label('Encoder Two Multiplier').required(),
Expand Down
6 changes: 6 additions & 0 deletions www/src/Contexts/AppContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ yup.addMethod(yup.string, 'validateColor', function () {
);
});

yup.addMethod(yup.string, 'validateUSBHexID', function() {
return this.test('', 'Valid USB hex ID required', (value) =>
value?.match(/^([0-9a-f]{4})$/i),
);
});

yup.addMethod(
yup.NumberSchema,
'validateSelectionWhenValue',
Expand Down
11 changes: 11 additions & 0 deletions www/src/Locales/en/SettingsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,15 @@ export default {
'keyboard-mapping-header-text': 'Keyboard Mapping',
'keyboard-mapping-sub-header-text':
'Use the form below to reconfigure your button-to-key mapping.',
'usb-override': {
'advanced-override': '⚠️ Advanced USB Override',
'invalid-warning-danger':
'DANGER: Invalid USB parameters will cause your input modes to not function!',
'product-name':'Product Name',
'manufacturer':'Manufacturer',
'version':'Version',
'physical-warning-danger':'⚡ Override Physical IDs ⚡ ',
'product-id':'Product ID',
'vendor-id':'Vendor ID',
},
};
10 changes: 6 additions & 4 deletions www/src/Pages/AddonsConfigPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,17 @@ export default function AddonsConfigPage() {
const flattened = flattenObject(storedData);

// Convert turbo LED color if available
values.turboLedColor = hexToInt(values.turboLedColor || '#000000');

const valuesCopy = schema.cast(values); // Strip invalid values
const data = {
...values,
turboLedColor: hexToInt(values.turboLedColor || '#000000')
};
const valuesSchema = schema.cast(data); // Strip invalid values

// Compare what's changed and set it to resultObject
let resultObject = {};
Object.entries(flattened)?.map((entry) => {
const [key, oldVal] = entry;
const newVal = get(valuesCopy, key);
const newVal = get(valuesSchema, key);
if (newVal !== oldVal) {
set(resultObject, key, newVal);
}
Expand Down
Loading
Loading