diff --git a/README.md b/README.md index 7ab8126..cdea06d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ MPG is a C++ library for processing and converting gamepad inputs, with support * XInput (PC, Android, Raspberry Pi, MiSTer, etc.) * DirectInput (PC, Mac, PS3) * Nintendo Switch + * Mega Drive 2 Mini * A standard set of USB descriptors, report data structures and conversion methods for supported input types * Per-button debouncing with a configurable interval * Use D-pad to emulate Left or Right analog stick movement @@ -294,22 +295,22 @@ void GamepadStorage::save() { } MPG uses a generic button labeling for gamepad state, which is then converted to the appropriate input type before sending. Here are the mappings of generic buttons to each supported platform/layout: -| MPG | XInput | Switch | PS3 | DirectInput | Arcade | -| ------ | ------ | ------- | ------------ | ------------ | ------ | -| **B1** | A | B | Cross | 2 | K1 | -| **B2** | B | A | Circle | 3 | K2 | -| **B3** | X | Y | Square | 1 | P1 | -| **B4** | Y | X | Triangle | 4 | P2 | -| **L1** | LB | L | L1 | 5 | P4 | -| **R1** | RB | R | R1 | 6 | P3 | -| **L2** | LT | ZL | L2 | 7 | K4 | -| **R2** | RT | ZR | R2 | 8 | K3 | -| **S1** | Back | Minus | Select | 9 | Coin | -| **S2** | Start | Plus | Start | 10 | Start | -| **L3** | LS | LS | L3 | 11 | LS | -| **R3** | RS | RS | R3 | 12 | RS | -| **A1** | Guide | Home | - | 13 | - | -| **A2** | - | Capture | - | 14 | - | +| MPG | XInput | Switch | PS3 | MD-Mini | DirectInput | Arcade | +| ------ | ------ | ------- | ------------ |---------| ------------ | ------ | +| **B1** | A | B | Cross | A | 2 | K1 | +| **B2** | B | A | Circle | B | 3 | K2 | +| **B3** | X | Y | Square | X | 1 | P1 | +| **B4** | Y | X | Triangle | Y | 4 | P2 | +| **L1** | LB | L | L1 | - | 5 | P4 | +| **R1** | RB | R | R1 | Z | 6 | P3 | +| **L2** | LT | ZL | L2 | - | 7 | K4 | +| **R2** | RT | ZR | R2 | C | 8 | K3 | +| **S1** | Back | Minus | Select | Mode | 9 | Coin | +| **S2** | Start | Plus | Start | Start | 10 | Start | +| **L3** | LS | LS | L3 | - | 11 | LS | +| **R3** | RS | RS | R3 | - | 12 | RS | +| **A1** | Guide | Home | - | - | 13 | - | +| **A2** | - | Capture | - | - | 14 | - | The MPG class contains helper methods for checking the state of each button, for instance `MPG::pressedB1()`, `MPG::pressedR3`, etc. diff --git a/library.json b/library.json index af850e3..d16552a 100644 --- a/library.json +++ b/library.json @@ -1,12 +1,12 @@ { "name": "MPG", - "version": "0.4.0", + "version": "0.4.1", "description": "C++ library for processing and converting gamepad inputs, with support for XInput, DirectInput and Nintendo Switch.", "keywords": ["c++", "baremetal", "gamepad", "hid", "dinput", "directinput", "switch", "xinput"], "authors": [ { - "name": "FeralAI", - "url": "https://github.com/FeralAI/MPG" + "name": "Ann", + "url": "https://github.com/alirin222/MPG" } ], "license": "MIT" diff --git a/library.properties b/library.properties index d996f04..6eb5dcb 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=MPG -version=0.4.0 +version=0.4.1 author=FeralAI maintainer=FeralAI sentence=C++ library for processing and converting gamepad inputs, with support for XInput, DirectInput and Nintendo Switch. diff --git a/src/GamepadDescriptors.cpp b/src/GamepadDescriptors.cpp index 3f35ab9..3fef7a3 100644 --- a/src/GamepadDescriptors.cpp +++ b/src/GamepadDescriptors.cpp @@ -12,6 +12,10 @@ static uint16_t getConfigurationDescriptor(const uint8_t *buffer, InputMode mode buffer = switch_configuration_descriptor; return sizeof(switch_configuration_descriptor); + case INPUT_MODE_MDMINI: + buffer = mdmini_configuration_descriptor; + return sizeof(mdmini_configuration_descriptor); + default: buffer = hid_configuration_descriptor; return sizeof(hid_configuration_descriptor); @@ -30,6 +34,10 @@ static uint16_t getDeviceDescriptor(const uint8_t *buffer, InputMode mode) buffer = switch_device_descriptor; return sizeof(switch_device_descriptor); + case INPUT_MODE_MDMINI: + buffer = mdmini_device_descriptor; + return sizeof(mdmini_device_descriptor); + default: buffer = hid_device_descriptor; return sizeof(hid_device_descriptor); @@ -44,6 +52,10 @@ static uint16_t getHIDDescriptor(const uint8_t *buffer, InputMode mode) buffer = switch_hid_descriptor; return sizeof(switch_hid_descriptor); + case INPUT_MODE_MDMINI: + buffer = mdmini_hid_descriptor; + return sizeof(mdmini_hid_descriptor); + default: buffer = hid_hid_descriptor; return sizeof(hid_hid_descriptor); @@ -58,6 +70,10 @@ static uint16_t getHIDReport(const uint8_t *buffer, InputMode mode) buffer = switch_report_descriptor; return sizeof(switch_report_descriptor); + case INPUT_MODE_MDMINI: + buffer = mdmini_report_descriptor; + return sizeof(mdmini_report_descriptor); + default: buffer = hid_report_descriptor; return sizeof(hid_report_descriptor); @@ -84,6 +100,11 @@ static uint16_t getStringDescriptor(const uint16_t *buffer, InputMode mode, uint size = sizeof(switch_string_descriptors[index]); break; + case INPUT_MODE_MDMINI: + value = (const char *)mdmini_string_descriptors[index]; + size = sizeof(mdmini_string_descriptors[index]); + break; + default: value = (const char *)hid_string_descriptors[index]; size = sizeof(hid_string_descriptors[index]); diff --git a/src/GamepadDescriptors.h b/src/GamepadDescriptors.h index 2aa6ccb..9c5a39b 100644 --- a/src/GamepadDescriptors.h +++ b/src/GamepadDescriptors.h @@ -10,6 +10,7 @@ #include "descriptors/HIDDescriptors.h" #include "descriptors/SwitchDescriptors.h" #include "descriptors/XInputDescriptors.h" +#include "descriptors/MdminiDescriptors.h" // Default value used for networking, override if necessary static uint8_t macAddress[6] = { 0x02, 0x02, 0x84, 0x6A, 0x96, 0x00 }; @@ -26,6 +27,10 @@ static const uint8_t *getConfigurationDescriptor(uint16_t *size, InputMode mode) *size = sizeof(switch_configuration_descriptor); return switch_configuration_descriptor; + case INPUT_MODE_MDMINI: + *size = sizeof(mdmini_configuration_descriptor); + return mdmini_configuration_descriptor; + default: *size = sizeof(hid_configuration_descriptor); return hid_configuration_descriptor; @@ -44,6 +49,10 @@ static const uint8_t *getDeviceDescriptor(uint16_t *size, InputMode mode) *size = sizeof(switch_device_descriptor); return switch_device_descriptor; + case INPUT_MODE_MDMINI: + *size = sizeof(mdmini_device_descriptor); + return mdmini_device_descriptor; + default: *size = sizeof(hid_device_descriptor); return hid_device_descriptor; @@ -58,6 +67,10 @@ static const uint8_t *getHIDDescriptor(uint16_t *size, InputMode mode) *size = sizeof(switch_hid_descriptor); return switch_hid_descriptor; + case INPUT_MODE_MDMINI: + *size = sizeof(mdmini_hid_descriptor); + return mdmini_hid_descriptor; + default: *size = sizeof(hid_hid_descriptor); return hid_hid_descriptor; @@ -72,6 +85,10 @@ static const uint8_t *getHIDReport(uint16_t *size, InputMode mode) *size = sizeof(switch_report_descriptor); return switch_report_descriptor; + case INPUT_MODE_MDMINI: + *size = sizeof(mdmini_report_descriptor); + return mdmini_report_descriptor; + default: *size = sizeof(hid_report_descriptor); return hid_report_descriptor; @@ -127,6 +144,10 @@ static const uint16_t *getStringDescriptor(uint16_t *size, InputMode mode, uint8 str = (char *)switch_string_descriptors[index]; break; + case INPUT_MODE_MDMINI: + str = (char *)mdmini_string_descriptors[index]; + break; + default: str = (char *)hid_string_descriptors[index]; break; diff --git a/src/GamepadEnums.h b/src/GamepadEnums.h index f76322b..8fa78eb 100644 --- a/src/GamepadEnums.h +++ b/src/GamepadEnums.h @@ -14,6 +14,7 @@ typedef enum INPUT_MODE_XINPUT, INPUT_MODE_SWITCH, INPUT_MODE_HID, + INPUT_MODE_MDMINI, INPUT_MODE_CONFIG = 255, } InputMode; diff --git a/src/GamepadState.h b/src/GamepadState.h index 5ecf2d7..82ee766 100644 --- a/src/GamepadState.h +++ b/src/GamepadState.h @@ -13,24 +13,24 @@ /* Gamepad button mapping table: - +--------+--------+---------+----------+----------+--------+ - | MPG | XInput | Switch | PS3 | DInput | Arcade | - +--------+--------+---------+----------|----------+--------+ - | B1 | A | B | Cross | 2 | K1 | - | B2 | B | A | Circle | 3 | K2 | - | B3 | X | Y | Square | 1 | P1 | - | B4 | Y | X | Triangle | 4 | P2 | - | L1 | LB | L | L1 | 5 | P4 | - | R1 | RB | R | R1 | 6 | P3 | - | L2 | LT | ZL | L2 | 7 | K4 | - | R2 | RT | ZR | R2 | 8 | K3 | - | S1 | Back | - | Select | 9 | Coin | - | S2 | Start | + | Start | 10 | Start | - | L3 | LS | LS | L3 | 11 | LS | - | R3 | RS | RS | R3 | 12 | RS | - | A1 | Guide | Home | - | 13 | - | - | A2 | - | Capture | - | 14 | - | - +--------+--------+---------+----------+----------+--------+ + +--------+--------+---------+----------+---------+----------+--------+ + | MPG | XInput | Switch | PS3 | MD-MINI | DInput | Arcade | + +--------+--------+---------+----------|---------|----------+--------+ + | B1 | A | B | Cross | A | 2 | K1 | + | B2 | B | A | Circle | B | 3 | K2 | + | B3 | X | Y | Square | X | 1 | P1 | + | B4 | Y | X | Triangle | Y | 4 | P2 | + | L1 | LB | L | L1 | - | 5 | P4 | + | R1 | RB | R | R1 | Z | 6 | P3 | + | L2 | LT | ZL | L2 | - | 7 | K4 | + | R2 | RT | ZR | R2 | C | 8 | K3 | + | S1 | Back | - | Select | Mode | 9 | Coin | + | S2 | Start | + | Start | Start | 10 | Start | + | L3 | LS | LS | L3 | - | 11 | LS | + | R3 | RS | RS | R3 | - | 12 | RS | + | A1 | Guide | Home | - | - | 13 | - | + | A2 | - | Capture | - | - | 14 | - | + +--------+--------+---------+----------+---------|----------+--------+ */ #define GAMEPAD_MASK_UP (1U << 0) diff --git a/src/MPG.cpp b/src/MPG.cpp index 770145c..258f083 100644 --- a/src/MPG.cpp +++ b/src/MPG.cpp @@ -41,6 +41,18 @@ static XInputReport xinputReport ._reserved = { }, }; +static MdminiReport mdminiReport +{ + .id = 0x01, // ID + .notuse1 = 0x7f, // [Not Use] 0x7f + .notuse2 = 0x7f, // [Not Use] 0x7f + .hat1 = 0x7f, // LEFT:0x00, RIGHT:0xFF + .hat2 = 0x7f, // UP:0x00, DOWN:0xFF + .buttons1 = 0x0f, // X A B Y 1b 1b 1b 1b + .buttons2 = 0x00, // 0b 0b START MODE 0b 0b C Z + .notuse3 = 0x00, // [Not Use] 0x00 +}; + void *MPG::getReport() { switch (options.inputMode) @@ -51,6 +63,9 @@ void *MPG::getReport() case INPUT_MODE_SWITCH: return getSwitchReport(); + case INPUT_MODE_MDMINI: + return getMdminiReport(); + default: return getHIDReport(); } @@ -67,6 +82,9 @@ uint16_t MPG::getReportSize() case INPUT_MODE_SWITCH: return sizeof(SwitchReport); + case INPUT_MODE_MDMINI: + return sizeof(MdminiReport); + default: return sizeof(HIDReport); } @@ -197,6 +215,33 @@ XInputReport *MPG::getXInputReport() return &xinputReport; } +MdminiReport *MPG::getMdminiReport() +{ + mdminiReport.hat1 = 0x7f; + mdminiReport.hat2 = 0x7f; + + if (pressedLeft()) { mdminiReport.hat1 = MDMINI_MASK_LEFT; } + if (pressedRight()) { mdminiReport.hat1 = MDMINI_MASK_RIGHT; } + + if (pressedUp()) { mdminiReport.hat2 = MDMINI_MASK_UP; } + if (pressedDown()) { mdminiReport.hat2 = MDMINI_MASK_DOWN; } + + mdminiReport.buttons1 = 0x0f + | (pressedB1() ? MDMINI_MASK_A : 0) + | (pressedB2() ? MDMINI_MASK_B : 0) + | (pressedB3() ? MDMINI_MASK_X : 0) + | (pressedB4() ? MDMINI_MASK_Y : 0) + ; + + mdminiReport.buttons2 = 0x00 + | (pressedR1() ? MDMINI_MASK_Z : 0) + | (pressedR2() ? MDMINI_MASK_C : 0) + | (pressedS2() ? MDMINI_MASK_START : 0) + | (pressedS1() ? MDMINI_MASK_MODE : 0) + ; + + return &mdminiReport; +} GamepadHotkey MPG::hotkey() { diff --git a/src/MPG.h b/src/MPG.h index aff232c..3d8afdf 100644 --- a/src/MPG.h +++ b/src/MPG.h @@ -130,6 +130,13 @@ class MPG */ XInputReport *getXInputReport(); + /** + * @brief Generate USB report for MD-mini 6B Pad mode. + * + * @return MdminiReport MDmini report pointer. + */ + MdminiReport *getMdminiReport(); + /** * @brief Check for a button press. Used by `pressed[Button]` helper methods. */ diff --git a/src/descriptors/MdminiDescriptors.h b/src/descriptors/MdminiDescriptors.h new file mode 100644 index 0000000..5ba5723 --- /dev/null +++ b/src/descriptors/MdminiDescriptors.h @@ -0,0 +1,181 @@ +/*. + * SPDX-License-Identifier: MIT + * SPDX-FileCopyrightText: Copyright (c) 2022 Ann(@alirin222) + */ + +#pragma once + +#include + +#define MDMINI_ENDPOINT_SIZE 64 + +// HAT1 report (8 bits) +#define MDMINI_MASK_LEFT 0x00 +#define MDMINI_MASK_RIGHT 0xff + +// HAT2 report (8 bits) +#define MDMINI_MASK_UP 0x00 +#define MDMINI_MASK_DOWN 0xff + +// Buttons 1 (8 bits) +#define MDMINI_MASK_X 0x80 +#define MDMINI_MASK_A 0x40 +#define MDMINI_MASK_B 0x20 +#define MDMINI_MASK_Y 0x10 + +// Buttons 2 (8 bits) +#define MDMINI_MASK_C 0x02 +#define MDMINI_MASK_Z 0x01 +#define MDMINI_MASK_START 0x20 +#define MDMINI_MASK_MODE 0x10 + +typedef struct __attribute((packed, aligned(1))) +{ + uint8_t id; + uint8_t notuse1; + uint8_t notuse2; + uint8_t hat1; + uint8_t hat2; + uint8_t buttons1; + uint8_t buttons2; + uint8_t notuse3; +} MdminiReport; + +static const uint8_t mdmini_string_language[] = { 0x09, 0x04 }; +static const uint8_t mdmini_string_manfacturer[] = "SEGA CORP."; +static const uint8_t mdmini_string_product[] = "MEGA DRIVE mini GAMEPAD"; +static const uint8_t mdmini_string_version[] = "1.0"; + +static const uint8_t *mdmini_string_descriptors[] = +{ + mdmini_string_language, + mdmini_string_manfacturer, + mdmini_string_product, + mdmini_string_version +}; + +static const uint8_t mdmini_device_descriptor[] = +{ + 0x12, // bLength + 0x01, // bDescriptorType (Device) + 0x00, 0x02, // bcdUSB 2.00 + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + 0x40, // bMaxPacketSize0 64 + 0xA3, 0x0C, // idVendor 0x0CA3 "Sega Corp." + 0x24, 0x00, // idProduct 0x0024 + 0x07, 0x02, // bcdDevice + 0x00, // iManufacturer (String Index) + 0x02, // iProduct (String Index) + 0x00, // iSerialNumber (String Index) + 0x01, // bNumConfigurations 1 +}; + +static const uint8_t mdmini_hid_descriptor[] = +{ + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x11, 0x01, // bcdHID 1.11 + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType[0] (HID) + 0x65, 0x00, // wDescriptorLength[0] 101 +}; + +static const uint8_t mdmini_configuration_descriptor[] = +{ + 0x09, // bLength + 0x02, // bDescriptorType (Configuration) + 0x29, 0x00, // wTotalLength 41 + 0x01, // bNumInterfaces 1 + 0x01, // bConfigurationValue + 0x00, // iConfiguration (String Index) + 0x80, // bmAttributes + 0x32, // bMaxPower 100mA + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x00, // bInterfaceNumber 0 + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints 2 + 0x03, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x11, 0x01, // bcdHID 1.11 + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType[0] (HID) + 0x65, 0x00, // wDescriptorLength[0] 101 + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x02, // bEndpointAddress (OUT/H2D) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x0A, // bInterval 10 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x81, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x0A, // bInterval 10 (unit depends on device speed) - NOTE: This is 125us on fast USB, which means it polls 8 times faster than the code responds. +}; + +static const uint8_t mdmini_report_descriptor[] = +{ + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x30, // Usage (X) + 0x09, 0x30, // Usage (X) + 0x09, 0x30, // Usage (X) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x25, 0x07, // Logical Maximum (7) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x09, 0x00, // Usage (Undefined) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0A, // Report Count (10) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0A, // Usage Maximum (0x0A) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0A, // Report Count (10) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x09, 0x01, // Usage (Vendor) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xA1, 0x01, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x02, // Usage (Mouse) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection +};