#include "virt_ds5.h" #include "message.h" #include #include #define DS_FEATURE_REPORT_PAIRING_INFO 0x09 #define DS_FEATURE_REPORT_PAIRING_INFO_SIZE 20 #define DS_FEATURE_REPORT_FIRMWARE_INFO 0x20 #define DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE 64 #define DS_FEATURE_REPORT_CALIBRATION 0x05 #define DS_FEATURE_REPORT_CALIBRATION_SIZE 41 #define DS_INPUT_REPORT_USB 0x01 #define DS_INPUT_REPORT_USB_SIZE 64 #define DS_INPUT_REPORT_BT 0x31 #define DS_INPUT_REPORT_BT_SIZE 78 #define DS_OUTPUT_REPORT_USB 0x02 #define DS_OUTPUT_REPORT_USB_SIZE 63 #define DS_OUTPUT_REPORT_BT 0x31 #define DS_OUTPUT_REPORT_BT_SIZE 78 #define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT 0x02 #define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 0x04 #define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION 0x01 #define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE 0x04 #define DS5_SPEC_DELTA_TIME 4096.0f static uint32_t le(uint32_t num) { const uint32_t b0 = (num & 0x000000ff) << 24u; const uint32_t b1 = (num & 0x0000ff00) << 8u; const uint32_t b2 = (num & 0x00ff0000) >> 8u; const uint32_t b3 = (num & 0xff000000) >> 24u; return b0 | b1 | b2 | b3; } static uint32_t crc32_le(uint32_t crc_initial, const uint8_t *const buf, size_t len) { return crc32(crc_initial ^ 0xffffffff, buf, len) ^ 0xffffffff; } /* Seed values for DualShock4 / DualSense CRC32 for different report types. */ static uint8_t PS_INPUT_CRC32_SEED = 0xA1; static uint8_t PS_OUTPUT_CRC32_SEED = 0xA2; static uint8_t PS_FEATURE_CRC32_SEED = 0xA3; #define DS5_EDGE_VERSION 256 #define DS5_EDGE_VENDOR 0x054C #define DS5_EDGE_PRODUCT 0x0DF2 static const char* path = "/dev/uhid"; //static const char* const MAC_ADDR_STR = "e8:47:3a:d6:e7:74"; static const uint8_t MAC_ADDR[] = { 0x74, 0xe7, 0xd6, 0x3a, 0x47, 0xe8 }; static unsigned char rdesc[] = { 0x05, 0x01, // Usage Page (Generic Desktop) 0 0x09, 0x05, // Usage (Game Pad) 2 0xA1, 0x01, // Collection (Application) 4 0x85, 0x01, // Report ID (1) 6 0x09, 0x30, // Usage (X) 8 0x09, 0x31, // Usage (Y) 10 0x09, 0x32, // Usage (Z) 12 0x09, 0x35, // Usage (Rz) 14 0x09, 0x33, // Usage (Rx) 16 0x09, 0x34, // Usage (Ry) 18 0x15, 0x00, // Logical Minimum (0) 20 0x26, 0xFF, 0x00, // Logical Maximum (255) 22 0x75, 0x08, // Report Size (8) 25 0x95, 0x06, // Report Count (6) 27 0x81, 0x02, // Input (Data,Var,Abs) 29 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined Page 1) 31 0x09, 0x20, // Usage (Vendor Usage 0x20) 34 0x95, 0x01, // Report Count (1) 36 0x81, 0x02, // Input (Data,Var,Abs) 38 0x05, 0x01, // Usage Page (Generic Desktop) 40 0x09, 0x39, // Usage (Hat switch) 42 0x15, 0x00, // Logical Minimum (0) 44 0x25, 0x07, // Logical Maximum (7) 46 0x35, 0x00, // Physical Minimum (0) 48 0x46, 0x3B, 0x01, // Physical Maximum (315) 50 0x65, 0x14, // Unit (EnglishRotation: deg) 53 0x75, 0x04, // Report Size (4) 55 0x95, 0x01, // Report Count (1) 57 0x81, 0x42, // Input (Data,Var,Abs,Null) 59 0x65, 0x00, // Unit (None) 61 0x05, 0x09, // Usage Page (Button) 63 0x19, 0x01, // Usage Minimum (1) 65 0x29, 0x0F, // Usage Maximum (15) 67 0x15, 0x00, // Logical Minimum (0) 69 0x25, 0x01, // Logical Maximum (1) 71 0x75, 0x01, // Report Size (1) 73 0x95, 0x0F, // Report Count (15) 75 0x81, 0x02, // Input (Data,Var,Abs) 77 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined Page 1) 79 0x09, 0x21, // Usage (Vendor Usage 0x21) 82 0x95, 0x0D, // Report Count (13) 84 0x81, 0x02, // Input (Data,Var,Abs) 86 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined Page 1) 88 0x09, 0x22, // Usage (Vendor Usage 0x22) 91 0x15, 0x00, // Logical Minimum (0) 93 0x26, 0xFF, 0x00, // Logical Maximum (255) 95 0x75, 0x08, // Report Size (8) 98 0x95, 0x34, // Report Count (52) 100 0x81, 0x02, // Input (Data,Var,Abs) 102 0x85, 0x02, // Report ID (2) 104 0x09, 0x23, // Usage (Vendor Usage 0x23) 106 0x95, 0x3F, // Report Count (63) 108 0x91, 0x02, // Output (Data,Var,Abs) 110 0x85, 0x05, // Report ID (5) 112 0x09, 0x33, // Usage (Vendor Usage 0x33) 114 0x95, 0x28, // Report Count (40) 116 0xB1, 0x02, // Feature (Data,Var,Abs) 118 0x85, 0x08, // Report ID (8) 120 0x09, 0x34, // Usage (Vendor Usage 0x34) 122 0x95, 0x2F, // Report Count (47) 124 0xB1, 0x02, // Feature (Data,Var,Abs) 126 0x85, 0x09, // Report ID (9) 128 0x09, 0x24, // Usage (Vendor Usage 0x24) 130 0x95, 0x13, // Report Count (19) 132 0xB1, 0x02, // Feature (Data,Var,Abs) 134 0x85, 0x0A, // Report ID (10) 136 0x09, 0x25, // Usage (Vendor Usage 0x25) 138 0x95, 0x1A, // Report Count (26) 140 0xB1, 0x02, // Feature (Data,Var,Abs) 142 0x85, 0x20, // Report ID (32) 144 0x09, 0x26, // Usage (Vendor Usage 0x26) 146 0x95, 0x3F, // Report Count (63) 148 0xB1, 0x02, // Feature (Data,Var,Abs) 150 0x85, 0x21, // Report ID (33) 152 0x09, 0x27, // Usage (Vendor Usage 0x27) 154 0x95, 0x04, // Report Count (4) 156 0xB1, 0x02, // Feature (Data,Var,Abs) 158 0x85, 0x22, // Report ID (34) 160 0x09, 0x40, // Usage (Vendor Usage 0x40) 162 0x95, 0x3F, // Report Count (63) 164 0xB1, 0x02, // Feature (Data,Var,Abs) 166 0x85, 0x80, // Report ID (128) 168 0x09, 0x28, // Usage (Vendor Usage 0x28) 170 0x95, 0x3F, // Report Count (63) 172 0xB1, 0x02, // Feature (Data,Var,Abs) 174 0x85, 0x81, // Report ID (129) 176 0x09, 0x29, // Usage (Vendor Usage 0x29) 178 0x95, 0x3F, // Report Count (63) 180 0xB1, 0x02, // Feature (Data,Var,Abs) 182 0x85, 0x82, // Report ID (130) 184 0x09, 0x2A, // Usage (Vendor Usage 0x2a) 186 0x95, 0x09, // Report Count (9) 188 0xB1, 0x02, // Feature (Data,Var,Abs) 190 0x85, 0x83, // Report ID (131) 192 0x09, 0x2B, // Usage (Vendor Usage 0x2b) 194 0x95, 0x3F, // Report Count (63) 196 0xB1, 0x02, // Feature (Data,Var,Abs) 198 0x85, 0x84, // Report ID (132) 200 0x09, 0x2C, // Usage (Vendor Usage 0x2c) 202 0x95, 0x3F, // Report Count (63) 204 0xB1, 0x02, // Feature (Data,Var,Abs) 206 0x85, 0x85, // Report ID (133) 208 0x09, 0x2D, // Usage (Vendor Usage 0x2d) 210 0x95, 0x02, // Report Count (2) 212 0xB1, 0x02, // Feature (Data,Var,Abs) 214 0x85, 0xA0, // Report ID (160) 216 0x09, 0x2E, // Usage (Vendor Usage 0x2e) 218 0x95, 0x01, // Report Count (1) 220 0xB1, 0x02, // Feature (Data,Var,Abs) 222 0x85, 0xE0, // Report ID (224) 224 0x09, 0x2F, // Usage (Vendor Usage 0x2f) 226 0x95, 0x3F, // Report Count (63) 228 0xB1, 0x02, // Feature (Data,Var,Abs) 230 0x85, 0xF0, // Report ID (240) 232 0x09, 0x30, // Usage (Vendor Usage 0x30) 234 0x95, 0x3F, // Report Count (63) 236 0xB1, 0x02, // Feature (Data,Var,Abs) 238 0x85, 0xF1, // Report ID (241) 240 0x09, 0x31, // Usage (Vendor Usage 0x31) 242 0x95, 0x3F, // Report Count (63) 244 0xB1, 0x02, // Feature (Data,Var,Abs) 246 0x85, 0xF2, // Report ID (242) 248 0x09, 0x32, // Usage (Vendor Usage 0x32) 250 0x95, 0x34, // Report Count (52) 252 0xB1, 0x02, // Feature (Data,Var,Abs) 254 0x85, 0xF4, // Report ID (244) 256 0x09, 0x35, // Usage (Vendor Usage 0x35) 258 0x95, 0x3F, // Report Count (63) 260 0xB1, 0x02, // Feature (Data,Var,Abs) 262 0x85, 0xF5, // Report ID (245) 264 0x09, 0x36, // Usage (Vendor Usage 0x36) 266 0x95, 0x03, // Report Count (3) 268 0xB1, 0x02, // Feature (Data,Var,Abs) 270 0x85, 0x60, // Report ID (96) 272 0x09, 0x41, // Usage (Vendor Usage 0x41) 274 0x95, 0x3F, // Report Count (63) 276 0xB1, 0x02, // Feature (Data,Var,Abs) 278 0x85, 0x61, // Report ID (97) 280 0x09, 0x42, // Usage (Vendor Usage 0x42) 282 0xB1, 0x02, // Feature (Data,Var,Abs) 284 0x85, 0x62, // Report ID (98) 286 0x09, 0x43, // Usage (Vendor Usage 0x43) 288 0xB1, 0x02, // Feature (Data,Var,Abs) 290 0x85, 0x63, // Report ID (99) 292 0x09, 0x44, // Usage (Vendor Usage 0x44) 294 0xB1, 0x02, // Feature (Data,Var,Abs) 296 0x85, 0x64, // Report ID (100) 298 0x09, 0x45, // Usage (Vendor Usage 0x45) 300 0xB1, 0x02, // Feature (Data,Var,Abs) 302 0x85, 0x65, // Report ID (101) 304 0x09, 0x46, // Usage (Vendor Usage 0x46) 306 0xB1, 0x02, // Feature (Data,Var,Abs) 308 0x85, 0x68, // Report ID (104) 310 0x09, 0x47, // Usage (Vendor Usage 0x47) 312 0xB1, 0x02, // Feature (Data,Var,Abs) 314 0x85, 0x70, // Report ID (112) 316 0x09, 0x48, // Usage (Vendor Usage 0x48) 318 0xB1, 0x02, // Feature (Data,Var,Abs) 320 0x85, 0x71, // Report ID (113) 322 0x09, 0x49, // Usage (Vendor Usage 0x49) 324 0xB1, 0x02, // Feature (Data,Var,Abs) 326 0x85, 0x72, // Report ID (114) 328 0x09, 0x4A, // Usage (Vendor Usage 0x4a) 330 0xB1, 0x02, // Feature (Data,Var,Abs) 332 0x85, 0x73, // Report ID (115) 334 0x09, 0x4B, // Usage (Vendor Usage 0x4b) 336 0xB1, 0x02, // Feature (Data,Var,Abs) 338 0x85, 0x74, // Report ID (116) 340 0x09, 0x4C, // Usage (Vendor Usage 0x4c) 342 0xB1, 0x02, // Feature (Data,Var,Abs) 344 0x85, 0x75, // Report ID (117) 346 0x09, 0x4D, // Usage (Vendor Usage 0x4d) 348 0xB1, 0x02, // Feature (Data,Var,Abs) 350 0x85, 0x76, // Report ID (118) 352 0x09, 0x4E, // Usage (Vendor Usage 0x4e) 354 0xB1, 0x02, // Feature (Data,Var,Abs) 356 0x85, 0x77, // Report ID (119) 358 0x09, 0x4F, // Usage (Vendor Usage 0x4f) 360 0xB1, 0x02, // Feature (Data,Var,Abs) 362 0x85, 0x78, // Report ID (120) 364 0x09, 0x50, // Usage (Vendor Usage 0x50) 366 0xB1, 0x02, // Feature (Data,Var,Abs) 368 0x85, 0x79, // Report ID (121) 370 0x09, 0x51, // Usage (Vendor Usage 0x51) 372 0xB1, 0x02, // Feature (Data,Var,Abs) 374 0x85, 0x7A, // Report ID (122) 376 0x09, 0x52, // Usage (Vendor Usage 0x52) 378 0xB1, 0x02, // Feature (Data,Var,Abs) 380 0x85, 0x7B, // Report ID (123) 382 0x09, 0x53, // Usage (Vendor Usage 0x53) 384 0xB1, 0x02, // Feature (Data,Var,Abs) 386 0xC0, // End Collection 388 }; static unsigned char rdesc_bt[] = { 0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0E, 0x81, 0x02, 0x75, 0x06, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x4D, 0x85, 0x31, 0x09, 0x31, 0x91, 0x02, 0x09, 0x3B, 0x81, 0x02, 0x85, 0x32, 0x09, 0x32, 0x95, 0x8D, 0x91, 0x02, 0x85, 0x33, 0x09, 0x33, 0x95, 0xCD, 0x91, 0x02, 0x85, 0x34, 0x09, 0x34, 0x96, 0x0D, 0x01, 0x91, 0x02, 0x85, 0x35, 0x09, 0x35, 0x96, 0x4D, 0x01, 0x91, 0x02, 0x85, 0x36, 0x09, 0x36, 0x96, 0x8D, 0x01, 0x91, 0x02, 0x85, 0x37, 0x09, 0x37, 0x96, 0xCD, 0x01, 0x91, 0x02, 0x85, 0x38, 0x09, 0x38, 0x96, 0x0D, 0x02, 0x91, 0x02, 0x85, 0x39, 0x09, 0x39, 0x96, 0x22, 0x02, 0x91, 0x02, 0x06, 0x80, 0xFF, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02, 0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09, 0x09, 0x24, 0x95, 0x13, 0xB1, 0x02, 0x85, 0x20, 0x09, 0x26, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x80, 0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09, 0xB1, 0x02, 0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x32, 0x95, 0x34, 0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x60, 0x09, 0x41, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x61, 0x09, 0x42, 0xB1, 0x02, 0x85, 0x62, 0x09, 0x43, 0xB1, 0x02, 0x85, 0x63, 0x09, 0x44, 0xB1, 0x02, 0x85, 0x64, 0x09, 0x45, 0xB1, 0x02, 0x85, 0x65, 0x09, 0x46, 0xB1, 0x02, 0x85, 0x68, 0x09, 0x47, 0xB1, 0x02, 0x85, 0x70, 0x09, 0x48, 0xB1, 0x02, 0x85, 0x71, 0x09, 0x49, 0xB1, 0x02, 0x85, 0x72, 0x09, 0x4A, 0xB1, 0x02, 0x85, 0x73, 0x09, 0x4B, 0xB1, 0x02, 0x85, 0x74, 0x09, 0x4C, 0xB1, 0x02, 0x85, 0x75, 0x09, 0x4D, 0xB1, 0x02, 0x85, 0x76, 0x09, 0x4E, 0xB1, 0x02, 0x85, 0x77, 0x09, 0x4F, 0xB1, 0x02, 0x85, 0x78, 0x09, 0x50, 0xB1, 0x02, 0x85, 0x79, 0x09, 0x51, 0xB1, 0x02, 0x85, 0x7A, 0x09, 0x52, 0xB1, 0x02, 0x85, 0x7B, 0x09, 0x53, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x2C, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF5, 0x09, 0x2D, 0x95, 0x07, 0xB1, 0x02, 0x85, 0xF6, 0x09, 0x2E, 0x96, 0x22, 0x02, 0xB1, 0x02, 0x85, 0xF7, 0x09, 0x2F, 0x95, 0x07, 0xB1, 0x02, 0xC0, 0x00, }; static int uhid_write(int fd, const struct uhid_event *ev) { ssize_t ret; ret = write(fd, ev, sizeof(*ev)); if (ret < 0) { fprintf(stderr, "Cannot write to uhid: %d\n", (int)ret); return -errno; } else if (ret != sizeof(*ev)) { fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", ret, sizeof(*ev)); return -EFAULT; } else { return 0; } } static int create(int fd, bool bluetooth) { struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_CREATE; strcpy((char*)ev.u.create.name, "Sony Corp. DualSense Edge wireless controller (PS5)"); ev.u.create.rd_data = bluetooth ? rdesc_bt : rdesc; ev.u.create.rd_size = bluetooth ? sizeof(rdesc_bt) : sizeof(rdesc); ev.u.create.bus = bluetooth ? BUS_BLUETOOTH : BUS_USB; ev.u.create.vendor = DS5_EDGE_VENDOR; ev.u.create.product = DS5_EDGE_PRODUCT; ev.u.create.version = DS5_EDGE_VERSION; ev.u.create.country = 0; memset(&ev.u.create.uniq, 0, sizeof(ev.u.create.uniq)); memcpy(&ev.u.create.uniq, (void*)MAC_ADDR, sizeof(MAC_ADDR)); return uhid_write(fd, &ev); } static void destroy(int fd) { struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_DESTROY; uhid_write(fd, &ev); } int virt_dualsense_init(virt_dualsense_t *const out_gamepad, bool bluetooth) { int ret = 0; out_gamepad->bluetooth = bluetooth; out_gamepad->dt_sum = 0; out_gamepad->dt_buffer_current = 0; memset(out_gamepad->dt_buffer, 0, sizeof(out_gamepad->dt_buffer)); out_gamepad->debug = false; out_gamepad->empty_reports = 0; out_gamepad->last_time = 0; out_gamepad->seq_num = 0; out_gamepad->fd = open(path, O_RDWR | O_CLOEXEC /* | O_NONBLOCK */); if (out_gamepad->fd < 0) { fprintf(stderr, "Cannot open uhid-cdev %s: %d\n", path, out_gamepad->fd); ret = out_gamepad->fd; goto virt_dualshock_init_err; } ret = create(out_gamepad->fd, bluetooth); if (ret) { fprintf(stderr, "Error creating uhid device: %d\n", ret); close(out_gamepad->fd); goto virt_dualshock_init_err; } virt_dualshock_init_err: return ret; } int virt_dualsense_get_fd(virt_dualsense_t *const in_gamepad) { return in_gamepad->fd; } int virt_dualsense_event(virt_dualsense_t *const gamepad, gamepad_status_t *const out_device_status) { struct uhid_event ev; ssize_t ret; int fd = virt_dualsense_get_fd(gamepad); memset(&ev, 0, sizeof(ev)); ret = read(fd, &ev, sizeof(ev)); if (ret == 0) { fprintf(stderr, "Read HUP on uhid-cdev\n"); return -EFAULT; } else if (ret == -1) { return 0; } else if (ret < 0) { fprintf(stderr, "Cannot read uhid-cdev: %d\n", (int)ret); return -errno; } else if (ret != sizeof(ev)) { fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", ret, sizeof(ev)); return -EFAULT; } switch (ev.type) { case UHID_START: if (gamepad->debug) { printf("UHID_START from uhid-dev\n"); } break; case UHID_STOP: if (gamepad->debug) { printf("UHID_STOP from uhid-dev\n"); } break; case UHID_OPEN: if (gamepad->debug) { printf("UHID_OPEN from uhid-dev\n"); } break; case UHID_CLOSE: if (gamepad->debug) { fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); } break; case UHID_OUTPUT: if (gamepad->debug) { printf("UHID_OUTPUT from uhid-dev\n"); } // Rumble and LED messages are adverised via OUTPUT reports; ignore the rest if (ev.u.output.rtype != UHID_OUTPUT_REPORT) return 0; if (ev.u.output.size == 48) { fprintf(stderr, "Ignored a 48 bytes report on purpose\n"); } //if (ev.u.output.size != 48) if ( (!gamepad->bluetooth) && (ev.u.output.size != DS_OUTPUT_REPORT_USB_SIZE) && (gamepad->bluetooth) && (ev.u.output.size != DS_OUTPUT_REPORT_BT_SIZE) ) { fprintf( stderr, "Invalid data length: got %d, expected %d\n", (int)ev.u.output.size, (gamepad->bluetooth) ? DS_OUTPUT_REPORT_BT_SIZE : DS_OUTPUT_REPORT_USB_SIZE ); return 0; } // first byte is report-id which is 0x01 if ( (!gamepad->bluetooth) && (ev.u.output.data[0] != DS_OUTPUT_REPORT_USB) && (gamepad->bluetooth) && ((ev.u.output.data[0] != DS_OUTPUT_REPORT_BT) && (ev.u.output.data[0] < 0x10)) ) { fprintf( stderr, "Unrecognised report-id: got 0x%x expected 0x%x\n", (int)ev.u.output.data[0], (gamepad->bluetooth) ? DS_OUTPUT_REPORT_BT : DS_OUTPUT_REPORT_USB ); return 0; } // When using bluetooth, the first byte after the reportID is uint8_t seq_tag, // while the next one is uint8_t tag, following bytes are the same. size_t offset = 1; if ((gamepad->bluetooth) && (ev.u.output.data[0] > 0x10)) { offset = 2; } else if ((gamepad->bluetooth) && (ev.u.output.data[0] == 0x02)) { offset = 3; } else if ((gamepad->bluetooth) && (ev.u.output.data[0] == 0x01)) { offset = 1; } const uint8_t *const common_report = &ev.u.output.data[offset]; const uint8_t valid_flag0 = common_report[0]; const uint8_t valid_flag1 = common_report[1]; // For DualShock 4 compatibility mode. const uint8_t motor_right = common_report[2]; const uint8_t motor_left = common_report[3]; // Audio controls const uint8_t reserved[4] = { common_report[4], common_report[5], common_report[6], common_report[7]}; const uint8_t mute_button_led = common_report[8]; uint8_t power_save_control = common_report[9]; uint8_t reserved2[28]; // LEDs and lightbar uint8_t valid_flag2 = common_report[38]; uint8_t reserved3[2] = {common_report[39], common_report[40]}; uint8_t lightbar_setup = common_report[41]; uint8_t led_brightness = common_report[42]; uint8_t player_leds = common_report[43]; uint8_t lightbar_red = common_report[44]; uint8_t lightbar_green = common_report[45]; uint8_t lightbar_blue = common_report[46]; if ((valid_flag0 & DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT) || ((motor_left == 0) && (motor_right == 0))) { uint8_t motors_shift = 0; if ((valid_flag2 & DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2) || (valid_flag0 & DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION)) { out_device_status->motors_intensity[0] = (motor_left << 1) | ((motor_left == 0) ? 0 : 1); out_device_status->motors_intensity[1] = (motor_right << 1) | ((motor_right == 0) ? 0 : 1); } else { out_device_status->motors_intensity[0] = motor_left; out_device_status->motors_intensity[1] = motor_right; } ++out_device_status->rumble_events_count; if (gamepad->debug) { printf( "Updated rumble -- motor_left: %d, motor_right: %d, valid_flag0; %d, valid_flag1: %d\n", motor_left, motor_right, valid_flag0, valid_flag1 ); } } if (valid_flag1 & DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE) { out_device_status->leds_colors[0] = lightbar_red; out_device_status->leds_colors[1] = lightbar_green; out_device_status->leds_colors[2] = lightbar_blue; out_device_status->leds_events_count++; } break; case UHID_OUTPUT_EV: if (gamepad->debug) { printf("UHID_OUTPUT_EV from uhid-dev\n"); } break; case UHID_GET_REPORT: //fprintf(stderr, "UHID_GET_REPORT from uhid-dev, report=%d\n", ev.u.get_report.rnum); if (ev.u.get_report.rnum == DS_FEATURE_REPORT_PAIRING_INFO) { struct uhid_event mac_addr_response = { .type = UHID_GET_REPORT_REPLY, .u = { .get_report_reply = { .size = 20, .id = ev.u.get_report.id, .err = 0, .data = { DS_FEATURE_REPORT_PAIRING_INFO, MAC_ADDR[0], MAC_ADDR[1], MAC_ADDR[2], MAC_ADDR[3], MAC_ADDR[4], MAC_ADDR[5], 0x08, 0x25, 0x00, 0x1e, 0x00, 0xee, 0x74, 0xd0, 0xbc, 0x00, 0x00, 0x00, 0x00 } } } }; if (gamepad->bluetooth) { uint32_t crc = crc32_le(0xFFFFFFFFU, (const uint8_t*)&PS_FEATURE_CRC32_SEED, sizeof(PS_FEATURE_CRC32_SEED)); crc = ~crc32_le(crc, (const Bytef *)&mac_addr_response.u.get_report_reply.data[0], mac_addr_response.u.get_report_reply.size - 4); memcpy(&mac_addr_response.u.get_report_reply.data[mac_addr_response.u.get_report_reply.size - sizeof(crc)], &crc, sizeof(crc)); } uhid_write(fd, &mac_addr_response); } else if (ev.u.get_report.rnum == DS_FEATURE_REPORT_FIRMWARE_INFO) { struct uhid_event firmware_info_response = { .type = UHID_GET_REPORT_REPLY, .u = { .get_report_reply = { .size = DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, .id = ev.u.get_report.id, .err = 0, .data = { DS_FEATURE_REPORT_FIRMWARE_INFO, 0x4a, 0x75, 0x6e, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x32, 0x33, 0x31, 0x34, 0x3a, 0x34, 0x37, 0x3a, 0x33, 0x34, 0x03, 0x00, 0x44, 0x00, 0x08, 0x02, 0x00, 0x01, 0x36, 0x00, 0x00, 0x01, 0xc1, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } } } }; if (gamepad->bluetooth) { uint32_t crc = crc32_le(0xFFFFFFFFU, (const uint8_t*)&PS_FEATURE_CRC32_SEED, sizeof(PS_FEATURE_CRC32_SEED)); crc = ~crc32_le(crc, (const Bytef *)&firmware_info_response.u.get_report_reply.data[0], firmware_info_response.u.get_report_reply.size - 4); memcpy(&firmware_info_response.u.get_report_reply.data[firmware_info_response.u.get_report_reply.size - sizeof(crc)], &crc, sizeof(crc)); } uhid_write(fd, &firmware_info_response); } else if (ev.u.get_report.rnum == DS_FEATURE_REPORT_CALIBRATION) { struct uhid_event calibration_response = { .type = UHID_GET_REPORT_REPLY, .u = { .get_report_reply = { .size = DS_FEATURE_REPORT_CALIBRATION_SIZE, .id = ev.u.get_report.id, .err = 0, .data = { DS_FEATURE_REPORT_CALIBRATION, 0xfe, 0xff, 0xfc, 0xff, 0xfe, 0xff, 0x83, 0x22, 0x78, 0xdd, 0x92, 0x22, 0x5f, 0xdd, 0x95, 0x22, 0x6d, 0xdd, 0x1c, 0x02, 0x1c, 0x02, 0xf2, 0x1f, 0xed, 0xdf, 0xe3, 0x20, 0xda, 0xe0, 0xee, 0x1f, 0xdf, 0xdf, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, } } } }; if (gamepad->bluetooth) { uint32_t crc = crc32_le(0xFFFFFFFFU, (const uint8_t*)&PS_FEATURE_CRC32_SEED, sizeof(PS_FEATURE_CRC32_SEED)); crc = ~crc32_le(crc, (const Bytef *)&calibration_response.u.get_report_reply.data[0], calibration_response.u.get_report_reply.size - 4); memcpy(&calibration_response.u.get_report_reply.data[calibration_response.u.get_report_reply.size - sizeof(crc)], &crc, sizeof(crc)); } uhid_write(fd, &calibration_response); } break; default: fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); } return 0; } typedef enum ds5_dpad_status { DPAD_N = 0, DPAD_NE = 1, DPAD_E = 2, DPAD_SE = 3, DPAD_S = 4, DPAD_SW = 5, DPAD_W = 6, DPAD_NW = 7, DPAD_RELEASED = 0x08, } ds5_dpad_status_t; static ds5_dpad_status_t ds5_dpad_from_gamepad(uint8_t dpad) { if (dpad == 0x01) { return DPAD_E; } else if (dpad == 0x02) { return DPAD_W; } else if (dpad == 0x10) { return DPAD_N; } else if (dpad == 0x20) { return DPAD_S; } else if (dpad == 0x11) { return DPAD_NE; } else if (dpad == 0x12) { return DPAD_NW; } else if (dpad == 0x21) { return DPAD_SE; } else if (dpad == 0x22) { return DPAD_SW; } return DPAD_RELEASED; } void virt_dualsense_close(virt_dualsense_t *const out_gamepad) { destroy(out_gamepad->fd); } void virt_dualsense_compose(virt_dualsense_t *const gamepad, gamepad_status_t *const in_device_status, uint8_t *const out_buf) { const int64_t time_us = in_device_status->last_gyro_motion_timestamp_ns / (int64_t)1000; const int64_t delta_time = time_us - gamepad->last_time; gamepad->last_time = time_us; // find the average Δt in the last 30 non-zero inputs; // this has to run thousands of times a second so i'm trying to do this as fast as possible if (delta_time == 0) { gamepad->empty_reports++; } else if (delta_time < 1000000 && delta_time > 0 ) { // ignore outliers gamepad->dt_sum -= gamepad->dt_buffer[gamepad->dt_buffer_current]; gamepad->dt_sum += delta_time; gamepad->dt_buffer[gamepad->dt_buffer_current] = delta_time; if (gamepad->dt_buffer_current == 29) { gamepad->dt_buffer_current = 0; } else { gamepad->dt_buffer_current++; } } static uint64_t sim_time = 0; const double correction_factor = DS5_SPEC_DELTA_TIME / ((double)gamepad->dt_sum / 30.f); if (delta_time != 0) { sim_time += (int)((double)delta_time * correction_factor); } const uint32_t timestamp = sim_time + (int)((double)gamepad->empty_reports * DS5_SPEC_DELTA_TIME); const int16_t g_x = in_device_status->raw_gyro[0]; const int16_t g_y = (int16_t)(-1) * in_device_status->raw_gyro[1]; // Swap Y and Z const int16_t g_z = (int16_t)(-1) * in_device_status->raw_gyro[2]; // Swap Y and Z const int16_t a_x = in_device_status->raw_accel[0]; const int16_t a_y = (int16_t)(-1) * in_device_status->raw_accel[1]; // Swap Y and Z const int16_t a_z = (int16_t)(-1) * in_device_status->raw_accel[2]; // Swap Y and Z out_buf[0] = gamepad->bluetooth ? DS_INPUT_REPORT_BT : DS_INPUT_REPORT_USB; // [00] report ID (0x01) uint8_t *const out_shifted_buf = gamepad->bluetooth ? &out_buf[1] : &out_buf[0]; out_shifted_buf[1] = ((uint64_t)((int64_t)in_device_status->joystick_positions[0][0] + (int64_t)32768) >> (uint64_t)8); // L stick, X axis out_shifted_buf[2] = ((uint64_t)((int64_t)in_device_status->joystick_positions[0][1] + (int64_t)32768) >> (uint64_t)8); // L stick, Y axis out_shifted_buf[3] = ((uint64_t)((int64_t)in_device_status->joystick_positions[1][0] + (int64_t)32768) >> (uint64_t)8); // R stick, X axis out_shifted_buf[4] = ((uint64_t)((int64_t)in_device_status->joystick_positions[1][1] + (int64_t)32768) >> (uint64_t)8); // R stick, Y axis out_shifted_buf[5] = in_device_status->l2_trigger; // Z out_shifted_buf[6] = in_device_status->r2_trigger; // RZ out_shifted_buf[7] = gamepad->seq_num++; // seq_number out_shifted_buf[8] = (in_device_status->square ? 0x10 : 0x00) | (in_device_status->cross ? 0x20 : 0x00) | (in_device_status->circle ? 0x40 : 0x00) | (in_device_status->triangle ? 0x80 : 0x00) | (uint8_t)ds5_dpad_from_gamepad(in_device_status->dpad); out_shifted_buf[9] = (in_device_status->l1 ? 0x01 : 0x00) | (in_device_status->r1 ? 0x02 : 0x00) | /*(in_device_status->l2_trigger >= 225 ? 0x04 : 0x00)*/ 0x00 | /*(in_device_status->r2_trigger >= 225 ? 0x08 : 0x00)*/ 0x00 | (in_device_status->option ? 0x10 : 0x00) | (in_device_status->share ? 0x20 : 0x00) | (in_device_status->l3 ? 0x40 : 0x00) | (in_device_status->r3 ? 0x80 : 0x00); // mic button press is 0x04, touchpad press is 0x02 out_shifted_buf[10] = (in_device_status->l5 ? 0x40 : 0x00) | (in_device_status->r5 ? 0x80 : 0x00) | (in_device_status->l4 ? 0x10 : 0x00) | (in_device_status->r4 ? 0x20 : 0x00) | (in_device_status->touchpad_press ? 0x02 : 0x00) | (in_device_status->center ? 0x01 : 0x00); //buf[11] = ; //buf[12] = 0x20; // [12] battery level | this is called sensor_temparature in the kernel driver but is never used... memcpy(&out_shifted_buf[16], &g_x, sizeof(int16_t)); memcpy(&out_shifted_buf[18], &g_y, sizeof(int16_t)); memcpy(&out_shifted_buf[20], &g_z, sizeof(int16_t)); memcpy(&out_shifted_buf[22], &a_x, sizeof(int16_t)); memcpy(&out_shifted_buf[24], &a_y, sizeof(int16_t)); memcpy(&out_shifted_buf[26], &a_z, sizeof(int16_t)); memcpy(&out_shifted_buf[28], ×tamp, sizeof(timestamp)); // point of contact number 0 out_shifted_buf[33] = ((in_device_status->touchpad_touch_num == -1) && (!in_device_status->touchpad_press)) ? 0x80 : 0x7F; //contact out_shifted_buf[34] = in_device_status->touchpad_x & (int16_t)0x00FF; //x_lo out_shifted_buf[35] = (((in_device_status->touchpad_x & (int16_t)0x0F00) >> (int16_t)8) | ((in_device_status->touchpad_y & (int16_t)0x000F) << (int16_t)4)); // x_hi:4 y_lo:4 out_shifted_buf[36] = (in_device_status->touchpad_y & (int16_t)0x0FF0) >> (int16_t)4; //y_hi // point of contact number 1 out_shifted_buf[37] = 0x80; //contact out_shifted_buf[38] = 0x00; //x_lo out_shifted_buf[39] = 0x00; //x_hi:4 y_lo:4 out_shifted_buf[40] = 0x00; //y_hi if (gamepad->bluetooth) { uint32_t crc = crc32_le(0xFFFFFFFFU, (const uint8_t*)&PS_FEATURE_CRC32_SEED, sizeof(PS_FEATURE_CRC32_SEED)); crc = ~crc32_le(crc, (const Bytef *)&out_shifted_buf[0], DS_INPUT_REPORT_BT_SIZE - 4); memcpy(&out_shifted_buf[DS_INPUT_REPORT_BT_SIZE - sizeof(crc)], &crc, sizeof(crc)); } } int virt_dualsense_send(virt_dualsense_t *const gamepad, uint8_t *const out_shifted_buf) { struct uhid_event l = { .type = UHID_INPUT2, .u = { .input2 = { .size = gamepad->bluetooth ? DS_INPUT_REPORT_BT_SIZE : DS_INPUT_REPORT_USB_SIZE, } } }; memcpy(&l.u.input2.data[0], &out_shifted_buf[0], l.u.input2.size); if (gamepad->bluetooth) { uint32_t crc = crc32_le(0xFFFFFFFFU, (const uint8_t*)&PS_INPUT_CRC32_SEED, sizeof(PS_INPUT_CRC32_SEED)); crc = ~crc32_le(crc, (const uint8_t *)&l.u.input2.data[0], l.u.input2.size - 4); memcpy(&l.u.input2.data[l.u.input2.size - sizeof(crc)], &crc, sizeof(crc)); } return uhid_write(gamepad->fd, &l); }