├── src ├── RDS4-DS4.hpp ├── api │ ├── UnoJoyAPI.cpp │ ├── UnoJoyAPI.hpp │ └── internals.hpp ├── utils │ ├── utils.cpp │ ├── platform.hpp │ └── utils.hpp └── ds4 │ ├── TransportPUSB.cpp │ ├── Authenticator.hpp │ ├── Controller.hpp │ ├── TransportTeensy.cpp │ ├── Authenticator.cpp │ ├── Controller.cpp │ └── Transport.hpp ├── library.properties ├── examples └── TeensyDS4 │ └── TeensyDS4.ino ├── README.md └── LICENSE.txt /src/RDS4-DS4.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** RDS4-DS4.hpp 3 | * Includes other necessary files for DS4 emulation. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "ds4/Authenticator.hpp" 11 | #include "ds4/Controller.hpp" 12 | #include "ds4/Transport.hpp" 13 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=RDS4Reboot 2 | version=0.0.0 3 | author=dogtopus 4 | maintainer=dogtopus 5 | sentence=A game controller library for current-gen game consoles. 6 | paragraph=RDS4Reboot provides a set of platform-independent, interface-independent APIs for game controller development. It is designed with current generation game consoles in mind and has extensible support for controller authentication. 7 | category=Communication 8 | url=https://github.com/dogtopus/RDS4Reboot 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/api/UnoJoyAPI.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** UnoJoyAPI.cpp 3 | * UnoJoy API definitions. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "utils/platform.hpp" 9 | #include "UnoJoyAPI.hpp" 10 | 11 | namespace rds4 { 12 | namespace api { 13 | 14 | dataForController_t getBlankDataForController() { 15 | dataForController_t buf; 16 | memset(&buf, 0, sizeof(buf)); 17 | buf.leftStickX = 0x80; 18 | buf.leftStickY = 0x80; 19 | buf.rightStickX = 0x80; 20 | buf.rightStickY = 0x80; 21 | return buf; 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/utils.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** utils.cpp 3 | * Various utility functions. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "utils.hpp" 9 | 10 | namespace rds4 { 11 | namespace utils { 12 | 13 | static const uint32_t crc32_table_4b[] = { 14 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 15 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 16 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 17 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 18 | }; 19 | 20 | uint32_t crc32(void *buf, size_t len) { 21 | uint32_t result = 0xfffffffful; 22 | for (size_t i=0; i> 4); 25 | result = crc32_table_4b[result & 0xf] ^ (result >> 4); 26 | } 27 | return result ^ 0xfffffffful; 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/platform.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** platform.hpp 3 | * Platform detection helper. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | // Modern Arduino environment (>= 1.6.6) 11 | #if defined(ARDUINO) && ARDUINO >= 10606 12 | #define RDS4_ARDUINO 13 | #include 14 | 15 | // Check for teensy 16 | #if defined(CORE_TEENSY) && (defined(__MK20DX128__) || \ 17 | defined(__MK20DX256__) || \ 18 | defined(__MKL26Z64__) || \ 19 | defined(__MK64FX512__) || \ 20 | defined(__MK66FX1M0__)) 21 | #define RDS4_TEENSY_3 22 | #endif 23 | 24 | // Older Arduino environment 25 | #elif defined(ARDUINO) 26 | #error "Legacy Arduino environment is not supported." 27 | 28 | // Linux 29 | #elif defined(RDS4_LINUX) 30 | // for int*_t 31 | #include 32 | // for size_t 33 | #include 34 | 35 | #else 36 | #error "Unknown/unsupported environment. If you are targeting for Linux system did you forget to set RDS4_LINUX?" 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/utils/utils.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** utils.hpp 3 | * Various utility functions. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | // For sysdep 11 | #include "platform.hpp" 12 | 13 | #ifdef RDS4_DEBUG 14 | 15 | #if defined(RDS4_LINUX) 16 | 17 | #define RDS4_DBG_DEFINED 18 | #define RDS4_DBG_PHEX(val) printf("%x", val) 19 | #define RDS4_DBG_PRINT(str) printf("%s", str) 20 | #define RDS4_DBG_PRINTLN(str) printf("%s\n", str) 21 | 22 | #elif defined(RDS4_ARDUINO) 23 | 24 | #ifndef RDS4_ARDUINO_DBG_IO 25 | #define RDS4_ARDUINO_DBG_IO Serial 26 | #endif // RDS4_ARDUINO_DBG_IO 27 | 28 | #define RDS4_DBG_DEFINED 29 | #define RDS4_DBG_PHEX(val) RDS4_ARDUINO_DBG_IO.print(val, HEX) 30 | #define RDS4_DBG_PRINT(str) RDS4_ARDUINO_DBG_IO.print(str) 31 | #define RDS4_DBG_PRINTLN(str) RDS4_ARDUINO_DBG_IO.println(str) 32 | 33 | #endif // defined(RDS4_ARDUINO) 34 | #endif // RDS4_DEBUG 35 | 36 | #ifndef RDS4_DBG_DEFINED 37 | #define RDS4_DBG_PHEX(val) do {} while (0) 38 | #define RDS4_DBG_PRINT(str) do {} while (0) 39 | #define RDS4_DBG_PRINTLN(str) do {} while (0) 40 | #endif 41 | 42 | namespace rds4 { 43 | namespace utils { 44 | extern uint32_t crc32(void *buf, size_t len); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/TeensyDS4/TeensyDS4.ino: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Unlicense */ 2 | 3 | #include 4 | #include 5 | 6 | // Namespace aliases 7 | namespace rds4api = rds4::api; 8 | namespace ds4 = rds4::ds4; 9 | 10 | // Setup USBH, which is required by rds4::ds4::AuthenticatorUSBH 11 | USB USBH; 12 | 13 | // Use patched PS4USB instead of the stock one, note that only patched PS4USB works with rds4::ds4::AuthenticatorUSBH 14 | ds4::PS4USB2 RealDS4(&USBH); 15 | 16 | // Setup the authenticator 17 | ds4::AuthenticatorUSBH RealDS4Au(&RealDS4); 18 | 19 | // Setup the transport backend and register the authenticator to it 20 | ds4::TransportTeensy DS4Tr(&RealDS4Au); 21 | 22 | // Setup the DS4 object using the transport backend 23 | ds4::Controller DS4(&DS4Tr); 24 | 25 | void setup() { 26 | Serial1.begin(115200); 27 | Serial1.println("Hello"); 28 | if (USBH.Init() == -1) { 29 | Serial.println("USBH init failed"); 30 | while (1); 31 | } 32 | DS4.begin(); 33 | } 34 | 35 | void loop() { 36 | // Poll the USB host controller 37 | USBH.Task(); 38 | // Handle any updates on auth 39 | DS4Tr.update(); 40 | // Grab updates from the host (e.g. rumble and LED state) 41 | DS4.update(); 42 | // Send DS4 report if possible 43 | DS4.sendReport(); 44 | } 45 | -------------------------------------------------------------------------------- /src/ds4/TransportPUSB.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** TransportDS4PUSB.cpp 3 | * PluggableUSB transport back-ends for PS4 controllers. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "Transport.hpp" 9 | #include "utils/utils.hpp" 10 | 11 | #if defined(RDS4_ARDUINO) && defined(USBCON) 12 | 13 | #if defined(ARDUINO_ARCH_SAMD) 14 | // TODO USB API binding for SAMD 15 | typedef uint32_t usb_ep_t; 16 | // TODO is the return value really uint8_t? 17 | static inline uint8_t USB_Send(usb_ep_t ep, void *buf, uint8_t len) { 18 | return USBDevice.send(ep, buf, len); 19 | } 20 | static inline uint8_t USB_ClearToSend(usb_ep_t ep) { 21 | //TODO 22 | return 0; 23 | } 24 | #else 25 | typedef uint8_t usb_ep_t; 26 | static inline uint8_t USB_ClearToSend(usb_ep_t ep) { 27 | return USB_SendSpace(ep); 28 | } 29 | #endif 30 | 31 | uint8_t TransportDS4PUSB::send(const void *buf, uint8_t len) { 32 | // Use USB_SendSpace() to check if we can send data 33 | // On SAMD this should be done by checking DMA buffer readiness and transfer status. 34 | uint8_t space = USB_ClearToSend(this->getOutEP()); 35 | if (space == 0) { 36 | 37 | USB_Send(this->getInEP(), buf, len); 38 | } else { 39 | return 0; 40 | } 41 | } 42 | 43 | uint8_t TransportDS4PUSB::recv(void *buf, uint8_t len) { 44 | //return USB_Recv(this->getOutEP(), buf, len); 45 | } 46 | 47 | 48 | bool TransportDS4PUSB::available() { 49 | //return USB_Available(this->getOutEP()); 50 | } 51 | 52 | #endif // defined(RDS4_ARDUINO) && defined(USBCON) 53 | -------------------------------------------------------------------------------- /src/api/UnoJoyAPI.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** UnoJoyAPI.hpp 3 | * UnoJoy API definitions. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "utils/platform.hpp" 11 | #include "internals.hpp" 12 | 13 | namespace rds4 { 14 | namespace api { 15 | 16 | typedef struct { 17 | // keys byte 0 18 | uint8_t triangleOn : 1; 19 | uint8_t circleOn : 1; 20 | uint8_t squareOn : 1; 21 | uint8_t crossOn : 1; 22 | uint8_t l1On : 1; 23 | uint8_t l2On : 1; 24 | uint8_t l3On : 1; 25 | uint8_t r1On : 1; 26 | 27 | // keys byte 1 28 | uint8_t r2On : 1; 29 | uint8_t r3On : 1; 30 | uint8_t selectOn : 1; 31 | uint8_t startOn : 1; 32 | uint8_t homeOn : 1; 33 | uint8_t dpadLeftOn : 1; 34 | uint8_t dpadUpOn : 1; 35 | uint8_t dpadRightOn : 1; 36 | 37 | // keys byte 2 38 | uint8_t dpadDownOn : 1; 39 | uint8_t reserved : 7; 40 | 41 | // sticks 42 | uint8_t leftStickX : 8; 43 | uint8_t leftStickY : 8; 44 | uint8_t rightStickX : 8; 45 | uint8_t rightStickY : 8; 46 | } dataForController_t; 47 | 48 | template 49 | class UnoJoyAPI { 50 | public: 51 | void setControllerData(dataForController_t buf) { 52 | auto *t = static_cast(this); 53 | t->setKeyUniversal(Key::X, buf.triangleOn); 54 | t->setKeyUniversal(Key::A, buf.circleOn); 55 | t->setKeyUniversal(Key::Y, buf.squareOn); 56 | t->setKeyUniversal(Key::B, buf.crossOn); 57 | t->setKeyUniversal(Key::LButton, buf.l1On); 58 | t->setKeyUniversal(Key::LTrigger, buf.l2On); 59 | t->setKeyUniversal(Key::LStick, buf.l3On); 60 | t->setKeyUniversal(Key::RButton, buf.r1On); 61 | t->setKeyUniversal(Key::RTrigger, buf.r2On); 62 | t->setKeyUniversal(Key::RStick, buf.r3On); 63 | t->setKeyUniversal(Key::Select, buf.selectOn); 64 | t->setKeyUniversal(Key::Start, buf.startOn); 65 | t->setKeyUniversal(Key::Home, buf.homeOn); 66 | t->setDpadUniversalSOCD(buf.dpadUpOn, buf.dpadRightOn, buf.dpadDownOn, buf.dpadLeftOn); 67 | t->setStick(Stick::L, buf.leftStickX, buf.leftStickY); 68 | t->setStick(Stick::R, buf.rightStickX, buf.rightStickY); 69 | } 70 | }; 71 | 72 | extern dataForController_t getBlankDataForController(); 73 | 74 | } // api 75 | } // rds4 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDS4Reboot 2 | 3 | A (hopefully) better game controller library for current-gen game consoles. 4 | 5 | **WARNING**: Work in progress and not ready for general use. API may change at any time. 6 | 7 | ## Disclaimer 8 | 9 | This project is for research purposes only and does not come with any warranty. Use it at your own risk. 10 | 11 | ## Why not b***k? 12 | 13 | Simply put, existing software/boards like B\*\*\*k, C\*\*\*\*\*u, P\*\*\*0, etc. are either not flexible enough for my needs (I want full control over what the controller sends and receives, not just buttons and sticks), proprietary as f\*\*\*, does not support current-gen consoles because they have "impossible to crack security measures", too expensive, or all of the above. Besides that, I am not hardcore enough to think that scrapping a $60 controller and figuring out how to padhack the touchpad, gyroscope, accelerometer, rumble motor, etc. without running into random obstacles such as frying the motherboard would be a good idea. So here we are. 14 | 15 | ## Use cases 16 | 17 | - Fightsticks 18 | - Rhythm game controllers 19 | - Accessible controllers 20 | - Keyboard/mouse/joystick/gamepad converters 21 | - Creative projects (e.g. beat Dark Souls BloodBorne Sekiro with bananas, etc.) 22 | 23 | ## Usage 24 | 25 | Currently the library only supports Teensy 3.x (including LC). A patched version of Teensyduino core (version 1.45 at the moment) is required, which can be found [here][td-ds4]. 26 | 27 | For Teensyduino IDE users, there are two ways to install the patched library. One way is to run `scripts/makepatch.sh` and apply the generated patch file using the `patch` tool to an existing Teensyduino core installation. The other way is to directly replace the core library of a working installation with the patched version. Then you might want to edit the `boards.txt` in a similar fashion as [this][td-boards] (with necessary changes like replace `USB_XINPUT` with `USB_DS4STUB` and so on). For PlatformIO users, it is possible to use the companion script hosted [here][td-pfio] and follow the instruction [here][td-pfio-readme]. Afterwards add `-DUSB_DS4STUB -UUSB_SERIAL` to the build parameters. 28 | 29 | After patching the Teensyduino core library just download and install RDS4Reboot normally. 30 | 31 | ## Current status 32 | 33 | ### Works 34 | 35 | - Builds on Teensy 3.6 and LC 36 | - USB Host Shield authenticator with 37 | - Official controller 38 | - Hori Mini 39 | - Authentication over USB Host Shield authenticator and Teensy USB transport 40 | - Interrupt IN/OUT reports over Teensy USB transport 41 | - UnoJoy compatibility layer 42 | 43 | ### Does not work/WIP 44 | 45 | - PluggableUSB transport 46 | - More example sketches 47 | 48 | ### Planned 49 | 50 | - A7105 authenticator 51 | 52 | [td-ds4]: https://github.com/dogtopus/teensy-cores 53 | [td-boards]: https://github.com/zlittell/MSF-XINPUT/blob/master/MSF_XINPUT/Teensyduino%20Files%20that%20were%20edited/hardware/teensy/avr/boards.txt#L833 54 | [td-pfio]: https://github.com/dogtopus/FT-Controller-FW/tree/master/patches 55 | [td-pfio-readme]: https://github.com/dogtopus/FT-Controller-FW#build 56 | -------------------------------------------------------------------------------- /src/ds4/Authenticator.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** AuthenticatorDS4.hpp 3 | * Authenticator for PS4 controllers via various back-ends. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "utils/platform.hpp" 11 | #include "api/internals.hpp" 12 | 13 | // Sigh... https://github.com/arduino/arduino-builder/issues/15#issuecomment-145558252 14 | #ifndef RDS4_MANUAL_CONFIG 15 | #if __has_include() 16 | #define RDS4_AUTH_USBH 17 | #endif 18 | #endif // RDS4_MANUAL_CONFIG 19 | 20 | #ifdef RDS4_AUTH_USBH 21 | #include 22 | // For auth structs 23 | #include "Controller.hpp" 24 | #endif 25 | 26 | namespace rds4 { 27 | namespace ds4 { 28 | 29 | class AuthenticatorNull : public api::Authenticator { 30 | void begin() { this->fitPageSize(); } 31 | bool available() override { return true; } 32 | bool canFitPageSize() override { return true; } 33 | bool canSetPageSize() override { return true; } 34 | bool needsReset() override { return false; } 35 | bool fitPageSize() override { 36 | this->challengePageSize = 0x38; 37 | this->responsePageSize = 0x38; 38 | return true; 39 | } 40 | bool endOfChallenge(uint8_t page) override { return true; } 41 | bool endOfResponse(uint8_t page) override { return true; } 42 | bool reset() override { return true; } 43 | size_t writeChallengePage(uint8_t page, void *buf, size_t len) { return len; } 44 | size_t readResponsePage(uint8_t page, void *buf, size_t len) { return 0; } 45 | api::AuthStatus getStatus() override { return api::AuthStatus::UNKNOWN_ERR; } 46 | }; 47 | 48 | #ifdef RDS4_AUTH_USBH 49 | 50 | class AuthenticatorUSBH; 51 | 52 | // TODO reading reports on licensed controllers don't work 53 | /** Modified PS4USB class that adds basic support for some licensed PS4 controllers. */ 54 | class PS4USB2 : public ::PS4USB { 55 | public: 56 | const uint16_t HORI_VID = 0x0f0d; 57 | const uint16_t HORI_PID_MINI = 0x00ee; 58 | const uint16_t RO_VID = 0x1430; 59 | const uint16_t RO_PID_GHPS4 = 0x07bb; 60 | 61 | PS4USB2(USB *p) : ::PS4USB(p), auth(nullptr) {}; 62 | bool connected() { 63 | uint16_t v = ::HIDUniversal::VID; 64 | uint16_t p = ::HIDUniversal::PID; 65 | return ::HIDUniversal::isReady() and this->VIDPIDOK(v, p); 66 | } 67 | 68 | bool isLicensed(void) { 69 | return ::HIDUniversal::VID != PS4_VID; 70 | } 71 | 72 | bool isQuirky(void) { 73 | return (::HIDUniversal::VID == RO_VID and ::HIDUniversal::PID == RO_PID_GHPS4); 74 | } 75 | 76 | protected: 77 | friend class AuthenticatorUSBH; 78 | bool VIDPIDOK(uint16_t vid, uint16_t pid) override { 79 | return (( \ 80 | vid == PS4_VID and ( \ 81 | pid == PS4_PID or \ 82 | pid == PS4_PID_SLIM \ 83 | ) 84 | ) or ( \ 85 | vid == PS4USB2::HORI_VID and ( \ 86 | pid == PS4USB2::HORI_PID_MINI \ 87 | ) \ 88 | ) or ( \ 89 | vid == PS4USB2::RO_VID and ( \ 90 | pid == PS4USB2::RO_PID_GHPS4 \ 91 | ) \ 92 | )); 93 | } 94 | 95 | uint8_t OnInitSuccessful() override; 96 | void registerAuthenticator(AuthenticatorUSBH *auth); 97 | private: 98 | AuthenticatorUSBH *auth; 99 | }; 100 | 101 | /** DS4 authenticator that uses USB PS4 controllers as the backend via USB Host Shield 2.x library. */ 102 | class AuthenticatorUSBH : public api::Authenticator { 103 | public: 104 | static const uint8_t PAYLOAD_MAX = 0x38; 105 | static const uint16_t CHALLENGE_SIZE = 0x100; 106 | static const uint16_t RESPONSE_SIZE = 0x410; 107 | AuthenticatorUSBH(PS4USB2 *donor); 108 | bool available() override; 109 | bool canFitPageSize() override { return true; } 110 | bool canSetPageSize() override { return false; } 111 | bool needsReset() override; 112 | bool fitPageSize() override; 113 | bool setChallengePageSize(uint8_t size) override { return false; } 114 | bool setResponsePageSize(uint8_t size) override { return false; } 115 | bool endOfChallenge(uint8_t page) override { 116 | return ((static_cast(page)+1) * this->getChallengePageSize()) >= AuthenticatorUSBH::CHALLENGE_SIZE; 117 | } 118 | bool endOfResponse(uint8_t page) override { 119 | return ((static_cast(page)+1) * this->getResponsePageSize()) >= AuthenticatorUSBH::RESPONSE_SIZE; 120 | } 121 | bool reset() override; 122 | size_t writeChallengePage(uint8_t page, void *buf, size_t len) override; 123 | size_t readResponsePage(uint8_t page, void *buf, size_t len) override; 124 | api::AuthStatus getStatus() override; 125 | 126 | protected: 127 | friend class PS4USB2; 128 | void onStateChange(); 129 | private: 130 | uint8_t getActualChallengePageSize(uint8_t page); 131 | uint8_t getActualResponsePageSize(uint8_t page); 132 | PS4USB2 *donor; 133 | uint8_t scratchPad[64]; 134 | bool statusOverrideEnabled; 135 | uint32_t statusOverrideTransactionStartTime; 136 | bool statusOverrideInTransaction; 137 | }; 138 | 139 | #endif // RDS4_AUTH_USBH 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/ds4/Controller.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** ControllerDS4.hpp 3 | * High-level report handling API for PS4 controllers. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "utils/platform.hpp" 11 | #include "api/internals.hpp" 12 | #include "api/UnoJoyAPI.hpp" 13 | 14 | namespace rds4 { 15 | namespace ds4 { 16 | 17 | struct TouchFrame { 18 | uint8_t seq; // 34 19 | uint32_t pos[2]; // 35-42 20 | } __attribute__((packed)); 21 | 22 | struct InputReport { 23 | uint8_t type; // 0 24 | union { 25 | struct { 26 | uint8_t stick_l_x; // 1 27 | uint8_t stick_l_y; // 2 28 | uint8_t stick_r_x; // 3 29 | uint8_t stick_r_y; // 4 30 | }; 31 | uint8_t sticks[4]; 32 | }; 33 | uint8_t buttons[3]; // 5-7 34 | union { 35 | struct { 36 | uint8_t trigger_l; // 8 37 | uint8_t trigger_r; // 9 38 | }; 39 | uint8_t triggers[2]; 40 | }; 41 | uint16_t sensor_timestamp; // 10-11 42 | uint8_t battery; // 12 43 | uint8_t u13; // 13 44 | int16_t accel_z; // 14-15 45 | int16_t accel_y; // 16-17 46 | int16_t accel_x; // 18-19 47 | int16_t gyro_x; // 20-21 48 | int16_t gyro_y; // 22-23 49 | int16_t gyro_z; // 24-25 50 | uint32_t u26; // 26-29 51 | uint8_t state_ext; // 30 52 | uint16_t u31; // 31-32 53 | uint8_t tp_available_frame; // 33 54 | TouchFrame frames[3]; // 34-60 55 | uint8_t padding[3]; // 61-62 (63?) 56 | } __attribute__((packed)); 57 | 58 | struct FeedbackReport { 59 | uint8_t type; // 0 60 | uint8_t flags; // 1 61 | uint8_t padding1[2]; // 2-3 62 | uint8_t rumble_right; // 4 63 | uint8_t rumble_left; // 5 64 | uint8_t led_color[3]; // 6-8 65 | uint8_t led_flash_on; // 9 66 | uint8_t led_flash_off; // 10 67 | uint8_t padding[21]; // 11-31 68 | } __attribute__((packed)); 69 | 70 | struct AuthPageSizeReport { 71 | uint8_t type; 72 | uint8_t u1; 73 | uint8_t size_challenge; 74 | uint8_t size_response; 75 | uint8_t u4[4]; // crc32? 76 | } __attribute__((packed)) ; 77 | 78 | struct AuthReport { 79 | uint8_t type; // 0 80 | uint8_t seq; // 1 81 | uint8_t page; // 2 82 | uint8_t sbz; // 3 83 | uint8_t data[56]; // 4-59 84 | uint32_t crc32; // 60-63 85 | } __attribute__((packed)); 86 | 87 | struct AuthStatusReport { 88 | uint8_t type; // 0 89 | uint8_t seq; // 1 90 | uint8_t status; // 2 0x10 = not ready, 0x00 = ready 91 | uint8_t padding[9]; // 3-11 92 | uint32_t crc32; // 12-15 93 | } __attribute__((packed)); 94 | 95 | class Controller : public api::Controller { 96 | public: 97 | enum : uint8_t { 98 | ROT_MAIN = 0, 99 | }; 100 | enum : uint8_t { 101 | KEY_SQR = 0, 102 | KEY_XRO, 103 | KEY_CIR, 104 | KEY_TRI, 105 | KEY_L1, 106 | KEY_R1, 107 | KEY_L2, 108 | KEY_R2, 109 | KEY_SHR, 110 | KEY_OPT, 111 | KEY_L3, 112 | KEY_R3, 113 | KEY_PS, 114 | KEY_TP, 115 | }; 116 | enum : uint8_t { 117 | AXIS_LX = 0, 118 | AXIS_LY, 119 | AXIS_RX, 120 | AXIS_RY, 121 | AXIS_L2, 122 | AXIS_R2, 123 | }; 124 | enum : uint8_t { 125 | IN_REPORT = 0x1, 126 | OUT_FEEDBACK = 0x5, 127 | SET_CHALLENGE = 0xf0, 128 | GET_RESPONSE, 129 | GET_AUTH_STATUS, 130 | GET_AUTH_PAGE_SIZE, 131 | }; 132 | Controller(api::Transport *backend); 133 | void begin() override; 134 | void update(); 135 | bool sendReport() override; 136 | bool sendReportBlocking() override; 137 | bool setRotary8Pos(uint8_t code, api::Rotary8Pos value) override; 138 | bool setKey(uint8_t code, bool action) override; 139 | bool setAxis(uint8_t code, uint8_t value) override; 140 | bool setAxis16(uint8_t code, uint16_t value) override; 141 | 142 | bool setKeyUniversal(api::Key code, bool action) override; 143 | bool setDpadUniversal(api::Dpad value) override; 144 | bool setStick(api::Stick index, uint8_t x, uint8_t y) override; 145 | bool setTrigger(api::Key code, uint8_t value) override; 146 | 147 | bool setTouchpad(uint8_t slot, uint8_t pos, bool pressed, uint8_t seq, uint16_t x, uint16_t y); 148 | bool setTouchEvent(uint8_t pos, bool pressed, uint16_t x=0, uint16_t y=0); 149 | bool finalizeTouchEvent(); 150 | void clearTouchEvents(); 151 | 152 | bool hasValidFeedback(); 153 | uint8_t getRumbleIntensityRight(); 154 | uint8_t getRumbleIntensityLeft(); 155 | uint32_t getLEDRGB(); 156 | uint8_t getLEDDelayOn(); 157 | uint8_t getLEDDelayOff(); 158 | 159 | private: 160 | InputReport report; 161 | FeedbackReport feedback; 162 | uint8_t currentTouchSeq; 163 | void incReportCtr(); 164 | static const uint8_t keyLookup[]; 165 | bool sendReport_(bool blocking); 166 | }; 167 | 168 | template 169 | class ControllerSOCD : public Controller, public api::SOCDBehavior, NS, WE>, public api::UnoJoyAPI> { 170 | public: 171 | ControllerSOCD(api::Transport *backend) : Controller(backend) {}; 172 | }; 173 | 174 | } // namespace ds4 175 | } // namespace rds4 176 | -------------------------------------------------------------------------------- /src/ds4/TransportTeensy.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** TransportDS4.cpp 3 | * Various transport back-ends for PS4 controllers. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "Transport.hpp" 9 | #include "utils/utils.hpp" 10 | 11 | #if defined(RDS4_ARDUINO) && defined(RDS4_TEENSY_3) 12 | 13 | #include 14 | #include 15 | 16 | // These should be in sync with the DS4 stub 17 | static const uint8_t TX_ENDPOINT = 1; 18 | static const uint8_t TX_SIZE = 64; 19 | static const uint8_t RX_ENDPOINT = 2; 20 | 21 | static const uint8_t MAX_PACKETS = 2; 22 | 23 | namespace rds4 { 24 | namespace ds4 { 25 | 26 | TransportTeensy *TransportTeensy::inst = nullptr; 27 | uint8_t *TransportTeensy::frBuffer = nullptr; 28 | uint32_t *TransportTeensy::frSize = nullptr; 29 | 30 | int TransportTeensy::frCallbackGet(void *setup_ptr, uint8_t *data, uint32_t *len) { 31 | auto setup = reinterpret_cast(setup_ptr); 32 | RDS4_DBG_PRINT("TransportDS4Teensy: setupcb: Set request received type="); 33 | RDS4_DBG_PHEX(setup->wValue); 34 | RDS4_DBG_PRINT("\n"); 35 | if (TransportTeensy::inst) { 36 | TransportTeensy::frBuffer = data; 37 | TransportTeensy::frSize = len; 38 | // returns 0 on success 39 | return static_cast(!TransportTeensy::inst->onGetReport(setup->wValue, setup->wIndex, setup->wLength)); 40 | } 41 | return 1; 42 | } 43 | 44 | int TransportTeensy::frCallbackSet(void *setup_ptr, uint8_t *data) { 45 | auto setup = reinterpret_cast(setup_ptr); 46 | RDS4_DBG_PRINT("TransportDS4Teensy: setupcb: Get request received type="); 47 | RDS4_DBG_PHEX(setup->wValue); 48 | RDS4_DBG_PRINT("\n"); 49 | if (TransportTeensy::inst) { 50 | TransportTeensy::frBuffer = data; 51 | TransportTeensy::frSize = nullptr; 52 | // returns 0 on success 53 | return static_cast(!TransportTeensy::inst->onSetReport(setup->wValue, setup->wIndex, setup->wLength)); 54 | } 55 | return 1; 56 | } 57 | 58 | bool TransportTeensy::onGetReport(uint16_t value, uint16_t index, uint16_t length) { 59 | bool result = false; 60 | // Report error if nobody responds to the request and return true as soon as somebody responds. 61 | result = FeatureConfigurator::onGetReport(value, index, length) or \ 62 | AuthenticationHandler::onGetReport(value, index, length); 63 | return result; 64 | } 65 | 66 | bool TransportTeensy::onSetReport(uint16_t value, uint16_t index, uint16_t length) { 67 | return AuthenticationHandler::onSetReport(value, index, length); 68 | } 69 | 70 | bool TransportTeensy::available() { 71 | // make sure the USB is initialized 72 | if (!usb_configuration) return false; 73 | // check for queued packets 74 | if (usb_rx_byte_count(RX_ENDPOINT) > 0) { 75 | return true; 76 | } else { 77 | return false; 78 | } 79 | } 80 | 81 | uint8_t TransportTeensy::send(const void *buf, uint8_t len) { 82 | usb_packet_t *pkt = NULL; 83 | 84 | // make sure the USB is initialized 85 | if (!usb_configuration) return 0; 86 | // check for queued packets 87 | if (usb_tx_packet_count(TX_ENDPOINT) < MAX_PACKETS) { 88 | pkt = usb_malloc(); 89 | if (pkt) { 90 | memcpy(pkt->buf, buf, len); 91 | pkt->len = len; 92 | usb_tx(TX_ENDPOINT, pkt); 93 | return len; 94 | } else { 95 | return 0; 96 | } 97 | } 98 | return 0; 99 | } 100 | 101 | uint8_t TransportTeensy::sendBlocking(const void *buf, uint8_t len) { 102 | usb_packet_t *pkt = NULL; 103 | uint32_t begin = millis(); 104 | 105 | // Blocking send 106 | while (1) { 107 | // make sure the USB is initialized 108 | if (!usb_configuration) return 0; 109 | // check for queued packets 110 | if (usb_tx_packet_count(TX_ENDPOINT) < MAX_PACKETS) { 111 | if ((pkt = usb_malloc())) { 112 | break; 113 | } 114 | } 115 | if (millis() - begin > 70) { 116 | RDS4_DBG_PRINTLN("send timeout"); 117 | return 0; 118 | } 119 | // Make sure any on-yield tasks got executed during waiting 120 | yield(); 121 | } 122 | memcpy(pkt->buf, buf, len); 123 | pkt->len = len; 124 | usb_tx(TX_ENDPOINT, pkt); 125 | return len; 126 | } 127 | 128 | uint8_t TransportTeensy::recv(void *buf, uint8_t len) { 129 | usb_packet_t *pkt = NULL; 130 | uint8_t actualSize; 131 | if ((pkt = usb_rx(RX_ENDPOINT))) { 132 | // Copy at most len bytes of data. Any remaining data will be discarded 133 | actualSize = (pkt->len > len) ? len : pkt->len; 134 | memcpy(buf, pkt->buf, actualSize); 135 | usb_free(pkt); 136 | return actualSize; 137 | } 138 | return 0; 139 | } 140 | 141 | uint8_t TransportTeensy::reply(const void *buf, uint8_t len) { 142 | if (TransportTeensy::frBuffer and TransportTeensy::frSize) { 143 | memcpy(TransportTeensy::frBuffer, buf, len); 144 | (*TransportTeensy::frSize) = len; 145 | return len; 146 | } 147 | return 0; 148 | } 149 | 150 | uint8_t TransportTeensy::check(void *buf, uint8_t len) { 151 | uint8_t actual; 152 | if (TransportTeensy::frBuffer) { 153 | actual = len; 154 | actual = actual > TX_SIZE ? TX_SIZE : actual; 155 | memcpy(buf, TransportTeensy::frBuffer, actual); 156 | return actual; 157 | } 158 | return 0; 159 | } 160 | } // ds4 161 | } // rds4 162 | #endif 163 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/ds4/Authenticator.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** AuthenticatorDS4.cpp 3 | * Authenticator for PS4 controllers via various back-ends. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "Authenticator.hpp" 9 | #include "utils/utils.hpp" 10 | 11 | #ifdef RDS4_LINUX 12 | // for memcpy(), etc. 13 | #include 14 | #endif 15 | 16 | namespace rds4 { 17 | namespace ds4 { 18 | 19 | #ifdef RDS4_AUTH_USBH 20 | 21 | uint8_t PS4USB2::OnInitSuccessful() { 22 | if (this->VIDPIDOK(::HIDUniversal::VID, ::HIDUniversal::PID)) { 23 | PS4Parser::Reset(); 24 | if (this->auth != nullptr) { 25 | this->auth->onStateChange(); 26 | } 27 | if (this->isLicensed()) { 28 | this->setLed(Blue); 29 | } 30 | } 31 | return 0; 32 | } 33 | 34 | void PS4USB2::registerAuthenticator(AuthenticatorUSBH *auth) { 35 | this->auth = auth; 36 | } 37 | 38 | AuthenticatorUSBH::AuthenticatorUSBH(PS4USB2 *donor) : donor(donor), statusOverrideEnabled(false), statusOverrideTransactionStartTime(0), statusOverrideInTransaction(false) { 39 | donor->registerAuthenticator(this); 40 | } 41 | 42 | bool AuthenticatorUSBH::available() { 43 | auto state = this->donor->connected(); 44 | return state; 45 | } 46 | 47 | bool AuthenticatorUSBH::needsReset() { 48 | return this->donor->isLicensed(); 49 | } 50 | 51 | bool AuthenticatorUSBH::reset() { 52 | return this->fitPageSize(); 53 | } 54 | 55 | bool AuthenticatorUSBH::fitPageSize() { 56 | if (this->donor->isLicensed()) { 57 | if (this->donor->GetReport(0, 0, 0x03, Controller::GET_AUTH_PAGE_SIZE, 8, this->scratchPad) == 0) { 58 | auto pgsize = (AuthPageSizeReport *) &(this->scratchPad); 59 | // basic sanity check 60 | if (pgsize->size_challenge > AuthenticatorUSBH::PAYLOAD_MAX or pgsize->size_response > AuthenticatorUSBH::PAYLOAD_MAX) { 61 | return false; 62 | } 63 | RDS4_DBG_PRINT("AuthenticatorDS4USBH: fit: nonce="); 64 | RDS4_DBG_PHEX(pgsize->size_challenge); 65 | RDS4_DBG_PRINT(" resp="); 66 | RDS4_DBG_PHEX(pgsize->size_response); 67 | RDS4_DBG_PRINT("\n"); 68 | this->challengePageSize = pgsize->size_challenge; 69 | this->responsePageSize = pgsize->size_response; 70 | return true; 71 | } else { 72 | RDS4_DBG_PRINTLN("AuthenticatorDS4USBH: fit: comm error"); 73 | return false; 74 | } 75 | } else { 76 | RDS4_DBG_PRINT("AuthenticatorDS4USBH: fit: is ds4"); 77 | this->challengePageSize = AuthenticatorUSBH::PAYLOAD_MAX; 78 | this->responsePageSize = AuthenticatorUSBH::PAYLOAD_MAX; 79 | return true; 80 | } 81 | } 82 | 83 | size_t AuthenticatorUSBH::writeChallengePage(uint8_t page, void *buf, size_t len) { 84 | auto authbuf = reinterpret_cast(&(this->scratchPad)); 85 | auto expected = this->getActualChallengePageSize(page); 86 | RDS4_DBG_PRINTLN("AuthenticatorDS4USBH: writing page"); 87 | // Insufficient data 88 | if (len < expected) { 89 | RDS4_DBG_PRINTLN("buf too small"); 90 | } 91 | 92 | authbuf->type = Controller::SET_CHALLENGE; 93 | // seq=0 seems to work on all controllers I tested. Leave this as-is for now. 94 | authbuf->seq = 1; 95 | authbuf->page = page; 96 | authbuf->sbz = 0; 97 | memcpy(&authbuf->data, buf, expected); 98 | // CRC32 is a must-have for official controller, not sure about licensed ones. 99 | authbuf->crc32 = utils::crc32(this->scratchPad, sizeof(*authbuf) - sizeof(authbuf->crc32)); 100 | if (this->donor->SetReport(0, 0, 0x03, Controller::SET_CHALLENGE, sizeof(*authbuf), this->scratchPad) != 0) { 101 | RDS4_DBG_PRINTLN("comm error"); 102 | return 0; 103 | } 104 | RDS4_DBG_PHEX(expected); 105 | RDS4_DBG_PRINTLN(" bytes written"); 106 | // Guitar Hero Dongle hack 107 | if (this->statusOverrideEnabled and this->endOfChallenge(page)) { 108 | RDS4_DBG_PRINTLN("gh hack timer start"); 109 | this->statusOverrideInTransaction = true; 110 | this->statusOverrideTransactionStartTime = millis(); 111 | } 112 | return expected; 113 | } 114 | 115 | size_t AuthenticatorUSBH::readResponsePage(uint8_t page, void *buf, size_t len) { 116 | auto authbuf = (AuthReport *) &(this->scratchPad); 117 | auto expected = this->getActualResponsePageSize(page); 118 | // Insufficient space for target buffer 119 | RDS4_DBG_PRINTLN("AuthenticatorDS4USBH: reading page"); 120 | if (len < expected) { 121 | RDS4_DBG_PRINTLN("buf too small"); 122 | return 0; 123 | } 124 | if (this->donor->GetReport(0, 0, 0x03, Controller::GET_RESPONSE, sizeof(*authbuf), this->scratchPad) != 0) { 125 | RDS4_DBG_PRINTLN("comm error"); 126 | return 0; 127 | } 128 | // Sanity check 129 | // (`page` has usage other than sanity check for, e.g., "security chip" authenticator.) 130 | if (authbuf->page != page) { 131 | RDS4_DBG_PRINT("page mismatch exp="); 132 | RDS4_DBG_PHEX(page); 133 | RDS4_DBG_PRINT(" act="); 134 | RDS4_DBG_PHEX(authbuf->page); 135 | RDS4_DBG_PRINT("\n"); 136 | return 0; 137 | } 138 | memcpy(buf, &authbuf->data, expected); 139 | RDS4_DBG_PHEX(expected); 140 | RDS4_DBG_PRINTLN(" bytes read"); 141 | // Guitar Hero Dongle hack 142 | if (this->statusOverrideEnabled and this->endOfResponse(page)) { 143 | RDS4_DBG_PRINTLN("gh hack end transaction"); 144 | this->statusOverrideInTransaction = false; 145 | } 146 | return expected; 147 | } 148 | 149 | api::AuthStatus AuthenticatorUSBH::getStatus() { 150 | auto rslbuf = (AuthStatusReport *) &(this->scratchPad); 151 | RDS4_DBG_PRINTLN("AuthenticatorDS4USBH: getting status"); 152 | if (this->statusOverrideEnabled) { 153 | RDS4_DBG_PRINTLN("gh hack enabled"); 154 | // wait for 2 seconds since the GH dongle takes about 2 seconds to sign the challenge 155 | if (this->statusOverrideInTransaction and millis() - this->statusOverrideTransactionStartTime > 2000) { 156 | return api::AuthStatus::OK; 157 | } else if (this->statusOverrideInTransaction) { 158 | return api::AuthStatus::BUSY; 159 | } else { 160 | return api::AuthStatus::NO_TRANSACTION; 161 | } 162 | } 163 | memset(rslbuf, 0, sizeof(*rslbuf)); 164 | if (this->donor->GetReport(0, 0, 0x03, Controller::GET_AUTH_STATUS, sizeof(*rslbuf), this->scratchPad) != 0) { 165 | RDS4_DBG_PRINTLN("comm err"); 166 | return api::AuthStatus::COMM_ERR; 167 | } 168 | switch (rslbuf->status) { 169 | case 0x00: 170 | 171 | RDS4_DBG_PRINTLN("ok"); 172 | return api::AuthStatus::OK; 173 | break; 174 | case 0x01: 175 | RDS4_DBG_PRINTLN("not in transaction"); 176 | return api::AuthStatus::NO_TRANSACTION; 177 | break; 178 | case 0x10: 179 | RDS4_DBG_PRINTLN("busy"); 180 | return api::AuthStatus::BUSY; 181 | break; 182 | default: 183 | RDS4_DBG_PRINT("unk err "); 184 | RDS4_DBG_PHEX(rslbuf->status); 185 | RDS4_DBG_PRINT("\n"); 186 | return api::AuthStatus::UNKNOWN_ERR; 187 | } 188 | } 189 | 190 | uint8_t AuthenticatorUSBH::getActualChallengePageSize(uint8_t page) { 191 | uint16_t remaining = AuthenticatorUSBH::CHALLENGE_SIZE - (uint16_t) this->challengePageSize * page; 192 | return remaining > this->challengePageSize ? this->challengePageSize : (uint8_t) remaining; 193 | } 194 | 195 | uint8_t AuthenticatorUSBH::getActualResponsePageSize(uint8_t page) { 196 | uint16_t remaining = AuthenticatorUSBH::RESPONSE_SIZE - (uint16_t) this->responsePageSize * page; 197 | return remaining > this->responsePageSize ? this->responsePageSize : (uint8_t) remaining; 198 | } 199 | 200 | void AuthenticatorUSBH::onStateChange() { 201 | RDS4_DBG_PRINTLN("AuthenticatorDS4USBH: Hotplug detected, re-fitting buffer"); 202 | this->fitPageSize(); 203 | this->statusOverrideEnabled = this->donor->isQuirky(); 204 | } 205 | 206 | #endif // RDS4_AUTH_USBH 207 | 208 | } // namespace ds4 209 | } // namespace rds4 210 | -------------------------------------------------------------------------------- /src/ds4/Controller.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** ControllerDS4.cpp 3 | * High-level report handling API for PS4 controllers. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #include "Controller.hpp" 9 | 10 | #include "utils/utils.hpp" 11 | 12 | #ifdef RDS4_LINUX 13 | // for memcpy(), etc. 14 | #include 15 | #endif 16 | 17 | namespace rds4 { 18 | namespace ds4 { 19 | 20 | const uint8_t Controller::keyLookup[static_cast(api::Key::_COUNT)] = { 21 | Controller::KEY_CIR, 22 | Controller::KEY_XRO, 23 | Controller::KEY_TRI, 24 | Controller::KEY_SQR, 25 | Controller::KEY_L1, 26 | Controller::KEY_R1, 27 | Controller::KEY_L2, 28 | Controller::KEY_R2, 29 | Controller::KEY_L3, 30 | Controller::KEY_R3, 31 | Controller::KEY_PS, 32 | Controller::KEY_SHR, 33 | Controller::KEY_OPT, 34 | }; 35 | 36 | Controller::Controller(api::Transport *backend) : api::Controller(backend), currentTouchSeq(0) { /* pass */ }; 37 | 38 | void Controller::begin() { 39 | this->backend->begin(); 40 | memset(&(this->report), 0, sizeof(this->report)); 41 | this->report.type = 0x01; 42 | // Center the D-Pad 43 | this->setRotary8Pos(0, api::Rotary8Pos::C); 44 | // Analog sticks 45 | memset(this->report.sticks, 0x80, sizeof(this->report.sticks)); 46 | // Touch 47 | this->clearTouchEvents(); 48 | // Ext TODO 49 | this->report.state_ext = 0x08; 50 | this->report.battery = 0xff; 51 | } 52 | 53 | void Controller::update() { 54 | if (this->backend->available()) { 55 | // TODO 56 | this->backend->recv(&(this->feedback), sizeof(this->feedback)); 57 | } 58 | } 59 | 60 | bool Controller::hasValidFeedback() { 61 | return this->feedback.type == 0x05; 62 | } 63 | 64 | inline bool Controller::sendReport_(bool blocking) { 65 | // https://www.psdevwiki.com/ps4/DS4-BT#0x11 66 | this->report.sensor_timestamp = ((millis() * 150) & 0xffff); 67 | uint8_t actual; 68 | if (blocking) { 69 | actual = this->backend->sendBlocking(&(this->report), sizeof(this->report)); 70 | } else { 71 | actual = this->backend->send(&(this->report), sizeof(this->report)); 72 | } 73 | if (actual != sizeof(this->report)) { 74 | return false; 75 | } else { 76 | this->incReportCtr(); 77 | if (this->report.tp_available_frame > 1) { 78 | // copy the last frame to the first slot and nuke the rest 79 | memcpy(&this->report.frames[0], &this->report.frames[this->report.tp_available_frame - 1], sizeof(this->report.frames[0])); 80 | for (uint8_t i=1; i<3; i++) { 81 | this->report.frames[i].seq = 0; 82 | this->report.frames[i].pos[0] = 1 << 7; 83 | this->report.frames[i].pos[1] = 1 << 7; 84 | } 85 | this->report.tp_available_frame = 1; 86 | } 87 | return true; 88 | } 89 | return false; 90 | } 91 | 92 | bool Controller::sendReport() { 93 | return this->sendReport_(false); 94 | } 95 | 96 | bool Controller::sendReportBlocking() { 97 | return this->sendReport_(true); 98 | } 99 | 100 | inline void Controller::incReportCtr() { 101 | this->report.buttons[2] += 4; 102 | } 103 | 104 | bool Controller::setRotary8Pos(uint8_t code, api::Rotary8Pos value) { 105 | if (code != 0) { 106 | return false; 107 | } 108 | this->report.buttons[0] ^= this->report.buttons[0] & 0x0f; 109 | this->report.buttons[0] |= static_cast(value); 110 | return true; 111 | } 112 | 113 | bool Controller::setKey(uint8_t code, bool action) { 114 | if (code < Controller::KEY_SQR or code > Controller::KEY_TP) { 115 | // key does not exist 116 | return false; 117 | } 118 | // Offset for button bitfield 119 | code += 4; 120 | // keycode structure: 000BBbbb 121 | // B: byte offset 122 | // b: bit offset 123 | if (action) { 124 | // pressed 125 | this->report.buttons[(code >> 3) & 3] |= 1 << (code & 7); 126 | } else { 127 | //released 128 | this->report.buttons[(code >> 3) & 3] &= ~(1 << (code & 7)); 129 | } 130 | return true; 131 | } 132 | 133 | bool Controller::setAxis(uint8_t code, uint8_t value) { 134 | if (code >= Controller::AXIS_LX and code <= Controller::AXIS_RY) { 135 | this->report.sticks[code] = value; 136 | } else if (code >= Controller::AXIS_L2 and code <= Controller::AXIS_R2) { 137 | this->report.triggers[code-4] = value; 138 | } else { 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | bool Controller::setAxis16(uint8_t code, uint16_t value) { 145 | // TODO accel and gyro support 146 | // No 16-bit axes at the moment, stub this 147 | return false; 148 | } 149 | 150 | bool Controller::setKeyUniversal(api::Key code, bool action) { 151 | if (code == api::Key::_COUNT) { 152 | return false; 153 | } 154 | auto ds4Code = this->keyLookup[static_cast(code)]; 155 | switch (code) { 156 | case api::Key::LTrigger: 157 | this->setAxis(Controller::AXIS_L2, action ? 0xff : 0x0); 158 | break; 159 | case api::Key::RTrigger: 160 | this->setAxis(Controller::AXIS_R2, action ? 0xff : 0x0); 161 | break; 162 | default: 163 | break; 164 | } 165 | this->setKey(ds4Code, action); 166 | return true; 167 | } 168 | 169 | bool Controller::setDpadUniversal(api::Dpad value) { 170 | return this->setDpad(0, value); 171 | } 172 | 173 | bool Controller::setStick(api::Stick index, uint8_t x, uint8_t y) { 174 | switch (index) { 175 | case api::Stick::L: 176 | this->setAxis(Controller::AXIS_LX, x); 177 | this->setAxis(Controller::AXIS_LY, y); 178 | break; 179 | case api::Stick::R: 180 | this->setAxis(Controller::AXIS_RX, x); 181 | this->setAxis(Controller::AXIS_RY, y); 182 | break; 183 | } 184 | return true; 185 | } 186 | 187 | bool Controller::setTrigger(api::Key code, uint8_t value) { 188 | auto ds4Code = this->keyLookup[static_cast(code)]; 189 | switch (code) { 190 | case api::Key::LTrigger: 191 | this->setAxis(Controller::AXIS_L2, value); 192 | break; 193 | case api::Key::RTrigger: 194 | this->setAxis(Controller::AXIS_R2, value); 195 | break; 196 | default: 197 | break; 198 | } 199 | this->setKey(ds4Code, value); 200 | return true; 201 | } 202 | 203 | bool Controller::setTouchpad(uint8_t slot, uint8_t pos, bool pressed, uint8_t seq, uint16_t x, uint16_t y) { 204 | // TODO Bluetooth has different event buffer size 205 | if (slot >= (sizeof(this->report.frames) / sizeof(TouchFrame)) || pos > 1) { 206 | return false; 207 | } 208 | this->report.frames[slot].pos[pos] = ((y & 0xfff) << 20) | ((x & 0xfff) << 8) | ((!pressed) << 7) | (seq & 0x7f); 209 | this->report.frames[slot].seq++; 210 | return true; 211 | } 212 | 213 | bool Controller::setTouchEvent(uint8_t pos, bool pressed, uint16_t x, uint16_t y) { 214 | return this->setTouchpad(this->report.tp_available_frame, pos, pressed, this->currentTouchSeq, x, y); 215 | } 216 | 217 | bool Controller::finalizeTouchEvent() { 218 | if (this->report.tp_available_frame < 3) { 219 | this->report.tp_available_frame++; 220 | this->currentTouchSeq++; 221 | return true; 222 | } else { 223 | return false; 224 | } 225 | } 226 | 227 | void Controller::clearTouchEvents() { 228 | this->report.tp_available_frame = 0; 229 | for (uint8_t i=0; i<3; i++) { 230 | this->report.frames[i].seq = 0; 231 | this->report.frames[i].pos[0] = 1 << 7; 232 | this->report.frames[i].pos[1] = 1 << 7; 233 | } 234 | } 235 | 236 | uint8_t Controller::getRumbleIntensityRight() { 237 | return this->feedback.rumble_right; 238 | } 239 | 240 | uint8_t Controller::getRumbleIntensityLeft() { 241 | return this->feedback.rumble_left; 242 | } 243 | 244 | uint8_t Controller::getLEDDelayOn() { 245 | return this->feedback.led_flash_on; 246 | } 247 | 248 | uint8_t Controller::getLEDDelayOff() { 249 | return this->feedback.led_flash_off; 250 | } 251 | 252 | uint32_t Controller::getLEDRGB() { 253 | // LED data is in the format of 0x00RRGGBB, same as the format Adafruit_NeoPixel accepts. 254 | return (this->feedback.led_color[0] << 16 | this->feedback.led_color[1] << 8 | this->feedback.led_color[2]); 255 | } 256 | 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/api/internals.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** internals.hpp 3 | * RDS4Reboot API definitions. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "utils/platform.hpp" 11 | 12 | // TODO: More documentation 13 | 14 | namespace rds4 { 15 | namespace api { 16 | 17 | /** Base class for a Transport class. 18 | * A Transport object should be able to handle high-level report transferring 19 | * (high-level as hiding details about device discovery and initialization, if 20 | * applicable), as well as be able to dispatch feature requests. 21 | * It should know the protocol of the device to the level that 22 | * allows it to correctly dispatch data to the right recipient (host device or 23 | * other classes on this device). 24 | * 25 | * Currently the size of packet is limited to 255 bytes per packet since even 26 | * full DS4 report is way less than 255 bytes long (without audio). This may 27 | * change in the future when demanded. 28 | */ 29 | class Transport { 30 | public: 31 | /** Start transport backend. Does nothing by default. */ 32 | virtual void begin() { }; 33 | /** Check if there are packets that are available for receiving. 34 | * 35 | * @return `true` if available, `false` if no packet is available. 36 | */ 37 | virtual bool available() = 0; 38 | /** Send data through the link. This method shall be implemented as 39 | * non-blocking, meaning it immediately returns if data transmission 40 | * is not possible. 41 | * 42 | * @param The buffer that contains data to send. 43 | * @param The length of the supplied buffer. 44 | * @return The number of actual bytes sent. 45 | */ 46 | virtual uint8_t send(const void *buf, uint8_t len) = 0; 47 | /** Receive data from the link. This method shall be implemented as 48 | * non-blocking, meaning it immediately returns if there is no data to 49 | * receive or any error occurred. 50 | * 51 | * @param The buffer for receiving the data. 52 | * @param The length of the supplied buffer. 53 | * @return The number of actual bytes received. 54 | */ 55 | virtual uint8_t recv(void *buf, uint8_t len) = 0; 56 | /** Send data through the link (blocking). The blocking timeout is 57 | * implementation-specific 58 | * 59 | * @param The buffer that contains data to send. 60 | * @param The length of the supplied buffer. 61 | * @return The number of actual bytes sent. 62 | */ 63 | virtual uint8_t sendBlocking(const void *buf, uint8_t len) = 0; 64 | protected: 65 | // Feature request API. Intended for internal use. If developing CRTPs 66 | // (for auth, etc.) one must set the CRTP class as friend in order to 67 | // use these. 68 | 69 | /** Reply to a feature request. For use within the implementation 70 | * classes only. 71 | * 72 | * @param The buffer that contains data to send. 73 | * @param The length of the supplied buffer. 74 | * @return The number of actual bytes sent. 75 | */ 76 | virtual uint8_t reply(const void *buf, uint8_t len) = 0; 77 | /** Checkout the supplied data from a feature request. For use within 78 | * the implementation classes only. 79 | * 80 | * @param The buffer for receiving the data. 81 | * @param The length of the supplied buffer. 82 | * @return The number of actual bytes received. 83 | */ 84 | virtual uint8_t check(void *buf, uint8_t len) = 0; 85 | virtual bool onGetReport(uint16_t value, uint16_t index, uint16_t length) = 0; 86 | virtual bool onSetReport(uint16_t value, uint16_t index, uint16_t length) = 0; 87 | }; // TransportBase 88 | 89 | enum class AuthStatus : uint8_t { 90 | OK, 91 | UNKNOWN_ERR, 92 | COMM_ERR, 93 | BUSY, 94 | NO_TRANSACTION, 95 | }; 96 | 97 | class Authenticator { 98 | public: 99 | /** Start authenticator. Does nothing by default. */ 100 | virtual void begin() { }; 101 | /** Check if the authenticator is connected and ready. 102 | * 103 | * @return `true` if the authenticator is connected and ready. 104 | */ 105 | virtual bool available() = 0; 106 | /** Check if the page size of challenge/response can be determined 107 | * automatically (e.g.\ using licensedResetAuth). 108 | * 109 | * @return `true` if the authenticator supports this. 110 | */ 111 | virtual bool canFitPageSize() = 0; 112 | /** Report if the page size of challenge/response can be manually 113 | * set to a specific value (e.g.\ direct access to the "security 114 | * chip"). 115 | * 116 | * @return `true` if the authenticator supports this. 117 | */ 118 | virtual bool canSetPageSize() = 0; 119 | /** Return true if the authenticator needs reset between authentications 120 | * 121 | * @return `true` if the authenticator needs reset. 122 | */ 123 | virtual bool needsReset() = 0; 124 | /** Automatically determine the size of page size. Only available on 125 | * authenticators that supports this feature 126 | * 127 | * @return `true` if the operation was successful. 128 | */ 129 | virtual bool fitPageSize() = 0; 130 | /** Manually set the challenge page size. 131 | * On authenticators that do not support this operation, this should do 132 | * nothing and return `false`. 133 | * 134 | * @param The page size. PS4 licensed controller "security chip" only 135 | * allows page size < 256 bytes. Therefore it currently uses 136 | * uint8_t. This might change when demanded. 137 | * @return `true` if the operation was successful. 138 | */ 139 | virtual bool setChallengePageSize(uint8_t size) { 140 | this->challengePageSize = size; 141 | /* Always returns true here. On authenticators that does not support 142 | * manual page size adjustments this should be overridden and return 143 | * false. */ 144 | return true; 145 | } 146 | /** Manually set the response page size. 147 | * On authenticators that do not support this operation, this should do 148 | * nothing and return `false`. 149 | * 150 | * @param The page size. PS4 licensed controller "security chip" only 151 | * allows page size < 256 bytes. Therefore it currently uses 152 | * uint8_t. This might change when demanded. 153 | * @return `true` if the operation was successful. 154 | */ 155 | virtual bool setResponsePageSize(uint8_t size) { 156 | this->responsePageSize = size; 157 | /* Always returns true here. On authenticators that does not support 158 | * manual page size adjustments this should be overridden and return 159 | * false. */ 160 | return true; 161 | } 162 | /** Determine if a page is the last page of challenge. Can be used as a 163 | * cue for state transition of authentication handlers. If not 164 | * applicable, always return `false` 165 | * 166 | * @param The page to be determined. 167 | * @return `true` if the page is the last page. 168 | */ 169 | virtual bool endOfChallenge(uint8_t page) = 0; 170 | /** Determine if a page is the last page of response. Can be used as a 171 | * cue for state transition of authentication handlers. If not 172 | * applicable, always return `false` 173 | * 174 | * @param The page to be determined. 175 | * @return `true` if the page is the last page. 176 | */ 177 | virtual bool endOfResponse(uint8_t page) = 0; 178 | /** Reset the authenticator. If not applicable, always return `true`. 179 | * 180 | * @return `true` if reset is successful. 181 | */ 182 | virtual bool reset() = 0; 183 | /** Write challenge page to the authenticator. 184 | * 185 | * @param Page number. 186 | * @param A pointer to the source buffer. 187 | * @param Length of the source buffer (for sanity checks). 188 | * @return Actual number of bytes copied. 189 | */ 190 | virtual size_t writeChallengePage(uint8_t page, void *buf, size_t len) = 0; 191 | /** Read response page from the authenticator to a specified buffer. 192 | * 193 | * @param Page number. 194 | * @param A pointer to the destination buffer. 195 | * @param Length of the destination buffer (for sanity checks). 196 | * @return Actual number of bytes copied. 197 | */ 198 | virtual size_t readResponsePage(uint8_t page, void *buf, size_t len) = 0; 199 | virtual uint8_t getChallengePageSize() { 200 | return this->challengePageSize; 201 | } 202 | virtual uint8_t getResponsePageSize() { 203 | return this->responsePageSize; 204 | } 205 | /** Check the current status of authentication. 206 | * 207 | * @return Current authentication status. 208 | * @see AuthStatus 209 | */ 210 | virtual AuthStatus getStatus() = 0; 211 | 212 | protected: 213 | uint8_t challengePageSize; 214 | uint8_t responsePageSize; 215 | }; // AuthenticatorBase 216 | 217 | 218 | /** Optional authentication feature for a Transport object. Should be a 219 | * mixin/wrapper (CRTP) of a specific implementation of Transport class. 220 | */ 221 | class AuthenticationHandler { 222 | public: 223 | AuthenticationHandler(Authenticator *auth) { 224 | this->auth = auth; 225 | } 226 | virtual void begin() = 0; 227 | /** Check for updates on authentication. 228 | * Can be called in a super-loop or called on-demand on an (RT)OS (when e.g. there is an update event on the host side). 229 | */ 230 | virtual void update() = 0; 231 | 232 | protected: 233 | virtual bool onGetReport(uint16_t value, uint16_t index, uint16_t length) = 0; 234 | virtual bool onSetReport(uint16_t value, uint16_t index, uint16_t length) = 0; 235 | Authenticator *auth; 236 | }; // AuthenticationHandlerBase 237 | 238 | enum class Rotary8Pos : uint8_t { 239 | N = 0, 240 | NE, 241 | E, 242 | SE, 243 | S, 244 | SW, 245 | W, 246 | NW, 247 | C = 8, 248 | NUL = 8, 249 | }; 250 | 251 | enum class Key : uint8_t { 252 | A = 0, B, X, Y, LButton, RButton, LTrigger, RTrigger, LStick, RStick, 253 | Home, Select, Start, _COUNT, 254 | }; 255 | 256 | enum class Stick : uint8_t { 257 | L = 0, R, 258 | }; 259 | 260 | using Dpad = Rotary8Pos; 261 | 262 | class Controller { 263 | public: 264 | Controller(Transport *backend) { 265 | this->backend = backend; 266 | } 267 | /** Initialize the report buffer and transport back-end. */ 268 | virtual void begin() = 0; 269 | /** Send the current report (non-blocking). 270 | * @return `true` if successful. 271 | */ 272 | virtual bool sendReport() = 0; 273 | /** Send the current report (blocking). 274 | * @return `true` if successful. 275 | */ 276 | virtual bool sendReportBlocking() = 0; 277 | /** Set state for a rotary encoder (8 positions with null state). 278 | * @param index of the encoder. This is implementation-specific. 279 | * @param value of the encoder. 280 | * @return `true` if successful. 281 | * @see Rotary8Pos 282 | */ 283 | virtual bool setRotary8Pos(uint8_t code, Rotary8Pos value) = 0; 284 | /** Set state for a push button/key. 285 | * @param the key code. This is implementation-specific. 286 | * @param `true` if the key is pressed. 287 | * @return `true` if successful. 288 | */ 289 | virtual bool setKey(uint8_t code, bool action) = 0; 290 | /** Set state for an analog axis (8-bit). 291 | * @param the index of axis. This is implementation-specific. 292 | * @param the value of the axis. 293 | * @return `true` if successful. 294 | * @see setAxis16() 295 | */ 296 | virtual bool setAxis(uint8_t code, uint8_t value) = 0; 297 | /** Set state for an analog axis (16-bit). 298 | * @param the index of axis. This is implementation-specific. 299 | * @param the value of the axis. 300 | * @return `true` if successful. 301 | * @see setAxis() 302 | */ 303 | virtual bool setAxis16(uint8_t code, uint16_t value) = 0; 304 | 305 | // Universal APIs 306 | virtual bool setKeyUniversal(Key code, bool action) = 0; 307 | virtual bool setDpadUniversal(Dpad value) = 0; 308 | virtual bool setStick(Stick index, uint8_t x, uint8_t y) = 0; 309 | virtual bool setTrigger(Key code, uint8_t value) = 0; 310 | 311 | // Helpers 312 | /** Set state for a D-pad. Equivalent to setRotary8Pos(). 313 | * @param index of the D-pad. This is implementation-specific. 314 | * @param value of the D-pad. 315 | * @return `true` if successful. 316 | * @see Dpad8Pos 317 | * @see setRotary8Pos() 318 | */ 319 | inline bool setDpad(uint8_t code, Dpad value) { 320 | return this->setRotary8Pos(code, value); 321 | } 322 | /** Set a key/push button to pressed state. 323 | * @param the key code. 324 | * @return `true` if successful. 325 | * @see releaseKey() 326 | * @see setKey() 327 | */ 328 | inline bool pressKey(uint8_t code) { 329 | return this->setKey(code, true); 330 | } 331 | /** Set a key/push button to released state. 332 | * @param the key code. 333 | * @return `true` if successful. 334 | * @see pressKey() 335 | * @see setKey() 336 | */ 337 | inline bool releaseKey(uint8_t code) { 338 | return this->setKey(code, false); 339 | } 340 | 341 | inline bool pressKeyUniversal(Key code) { 342 | return this->setKeyUniversal(code, true); 343 | } 344 | 345 | inline bool releaseKeyUniversal(Key code) { 346 | return this->setKeyUniversal(code, false); 347 | } 348 | 349 | protected: 350 | Transport *backend; 351 | }; // ControllerBase 352 | 353 | /** A simple SOCD cleaner mixin. Can be attached to a ControllerBase-compatible 354 | * class by creating a new subclass of it as `C` and inheriting 355 | * `SOCDBehavior`. 356 | * The exact behavior of the cleaner is specified via template parameter `NS` 357 | * (both Up (north) and Down (south) are pressed) and `WE` (both Left (west) 358 | * and Right (east) are pressed). When `NS` is set to either `Dpad8Pos::N` or 359 | * `Dpad8Pos::S`, the cleaner only keeps Up or Down press while discards the 360 | * opposite press. 361 | * Similarily, when `WE` is set to either `Dpad8Pos::W` or `Dpad8Pos::E`, only 362 | * Left or Right will be kept. Any other parameters, including `Dpad8Pos::C` 363 | * will be treated as neutral and the cleaner removes presses of both 364 | * directions. 365 | */ 366 | template 367 | class SOCDBehavior { 368 | public: 369 | /** Do SOCD cleaning and set state for a D-pad. 370 | * @param index of the D-pad. This is implementation-specific. 371 | * @param True if the Up (North) button is pressed. 372 | * @param True if the Right (East) button is pressed. 373 | * @param True if the Down (South) button is pressed. 374 | * @param True if the Left (West) button is pressed. 375 | * @return `true` if successful. 376 | * @see Dpad8Pos 377 | */ 378 | bool setDpadSOCD(uint8_t code, bool n, bool e, bool s, bool w) { 379 | auto *cobj = static_cast(this); 380 | return cobj->setDpad(code, this->doCleaning(n, e, s, w)); 381 | } 382 | bool setDpadUniversalSOCD(bool n, bool e, bool s, bool w) { 383 | auto *cobj = static_cast(this); 384 | return cobj->setDpadUniversal(this->doCleaning(n, e, s, w)); 385 | } 386 | private: 387 | Dpad doCleaning(bool n, bool e, bool s, bool w) { 388 | auto pos = Dpad::C; 389 | // Clean the input 390 | if (n and s) { 391 | switch (NS) { 392 | case Dpad::N: 393 | s = false; 394 | break; 395 | case Dpad::S: 396 | n = false; 397 | break; 398 | case Dpad::C: 399 | default: 400 | s = false; 401 | n = false; 402 | } 403 | } 404 | if (w and e) { 405 | switch (WE) { 406 | case Dpad::W: 407 | e = false; 408 | break; 409 | case Dpad::E: 410 | w = false; 411 | break; 412 | case Dpad::C: 413 | default: 414 | e = false; 415 | w = false; 416 | } 417 | } 418 | // Map cleaned input to D-Pad positions 419 | if (n) { 420 | // NE 421 | if (e) { 422 | pos = Dpad::NE; 423 | // NW 424 | } else if (w) { 425 | pos = Dpad::NW; 426 | // N only 427 | } else { 428 | pos = Dpad::N; 429 | } 430 | } else if (s) { 431 | // SE 432 | if (e) { 433 | pos = Dpad::SE; 434 | // SW 435 | } else if (w) { 436 | pos = Dpad::SW; 437 | // S only 438 | } else { 439 | pos = Dpad::S; 440 | } 441 | // W only 442 | } else if (w) { 443 | pos = Dpad::W; 444 | // E only 445 | } else if (e) { 446 | pos = Dpad::E; 447 | // Centered/neutral 448 | } else { 449 | pos = Dpad::C; 450 | } 451 | return pos; 452 | } 453 | }; // SOCDBehavior 454 | } // namespace api 455 | } // namespace rds4 456 | -------------------------------------------------------------------------------- /src/ds4/Transport.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | /** TransportDS4.hpp 3 | * Various transport back-ends for PS4 controllers. 4 | * 5 | * Copyright 2019 dogtopus 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "utils/platform.hpp" 11 | #include "api/internals.hpp" 12 | #include "utils/utils.hpp" 13 | 14 | #if defined(RDS4_ARDUINO) && defined(RDS4_TEENSY_3) 15 | #include 16 | #endif 17 | 18 | #include "Controller.hpp" 19 | 20 | #ifdef RDS4_LINUX 21 | #include 22 | #endif 23 | 24 | namespace rds4 { 25 | namespace ds4 { 26 | 27 | enum class DS4AuthState : uint8_t { 28 | IDLE, 29 | NONCE_RECEIVED, 30 | WAIT_NONCE, 31 | WAIT_RESP, 32 | POLL_RESP, 33 | RESP_BUFFERED, 34 | RESP_UNLOADED, 35 | ERROR, 36 | }; 37 | 38 | /** A wrapper for TransportDS4 family classes that adds ability to respond to 39 | * authentication requests. 40 | */ 41 | template 42 | class AuthenticationHandler : public api::AuthenticationHandler { 43 | public: 44 | typedef void (*StateChangeCallback)(void); 45 | AuthenticationHandler(api::Authenticator *auth) : api::AuthenticationHandler(auth), 46 | state(DS4AuthState::IDLE), 47 | page(-1), 48 | seq(0), 49 | scratchPad{0}, 50 | _notifyStateChange(nullptr) {} 51 | void begin() override { 52 | this->auth->begin(); 53 | } 54 | void update() override; 55 | void attachStateChangeCallback(StateChangeCallback callback) { 56 | _notifyStateChange = callback; 57 | } 58 | 59 | protected: 60 | DS4AuthState state; 61 | int8_t page; 62 | uint8_t seq; 63 | // Maximum size for challenge/response 64 | uint8_t scratchPad[64]; 65 | bool onGetReport(uint16_t value, uint16_t index, uint16_t length) override; 66 | bool onSetReport(uint16_t value, uint16_t index, uint16_t length) override; 67 | void notifyStateChange(void) { 68 | if (this->_notifyStateChange != nullptr) { 69 | (*this->_notifyStateChange)(); 70 | } 71 | } 72 | StateChangeCallback _notifyStateChange; 73 | }; 74 | 75 | template 76 | void AuthenticationHandler::update() { 77 | auto *pkt = reinterpret_cast(&(this->scratchPad)); 78 | if (this->auth->available()) { 79 | switch (this->state) { 80 | // Got nonce (challenge) from host 81 | case DS4AuthState::NONCE_RECEIVED: 82 | RDS4_DBG_PRINTLN("AuthenticationHandlerDS4: consuming nonce"); 83 | // If this is the first page and the auth device is resettable, reset it 84 | if (this->page == 0) { 85 | // Use auto fit if available, otherwise manually set to maximum size if possible 86 | // TODO verify buffer size 0x38 on A7105 87 | if (this->auth->canSetPageSize() and not this->auth->canFitPageSize()) { 88 | RDS4_DBG_PRINTLN("set pagesize to maximum"); 89 | this->auth->setChallengePageSize(sizeof(pkt->data)); 90 | this->auth->setResponsePageSize(sizeof(pkt->data)); 91 | } 92 | // Reset also fits the buffer size if possible 93 | if (this->auth->needsReset()) { 94 | RDS4_DBG_PRINTLN("reset"); 95 | this->auth->reset(); 96 | // Otherwise trigger auto fit explicitly 97 | } else if (this->auth->canFitPageSize()) { 98 | RDS4_DBG_PRINTLN("auto fit"); 99 | this->auth->fitPageSize(); 100 | } 101 | } 102 | // Submit the page to auth device 103 | if (this->auth->writeChallengePage(this->page, &(pkt->data), sizeof(pkt->data))) { 104 | if (this->auth->endOfChallenge(this->page)) { 105 | RDS4_DBG_PRINTLN("last cpage"); 106 | this->state = DS4AuthState::WAIT_RESP; 107 | } else { 108 | // wait for more 109 | this->state = DS4AuthState::WAIT_NONCE; 110 | } 111 | } else { 112 | RDS4_DBG_PRINTLN("write err"); 113 | this->state = DS4AuthState::ERROR; 114 | } 115 | break; 116 | case DS4AuthState::POLL_RESP: { 117 | auto as = this->auth->getStatus(); 118 | RDS4_DBG_PRINTLN("AuthenticationHandlerDS4: checking auth status"); 119 | switch (as) { 120 | // Authenticator is ready to answer the challenge. 121 | case api::AuthStatus::OK: { 122 | // buffer the first response packet 123 | auto *pkt = reinterpret_cast(&(this->scratchPad)); 124 | RDS4_DBG_PRINTLN("ok"); 125 | pkt->type = Controller::GET_RESPONSE; 126 | pkt->seq = this->seq; 127 | pkt->page = 0; 128 | pkt->crc32 = strictCRC ? utils::crc32(this->scratchPad, sizeof(*pkt) - sizeof(pkt->crc32)) : 0; 129 | this->page = 0; 130 | 131 | if (this->auth->readResponsePage(this->page, &(pkt->data), sizeof(pkt->data))) { 132 | this->state = DS4AuthState::RESP_BUFFERED; 133 | } else { 134 | RDS4_DBG_PRINTLN("err"); 135 | this->state = DS4AuthState::ERROR; 136 | } 137 | break; 138 | } 139 | // Authenticator is busy, wait for some more time. 140 | case api::AuthStatus::BUSY: 141 | // Wait until host polls again. 142 | RDS4_DBG_PRINTLN("busy"); 143 | this->state = DS4AuthState::WAIT_RESP; 144 | break; 145 | // Something went wrong 146 | default: 147 | RDS4_DBG_PRINTLN("err"); 148 | this->state = DS4AuthState::ERROR; 149 | break; 150 | } 151 | break; 152 | } 153 | case DS4AuthState::RESP_UNLOADED: { 154 | RDS4_DBG_PRINTLN("AuthenticationHandlerDS4: producing resp"); 155 | if (this->auth->endOfResponse(this->page)) { 156 | RDS4_DBG_PRINTLN("last rpage"); 157 | this->state = DS4AuthState::IDLE; 158 | this->page = -1; 159 | break; 160 | } 161 | auto *pkt = reinterpret_cast(&(this->scratchPad)); 162 | RDS4_DBG_PRINTLN("next"); 163 | this->page++; 164 | pkt->type = Controller::GET_RESPONSE; 165 | pkt->seq = this->seq; 166 | pkt->page = this->page; 167 | pkt->crc32 = strictCRC ? utils::crc32(this->scratchPad, sizeof(*pkt) - sizeof(pkt->crc32)) : 0; 168 | // clear the buffer just in case 169 | memset(&(pkt->data), 0, sizeof(pkt->data)); 170 | if (this->auth->readResponsePage(this->page, &(pkt->data), sizeof(pkt->data))) { 171 | this->state = DS4AuthState::RESP_BUFFERED; 172 | } else { 173 | RDS4_DBG_PRINTLN("err"); 174 | this->state = DS4AuthState::ERROR; 175 | } 176 | break; 177 | } 178 | case DS4AuthState::IDLE: 179 | default: 180 | break; 181 | } 182 | } 183 | } 184 | 185 | template 186 | bool AuthenticationHandler::onSetReport(uint16_t value, uint16_t index, uint16_t length) { 187 | TR *tr = static_cast(this); 188 | if ((value >> 8) == 0x03) { 189 | switch (value & 0xff) { 190 | case Controller::SET_CHALLENGE: { 191 | auto *pkt = reinterpret_cast(&(this->scratchPad)); 192 | RDS4_DBG_PRINTLN("AuthenticationHandlerDS4: SET_CHALLENGE"); 193 | if (tr->check(pkt, sizeof(*pkt)) != sizeof(*pkt)) { 194 | RDS4_DBG_PRINTLN("wrong size"); 195 | return false; 196 | } 197 | // sanity check 198 | if (pkt->type != Controller::SET_CHALLENGE) { 199 | RDS4_DBG_PRINT("wrong magic "); 200 | RDS4_DBG_PHEX(pkt->type); 201 | RDS4_DBG_PRINT("\n"); 202 | return false; 203 | } 204 | // Page 0 acts like a reset 205 | if (pkt->page == 0) { 206 | RDS4_DBG_PRINTLN("reset"); 207 | this->page = 0; 208 | this->seq = pkt->seq; 209 | this->state = DS4AuthState::NONCE_RECEIVED; 210 | this->notifyStateChange(); 211 | } else if (this->state == DS4AuthState::WAIT_NONCE) { 212 | // If currently waiting for more nonce, make sure the order is consistent. Otherwise go to error state. 213 | if (pkt->seq == this->seq and pkt->page == this->page + 1) { 214 | RDS4_DBG_PRINTLN("cont"); 215 | this->page++; 216 | this->state = DS4AuthState::NONCE_RECEIVED; 217 | this->notifyStateChange(); 218 | this->notifyStateChange(); 219 | } else { 220 | RDS4_DBG_PRINTLN("ooo"); 221 | this->state = DS4AuthState::ERROR; 222 | } 223 | } else { 224 | RDS4_DBG_PRINTLN("err"); 225 | this->page = -1; 226 | this->state = DS4AuthState::ERROR; 227 | } 228 | break; 229 | } 230 | default: 231 | // unknown cmd, stall 232 | return false; 233 | } 234 | } 235 | return true; 236 | } 237 | 238 | template 239 | bool AuthenticationHandler::onGetReport(uint16_t value, uint16_t index, uint16_t length) { 240 | TR *tr = static_cast(this); 241 | if ((value >> 8) == 0x03) { 242 | switch (value & 0xff) { 243 | case Controller::GET_RESPONSE: 244 | // Will be processed in update() 245 | if (this->state == DS4AuthState::RESP_BUFFERED) { 246 | this->state = DS4AuthState::RESP_UNLOADED; 247 | this->notifyStateChange(); 248 | } else { 249 | // TODO do we need to clean the buffer? 250 | this->state = DS4AuthState::ERROR; 251 | } 252 | tr->reply(&scratchPad, sizeof(AuthReport)); 253 | break; 254 | case Controller::GET_AUTH_STATUS: { 255 | // Use a separate buffer here to make sure we don't overwrite buffered response. 256 | AuthStatusReport pkt = {0}; 257 | pkt.type = Controller::GET_AUTH_STATUS; 258 | pkt.seq = this->seq; 259 | pkt.crc32 = strictCRC ? utils::crc32(&pkt, sizeof(pkt) - sizeof(pkt.crc32)) : 0; 260 | switch (this->state) { 261 | // Already responding to the host (aka. ready) 262 | case DS4AuthState::RESP_BUFFERED: 263 | case DS4AuthState::RESP_UNLOADED: 264 | pkt.status = 0x00; // ok 265 | break; 266 | // Still waiting for the auth device 267 | case DS4AuthState::WAIT_RESP: 268 | case DS4AuthState::POLL_RESP: 269 | pkt.status = 0x10; // busy 270 | // notify the other end that the host polled us 271 | this->state = DS4AuthState::POLL_RESP; 272 | this->notifyStateChange(); 273 | break; 274 | // Something went wrong or not in a transaction 275 | case DS4AuthState::ERROR: 276 | pkt.status = 0xf0; 277 | break; 278 | default: 279 | pkt.status = 0x01; // not in a transaction 280 | break; 281 | } 282 | tr->reply(&pkt, sizeof(pkt)); 283 | break; 284 | } 285 | case Controller::GET_AUTH_PAGE_SIZE: { 286 | auto *ps = reinterpret_cast(&(this->scratchPad)); 287 | memset(ps, 0, sizeof(*ps)); 288 | ps->type = Controller::GET_AUTH_PAGE_SIZE; 289 | ps->size_challenge = this->auth->getChallengePageSize(); 290 | ps->size_response = this->auth->getResponsePageSize(); 291 | tr->reply(ps, sizeof(*ps)); 292 | break; 293 | } 294 | default: 295 | // unknown cmd, stall 296 | return false; 297 | } 298 | } 299 | return true; 300 | } 301 | 302 | template 303 | class FeatureConfigurator { 304 | protected: 305 | static const uint8_t RESPONSE[48]; 306 | bool onGetReport(uint16_t value, uint16_t index, uint16_t length); 307 | }; 308 | 309 | template 310 | const uint8_t FeatureConfigurator::RESPONSE[] PROGMEM = { 311 | 0x03, 0x21, 0x27, 0x04, 0x4d, 0x00, 0x2c, 0x56, 312 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 313 | 0x00, 0x00, 0x0d, 0x0d, 0x00, 0x00, 0x00, 0x00, 314 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 315 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 316 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 317 | }; 318 | 319 | template 320 | bool FeatureConfigurator::onGetReport(uint16_t value, uint16_t index, uint16_t length) { 321 | TR *tr = static_cast(this); 322 | if (value == 0x0303) { 323 | tr->reply(&FeatureConfigurator::RESPONSE, sizeof(FeatureConfigurator::RESPONSE)); 324 | return true; 325 | } else { 326 | return false; 327 | } 328 | } 329 | 330 | #if defined(RDS4_ARDUINO) && defined(RDS4_TEENSY_3) 331 | typedef struct { 332 | union { 333 | struct { 334 | uint8_t bmRequestType; 335 | uint8_t bmRequest; 336 | }; 337 | uint16_t wRequestAndType; 338 | }; 339 | uint16_t wValue; 340 | uint16_t wIndex; 341 | uint16_t wLength; 342 | } usb_setup_pkt_t; 343 | 344 | /** Tansport backend for teensy 3.x/LC boards. Requires patched teensyduino core library */ 345 | class TransportTeensy; 346 | class TransportTeensy : public api::Transport, public AuthenticationHandler, public FeatureConfigurator { 347 | public: 348 | TransportTeensy(api::Authenticator *auth) : AuthenticationHandler(auth) { 349 | TransportTeensy::inst = this; 350 | usb_ds4stub_on_get_report = &(TransportTeensy::frCallbackGet); 351 | usb_ds4stub_on_set_report = &(TransportTeensy::frCallbackSet); 352 | } 353 | void begin() override { 354 | AuthenticationHandler::begin(); 355 | } 356 | bool available() override; 357 | uint8_t send(const void *buf, uint8_t len) override; 358 | uint8_t sendBlocking(const void *buf, uint8_t len) override; 359 | uint8_t recv(void *buf, uint8_t len) override; 360 | 361 | protected: 362 | friend class AuthenticationHandler; 363 | friend class FeatureConfigurator; 364 | // Copy data to DMA buffer 365 | uint8_t check(void *buf, uint8_t len) override; 366 | // Unload data from DMA buffer 367 | uint8_t reply(const void *buf, uint8_t len) override; 368 | bool onGetReport(uint16_t value, uint16_t index, uint16_t length) override; 369 | bool onSetReport(uint16_t value, uint16_t index, uint16_t length) override; 370 | static int frCallbackGet(void *setup_ptr, uint8_t *data, uint32_t *len); 371 | static int frCallbackSet(void *setup_ptr, uint8_t *data); 372 | // Feature request DMA buffer 373 | static uint8_t *frBuffer; 374 | static uint32_t *frSize; 375 | // Since this class can only be instantiated once, this should be fine. 376 | static TransportTeensy *inst; 377 | }; 378 | 379 | #elif defined(RDS4_ARDUINO) && defined(USBCON) 380 | 381 | #include "PluggableUSB.h" 382 | #include "HID.h" 383 | 384 | #if !defined(_USING_HID) 385 | #error "Pluggable HID required" 386 | #endif 387 | 388 | typedef struct { 389 | InterfaceDescriptor hid; 390 | HIDDescDescriptor desc; 391 | EndpointDescriptor in; 392 | EndpointDescriptor out; 393 | } DS4HIDDescriptor; 394 | 395 | #if defined(ARDUINO_ARCH_AVR) 396 | typedef uint8_t usb_eptype_t; 397 | #elif defined(ARDUINO_ARCH_SAMD) 398 | typedef uint32_t usb_eptype_t; 399 | const usb_eptype_t EP_TYPE_INTERRUPT_IN = USB_ENDPOINT_TYPE_INTERRUPT | USB_ENDPOINT_IN(0); 400 | const usb_eptype_t EP_TYPE_INTERRUPT_OUT = USB_ENDPOINT_TYPE_INTERRUPT | USB_ENDPOINT_OUT(0); 401 | #endif 402 | 403 | /** Tansport backend for Arduino PluggableUSB-compatible boards. */ 404 | class TransportDS4PUSB : public PluggableUSBModule, public Transport, public AuthenticationHandler { 405 | public: 406 | TransportDS4PUSB(Authenticator *auth) : AuthenticationHandler(auth), 407 | PluggableUSBModule(2, 1, epType) { 408 | this->epType = {EP_TYPE_INTERRUPT_IN, EP_TYPE_INTERRUPT_OUT}; 409 | PluggableUSB().plug(this); 410 | 411 | } 412 | int begin(void) override { return; } 413 | uint8_t send(const void *buf, uint8_t len) override; 414 | uint8_t recv(void *buf, uint8_t len) override; 415 | bool available() override; 416 | 417 | protected: 418 | // PluggableUSB API 419 | bool setup(USBSetup& setup); 420 | int getInterface(uint8_t* interfaceCount) override; 421 | int getDescriptor(USBSetup& setup) override; 422 | uint8_t reply(const void *buf, uint8_t len) override; 423 | uint8_t check(void *buf, uint8_t len) override; 424 | 425 | private: 426 | inline uint8_t getOutEP() { return this->pluggedEndpoint; } 427 | inline uint8_t getInEP() { return this->pluggedEndpoint - 1; } 428 | usb_eptype_t epType[2]; 429 | // TODO do we need these? 430 | uint8_t protocol; 431 | uint8_t idle; 432 | }; 433 | 434 | #endif 435 | #if 0 436 | // This is outdated, update soon 437 | #ifdef RDS4_LINUX 438 | 439 | class TransportNull : public Transport { 440 | bool sendAvailable() { return false; } 441 | bool recvAvailable() { return false; } 442 | size_t sendReport(const void *buf, size_t len) { return 0; } 443 | size_t recvReport(void *buf, size_t len) { return 0; } 444 | }; 445 | 446 | class TransportStdio : public Transport { 447 | bool sendAvailable() { return true; } 448 | bool recvAvailable() { return false; } 449 | size_t sendReport(const void *buf, size_t len) { 450 | return fwrite(buf, sizeof(uint8_t), len, stdout); 451 | } 452 | size_t recvReport(void *buf, size_t len) { return 0; } 453 | }; 454 | 455 | #endif // RDS4_MOCK 456 | #endif 457 | } // namespace ds4 458 | } // namespace rds4 459 | --------------------------------------------------------------------------------