diff --git a/.gitignore b/.gitignore index b4ebcb3..ca6169a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ bld/ vc140.pdb build + +**/*.dll diff --git a/project/dependencies/JoyShockLibrary/InputHelpers.cpp b/project/dependencies/JoyShockLibrary/InputHelpers.cpp new file mode 100644 index 0000000..5d62b97 --- /dev/null +++ b/project/dependencies/JoyShockLibrary/InputHelpers.cpp @@ -0,0 +1,548 @@ +#include "JoyShockLibrary.h" +#include "JoyShock.cpp" + +#include + +bool handle_input(JoyShock *jc, uint8_t *packet, int len, bool &hasIMU) { + hasIMU = true; + if (packet[0] == 0) return false; // ignore non-responses + // remember last input + + //printf("%d: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + // jc->left_right, + // packet[0], packet[1], packet[2], packet[3], packet[4], packet[5], packet[6], packet[7], packet[8], packet[9], + // packet[10], packet[11], packet[12], packet[13], packet[14], packet[15], packet[16], packet[17], packet[18], packet[19], packet[20]); + + jc->last_simple_state = jc->simple_state; + jc->simple_state.buttons = 0; + jc->last_imu_state = jc->imu_state; + IMU_STATE imu_state; + // delta time + auto time_now = std::chrono::steady_clock::now(); + jc->delta_time = (float)(std::chrono::duration_cast(time_now - jc->last_polled).count() / 1000000.0); + jc->last_polled = time_now; + if (jc->cue_motion_reset) + { + //printf("RESET motion\n"); + jc->cue_motion_reset = false; + jc->motion.Reset(); + } + if (jc->motion.GetCalibrationMode() == GamepadMotionHelpers::CalibrationMode::Manual) + { + if (jc->use_continuous_calibration) + { + jc->motion.StartContinuousCalibration(); + } + else + { + jc->motion.PauseContinuousCalibration(); + } + } + // ds4 + if (jc->controller_type == ControllerType::s_ds4) { + int indexOffset = 0; + bool isValid = true; + if (!jc->is_usb) { + isValid = packet[0] == 0x11; + indexOffset = 2; + } + else { + isValid = packet[0] == 0x01; + if (isValid && (packet[31] & 0x04) == 0x04) + return false; // ignore packets from Dongle with no connected controller + } + if (isValid) { + // Gyroscope: + // Gyroscope data is relative (degrees/s) + int16_t gyroSampleX = uint16_to_int16(packet[indexOffset+13] | (packet[indexOffset+14] << 8) & 0xFF00); + int16_t gyroSampleY = uint16_to_int16(packet[indexOffset+15] | (packet[indexOffset+16] << 8) & 0xFF00); + int16_t gyroSampleZ = uint16_to_int16(packet[indexOffset+17] | (packet[indexOffset+18] << 8) & 0xFF00); + int16_t accelSampleX = uint16_to_int16(packet[indexOffset+19] | (packet[indexOffset+20] << 8) & 0xFF00); + int16_t accelSampleY = uint16_to_int16(packet[indexOffset+21] | (packet[indexOffset+22] << 8) & 0xFF00); + int16_t accelSampleZ = uint16_to_int16(packet[indexOffset+23] | (packet[indexOffset+24] << 8) & 0xFF00); + + if ((gyroSampleX | gyroSampleY | gyroSampleZ | accelSampleX | accelSampleY | accelSampleZ) == 0) + { + // all zero? + hasIMU = false; + } + + // convert to real units + imu_state.gyroX = (float)(gyroSampleX) * (2000.0f / 32767.0f); + imu_state.gyroY = (float)(gyroSampleY) * (2000.0f / 32767.0f); + imu_state.gyroZ = (float)(gyroSampleZ) * (2000.0f / 32767.0f); + + imu_state.accelX = (float)(accelSampleX) / 8192.0f; + imu_state.accelY = (float)(accelSampleY) / 8192.0f; + imu_state.accelZ = (float)(accelSampleZ) / 8192.0f; + + //printf("DS4 accel: %.4f, %.4f, %.4f\n", imu_state.accelX, imu_state.accelY, imu_state.accelZ); + + //printf("%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%d\n", + // jc->gyro.yaw, jc->gyro.pitch, jc->gyro.roll, jc->accel.x, jc->accel.y, jc->accel.z, universal_counter++); + + // Touchpad: + jc->last_touch_state = jc->touch_state; + + jc->touch_state.t0Id = (int)(packet[indexOffset+35] & 0x7F); + jc->touch_state.t1Id = (int)(packet[indexOffset+39] & 0x7F); + jc->touch_state.t0Down = (packet[indexOffset+35] & 0x80) == 0; + jc->touch_state.t1Down = (packet[indexOffset+39] & 0x80) == 0; + + jc->touch_state.t0X = (packet[indexOffset+36] | (packet[indexOffset+37] & 0x0F) << 8) / 1920.0f; + jc->touch_state.t0Y = ((packet[indexOffset+37] & 0xF0) >> 4 | packet[indexOffset+38] << 4) / 943.0f; + jc->touch_state.t1X = (packet[indexOffset+40] | (packet[indexOffset+41] & 0x0F) << 8) / 1920.0f; + jc->touch_state.t1Y = ((packet[indexOffset+41] & 0xF0) >> 4 | packet[indexOffset+42] << 4) / 943.0f; + + //printf("DS4 touch: %d, %d, %d, %d, %.4f, %.4f, %.4f, %.4f\n", + // jc->touch_state.t0Id, jc->touch_state.t1Id, jc->touch_state.t0Down, jc->touch_state.t1Down, + // jc->touch_state.t0X, jc->touch_state.t0Y, jc->touch_state.t1X, jc->touch_state.t1Y); + + // DS4 dpad is a hat... 0x08 is released, 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW + // http://eleccelerator.com/wiki/index.php?title=DualShock_4 + uint8_t hat = packet[indexOffset+5] & 0x0f; + + if ((hat > 2) & (hat < 6)) jc->simple_state.buttons |= JSMASK_DOWN; // down = SE | S | SW + if ((hat == 7) | (hat < 2)) jc->simple_state.buttons |= JSMASK_UP; // up = N | NE | NW + if ((hat > 0) & (hat < 4)) jc->simple_state.buttons |= JSMASK_RIGHT; // right = NE | E | SE + if ((hat > 4) & (hat < 8)) jc->simple_state.buttons |= JSMASK_LEFT; // left = SW | W | NW + + jc->simple_state.buttons |= ((int)(packet[indexOffset+5] >> 4) << JSOFFSET_W) & JSMASK_W; + jc->simple_state.buttons |= ((int)(packet[indexOffset+5] >> 7) << JSOFFSET_N) & JSMASK_N; + jc->simple_state.buttons |= ((int)(packet[indexOffset+5] >> 5) << JSOFFSET_S) & JSMASK_S; + jc->simple_state.buttons |= ((int)(packet[indexOffset+5] >> 6) << JSOFFSET_E) & JSMASK_E; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6] >> 6) << JSOFFSET_LCLICK) & JSMASK_LCLICK; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6] >> 7) << JSOFFSET_RCLICK) & JSMASK_RCLICK; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6] >> 5) << JSOFFSET_OPTIONS) & JSMASK_OPTIONS; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6] >> 4) << JSOFFSET_SHARE) & JSMASK_SHARE; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6] >> 1) << JSOFFSET_R) & JSMASK_R; + jc->simple_state.buttons |= ((int)(packet[indexOffset+6]) << JSOFFSET_L) & JSMASK_L; + jc->simple_state.buttons |= ((int)(packet[indexOffset+7]) << JSOFFSET_PS) & JSMASK_PS; + jc->simple_state.buttons |= ((int)(packet[indexOffset+7] >> 1) << JSOFFSET_TOUCHPAD_CLICK) & JSMASK_TOUCHPAD_CLICK; + //jc->btns.zr = (packet[indexOffset+6] >> 3) & 1; + //jc->btns.zl = (packet[indexOffset+6] >> 2) & 1; + jc->simple_state.rTrigger = packet[indexOffset+9] / 255.0f; + jc->simple_state.lTrigger = packet[indexOffset+8] / 255.0f; + + if (jc->simple_state.rTrigger > 0.0) jc->simple_state.buttons |= JSMASK_ZR; + if (jc->simple_state.lTrigger > 0.0) jc->simple_state.buttons |= JSMASK_ZL; + + uint16_t stick_x = packet[indexOffset+1]; + uint16_t stick_y = packet[indexOffset+2]; + stick_y = 255 - stick_y; + + uint16_t stick2_x = packet[indexOffset+3]; + uint16_t stick2_y = packet[indexOffset+4]; + stick2_y = 255 - stick2_y; + + jc->simple_state.stickLX = (std::fmin)(1.0f, (stick_x - 127.0f) / 127.0f); + jc->simple_state.stickLY = (std::fmin)(1.0f, (stick_y - 127.0f) / 127.0f); + jc->simple_state.stickRX = (std::fmin)(1.0f, (stick2_x - 127.0f) / 127.0f); + jc->simple_state.stickRY = (std::fmin)(1.0f, (stick2_y - 127.0f) / 127.0f); + + jc->modifying_lock.lock(); + jc->push_sensor_samples(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ, + imu_state.accelX, imu_state.accelY, imu_state.accelZ, jc->delta_time); + + jc->get_calibrated_gyro(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ); + jc->modifying_lock.unlock(); + + jc->imu_state = imu_state; + } + + //printf("Buttons: %d LX: %.5f LY: %.5f RX: %.5f RY: %.5f GX: %.4f GY: %.4f GZ: %.4f\n", \ + // jc->simple_state.buttons, (jc->simple_state.stickLX + 1), (jc->simple_state.stickLY + 1), (jc->simple_state.stickRX + 1), (jc->simple_state.stickRY + 1), jc->imu_state.gyroX, jc->imu_state.gyroY, jc->imu_state.gyroZ); + + + return true; + } + + if (jc->controller_type == ControllerType::s_ds) { + //printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + // packet[0], packet[1], packet[2], packet[3], packet[4], packet[5], packet[6], packet[7], packet[8], packet[9], + // packet[10], packet[11], packet[12], packet[13], packet[14], packet[15], packet[16], packet[17], packet[18], packet[19], packet[20], + // packet[21], packet[22], packet[23], packet[24], packet[25], packet[26], packet[27], packet[28], packet[29], packet[30], + // packet[31], packet[32], packet[33], packet[34], packet[35], packet[36], packet[37], packet[38], packet[39], packet[40], + // packet[41], packet[42], packet[43], packet[44], packet[45], packet[46], packet[47], packet[48], packet[49], packet[50]); + int indexOffset = 1; + if(!jc->is_usb) { + indexOffset = 2; + } + + // Gyroscope: + // Gyroscope data is relative (degrees/s) + int16_t gyroSampleX = uint16_to_int16(packet[indexOffset + 15] | (packet[indexOffset + 16] << 8) & 0xFF00); + int16_t gyroSampleY = uint16_to_int16(packet[indexOffset + 17] | (packet[indexOffset + 18] << 8) & 0xFF00); + int16_t gyroSampleZ = uint16_to_int16(packet[indexOffset + 19] | (packet[indexOffset + 20] << 8) & 0xFF00); + int16_t accelSampleX = uint16_to_int16(packet[indexOffset + 21] | (packet[indexOffset + 22] << 8) & 0xFF00); + int16_t accelSampleY = uint16_to_int16(packet[indexOffset + 23] | (packet[indexOffset + 24] << 8) & 0xFF00); + int16_t accelSampleZ = uint16_to_int16(packet[indexOffset + 25] | (packet[indexOffset + 26] << 8) & 0xFF00); + + if ((gyroSampleX | gyroSampleY | gyroSampleZ | accelSampleX | accelSampleY | accelSampleZ) == 0) { + // all zero? + hasIMU = false; + } + + // convert to real units + imu_state.gyroX = (float) (gyroSampleX) * (2000.0f / 32767.0f); + imu_state.gyroY = (float) (gyroSampleY) * (2000.0f / 32767.0f); + imu_state.gyroZ = (float) (gyroSampleZ) * (2000.0f / 32767.0f); + + imu_state.accelX = (float) (accelSampleX) / 8192.0f; + imu_state.accelY = (float) (accelSampleY) / 8192.0f; + imu_state.accelZ = (float) (accelSampleZ) / 8192.0f; + + //printf("DS accel: %.4f, %.4f, %.4f\n", imu_state.accelX, imu_state.accelY, imu_state.accelZ); + + //printf("%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%d\n", + // jc->gyro.yaw, jc->gyro.pitch, jc->gyro.roll, jc->accel.x, jc->accel.y, jc->accel.z, universal_counter++); + + // Touchpad: + jc->last_touch_state = jc->touch_state; + + jc->touch_state.t0Id = (int) (packet[indexOffset + 32] & 0x7F); + jc->touch_state.t1Id = (int) (packet[indexOffset + 36] & 0x7F); + jc->touch_state.t0Down = (packet[indexOffset + 32] & 0x80) == 0; + jc->touch_state.t1Down = (packet[indexOffset + 36] & 0x80) == 0; + + jc->touch_state.t0X = (packet[indexOffset + 33] | (packet[indexOffset + 34] & 0x0F) << 8) / 1920.0f; + jc->touch_state.t0Y = ((packet[indexOffset + 34] & 0xF0) >> 4 | packet[indexOffset + 35] << 4) / 943.0f; + jc->touch_state.t1X = (packet[indexOffset + 37] | (packet[indexOffset + 38] & 0x0F) << 8) / 1920.0f; + jc->touch_state.t1Y = ((packet[indexOffset + 38] & 0xF0) >> 4 | packet[indexOffset + 39] << 4) / 943.0f; + + //printf("DS touch: %d, %d, %d, %d, %.4f, %.4f, %.4f, %.4f\n", + // jc->touch_state.t0Id, jc->touch_state.t1Id, jc->touch_state.t0Down, jc->touch_state.t1Down, + // jc->touch_state.t0X, jc->touch_state.t0Y, jc->touch_state.t1X, jc->touch_state.t1Y); + + // DS dpad is a hat... 0x08 is released, 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW + // http://eleccelerator.com/wiki/index.php?title=DualShock_4 + uint8_t hat = packet[indexOffset + 7] & 0x0f; + + if ((hat > 2) & (hat < 6)) jc->simple_state.buttons |= JSMASK_DOWN; // down = SE | S | SW + if ((hat == 7) | (hat < 2)) jc->simple_state.buttons |= JSMASK_UP; // up = N | NE | NW + if ((hat > 0) & (hat < 4)) jc->simple_state.buttons |= JSMASK_RIGHT; // right = NE | E | SE + if ((hat > 4) & (hat < 8)) jc->simple_state.buttons |= JSMASK_LEFT; // left = SW | W | NW + + jc->simple_state.buttons |= ((int) (packet[indexOffset + 7] >> 4) << JSOFFSET_W) & JSMASK_W; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 7] >> 7) << JSOFFSET_N) & JSMASK_N; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 7] >> 5) << JSOFFSET_S) & JSMASK_S; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 7] >> 6) << JSOFFSET_E) & JSMASK_E; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8] >> 6) << JSOFFSET_LCLICK) & JSMASK_LCLICK; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8] >> 7) << JSOFFSET_RCLICK) & JSMASK_RCLICK; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8] >> 5) << JSOFFSET_OPTIONS) & JSMASK_OPTIONS; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8] >> 4) << JSOFFSET_SHARE) & JSMASK_SHARE; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8] >> 1) << JSOFFSET_R) & JSMASK_R; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 8]) << JSOFFSET_L) & JSMASK_L; + jc->simple_state.buttons |= ((int) (packet[indexOffset + 9]) << JSOFFSET_PS) & JSMASK_PS; + // The DS5 has a mute button that is normally ignored on PC. We can use this. + jc->simple_state.buttons |= ((int) (packet[indexOffset + 9] >> 2) << JSOFFSET_MIC) & JSMASK_MIC; + jc->simple_state.buttons |= + ((int) (packet[indexOffset + 9] >> 1) << JSOFFSET_TOUCHPAD_CLICK) & JSMASK_TOUCHPAD_CLICK; + //jc->btns.zr = (packet[indexOffset+6] >> 3) & 1; + //jc->btns.zl = (packet[indexOffset+6] >> 2) & 1; + jc->simple_state.rTrigger = packet[indexOffset + 5] / 255.0f; + jc->simple_state.lTrigger = packet[indexOffset + 4] / 255.0f; + + if (jc->simple_state.rTrigger > 0.0) jc->simple_state.buttons |= JSMASK_ZR; + if (jc->simple_state.lTrigger > 0.0) jc->simple_state.buttons |= JSMASK_ZL; + + uint16_t stick_x = packet[indexOffset + 0]; + uint16_t stick_y = packet[indexOffset + 1]; + + stick_y = 255 - stick_y; + + uint16_t stick2_x = packet[indexOffset + 2]; + uint16_t stick2_y = packet[indexOffset + 3]; + stick2_y = 255 - stick2_y; + + jc->simple_state.stickLX = (std::fmin)(1.0f, (stick_x - 127.0f) / 127.0f); + jc->simple_state.stickLY = (std::fmin)(1.0f, (stick_y - 127.0f) / 127.0f); + jc->simple_state.stickRX = (std::fmin)(1.0f, (stick2_x - 127.0f) / 127.0f); + jc->simple_state.stickRY = (std::fmin)(1.0f, (stick2_y - 127.0f) / 127.0f); + + jc->modifying_lock.lock(); + jc->push_sensor_samples(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ, + imu_state.accelX, imu_state.accelY, imu_state.accelZ, jc->delta_time); + + jc->get_calibrated_gyro(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ); + jc->modifying_lock.unlock(); + + jc->imu_state = imu_state; + + return true; + } + + // most of this JoyCon and Pro Controller stuff is adapted from MFosse's Joycon driver. + + // bluetooth button pressed packet: + if (packet[0] == 0x3F) { + + //uint16_t old_buttons = jc->buttons; + //int8_t old_dstick = jc->dstick; + + jc->dstick = packet[3]; + // todo: get button states here aswell: + } + + int buttons_pressed = 0; + + // input update packet: + // 0x21 is just buttons, 0x30 includes gyro, 0x31 includes NFC (large packet size) + if (packet[0] == 0x21 || packet[0] == 0x30 || packet[0] == 0x31) { + + // offset for usb or bluetooth data: + /*int offset = settings.usingBluetooth ? 0 : 10;*/ + int offset = 0; + //int offset = !jc->is_usb ? 0 : 10; + + uint8_t *btn_data = packet + offset + 3; + + // get button states: + { + uint16_t states = 0; + uint16_t states2 = 0; + + // Left JoyCon: + if (jc->left_right == 1) { + states = (btn_data[1] << 8) | (btn_data[2] & 0xFF); + // Right JoyCon: + } + else if (jc->left_right == 2) { + states = (btn_data[1] << 8) | (btn_data[0] & 0xFF); + // Pro Controller: + } + else if (jc->left_right == 3) { + states = (btn_data[1] << 8) | (btn_data[2] & 0xFF); + states2 = (btn_data[1] << 8) | (btn_data[0] & 0xFF); + } + + buttons_pressed = states; + // Pro Controller: + if (jc->left_right == 3) { + buttons_pressed |= states2 << 16; + + // fix some non-sense the Pro Controller does + // clear nth bit + //num &= ~(1UL << n); + buttons_pressed &= ~(1L << 9); + buttons_pressed &= ~(1L << 12); + buttons_pressed &= ~(1L << 14); + + buttons_pressed &= ~(1UL << (8 + 16)); + buttons_pressed &= ~(1UL << (11 + 16)); + buttons_pressed &= ~(1UL << (13 + 16)); + } + else if (jc->left_right == 2) { + buttons_pressed = buttons_pressed << 16; + } + } + + // get stick data: + uint8_t *stick_data = packet + offset; + if (jc->left_right == 1) { + stick_data += 6; + } + else if (jc->left_right == 2) { + stick_data += 9; + } + + uint16_t stick_x = stick_data[0] | ((stick_data[1] & 0xF) << 8); + uint16_t stick_y = (stick_data[1] >> 4) | (stick_data[2] << 4); + + // use calibration data: + if (jc->left_right == 1) { + jc->CalcAnalogStick2(jc->simple_state.stickLX, jc->simple_state.stickLY, + stick_x, + stick_y, + jc->stick_cal_x_l, + jc->stick_cal_y_l); + } + else if (jc->left_right == 2) { + jc->CalcAnalogStick2(jc->simple_state.stickRX, jc->simple_state.stickRY, + stick_x, + stick_y, + jc->stick_cal_x_r, + jc->stick_cal_y_r); + } + else if (jc->left_right == 3) { + // pro controller + stick_data += 6; + //printf("%d, %d\n", + // jc->stick_cal_x_l, + // jc->stick_cal_y_l); + uint16_t stick_x = stick_data[0] | ((stick_data[1] & 0xF) << 8); + uint16_t stick_y = (stick_data[1] >> 4) | (stick_data[2] << 4); + jc->CalcAnalogStick2(jc->simple_state.stickLX, jc->simple_state.stickLY, + stick_x, + stick_y, + jc->stick_cal_x_l, + jc->stick_cal_y_l); + stick_data += 3; + uint16_t stick_x2 = stick_data[0] | ((stick_data[1] & 0xF) << 8); + uint16_t stick_y2 = (stick_data[1] >> 4) | (stick_data[2] << 4); + jc->CalcAnalogStick2(jc->simple_state.stickRX, jc->simple_state.stickRY, + stick_x2, + stick_y2, + jc->stick_cal_x_r, + jc->stick_cal_y_r); + } + + jc->battery = (stick_data[1] & 0xF0) >> 4; + //printf("JoyCon battery: %d\n", jc->battery); + + // Accelerometer: + // Accelerometer data is absolute + { + // get accelerometer X: + float accelSampleZ = (float)uint16_to_int16(packet[13] | (packet[14] << 8) & 0xFF00) * jc->acc_cal_coeff[0]; + float accelSampleX = (float)uint16_to_int16(packet[15] | (packet[16] << 8) & 0xFF00) * jc->acc_cal_coeff[1]; + float accelSampleY = (float)uint16_to_int16(packet[17] | (packet[18] << 8) & 0xFF00) * jc->acc_cal_coeff[2]; + float gyroSampleX = (float)uint16_to_int16(packet[19] | (packet[20] << 8) & 0xFF00) * jc->gyro_cal_coeff[0]; + float gyroSampleY = (float)uint16_to_int16(packet[21] | (packet[22] << 8) & 0xFF00) * jc->gyro_cal_coeff[1]; + float gyroSampleZ = (float)uint16_to_int16(packet[23] | (packet[24] << 8) & 0xFF00) * jc->gyro_cal_coeff[2]; + + if (gyroSampleX == 0.f && gyroSampleY == 0.f && gyroSampleZ == 0.f && accelSampleX == 0.f && accelSampleY == 0.f && accelSampleZ == 0.f) + { + // all zero? + hasIMU = false; + } + + //jc->push_sensor_samples(accelSampleX, accelSampleY, accelSampleZ, gyroSampleX, gyroSampleY, gyroSampleZ); + float accelX = accelSampleX; + float accelY = accelSampleY; + float accelZ = accelSampleZ; + float totalGyroX = gyroSampleX - jc->sensor_cal[1][0]; + float totalGyroY = gyroSampleY - jc->sensor_cal[1][1]; + float totalGyroZ = gyroSampleZ - jc->sensor_cal[1][2]; + // each packet actually has 3 samples worth of data, so collect sample 2 + accelSampleZ = (float)uint16_to_int16(packet[25] | (packet[26] << 8) & 0xFF00) * jc->acc_cal_coeff[0]; + accelSampleX = (float)uint16_to_int16(packet[27] | (packet[28] << 8) & 0xFF00) * jc->acc_cal_coeff[1]; + accelSampleY = (float)uint16_to_int16(packet[29] | (packet[30] << 8) & 0xFF00) * jc->acc_cal_coeff[2]; + gyroSampleX = (float)uint16_to_int16(packet[31] | (packet[32] << 8) & 0xFF00) * jc->gyro_cal_coeff[0]; + gyroSampleY = (float)uint16_to_int16(packet[33] | (packet[34] << 8) & 0xFF00) * jc->gyro_cal_coeff[1]; + gyroSampleZ = (float)uint16_to_int16(packet[35] | (packet[36] << 8) & 0xFF00) * jc->gyro_cal_coeff[2]; + //jc->push_sensor_samples(accelSampleX, accelSampleY, accelSampleZ, gyroSampleX, gyroSampleY, gyroSampleZ); + accelX += accelSampleX; + accelY += accelSampleY; + accelZ += accelSampleZ; + totalGyroX += gyroSampleX - jc->sensor_cal[1][0]; + totalGyroY += gyroSampleY - jc->sensor_cal[1][1]; + totalGyroZ += gyroSampleZ - jc->sensor_cal[1][2]; + // ... and sample 3 + accelSampleZ = (float)uint16_to_int16(packet[37] | (packet[38] << 8) & 0xFF00) * jc->acc_cal_coeff[0]; + accelSampleX = (float)uint16_to_int16(packet[39] | (packet[40] << 8) & 0xFF00) * jc->acc_cal_coeff[1]; + accelSampleY = (float)uint16_to_int16(packet[41] | (packet[42] << 8) & 0xFF00) * jc->acc_cal_coeff[2]; + gyroSampleX = (float)uint16_to_int16(packet[43] | (packet[44] << 8) & 0xFF00) * jc->gyro_cal_coeff[0]; + gyroSampleY = (float)uint16_to_int16(packet[45] | (packet[46] << 8) & 0xFF00) * jc->gyro_cal_coeff[1]; + gyroSampleZ = (float)uint16_to_int16(packet[47] | (packet[48] << 8) & 0xFF00) * jc->gyro_cal_coeff[2]; + //jc->push_sensor_samples(accelSampleX, accelSampleY, accelSampleZ, gyroSampleX, gyroSampleY, gyroSampleZ); + accelX += accelSampleX; + accelY += accelSampleY; + accelZ += accelSampleZ; + totalGyroX += gyroSampleX - jc->sensor_cal[1][0]; + totalGyroY += gyroSampleY - jc->sensor_cal[1][1]; + totalGyroZ += gyroSampleZ - jc->sensor_cal[1][2]; + // average the 3 samples + accelX /= 3; + accelY /= 3; + accelZ /= 3; + totalGyroX /= 3; + totalGyroY /= 3; + totalGyroZ /= 3; + imu_state.accelX = -accelX; + imu_state.accelY = accelY; + imu_state.accelZ = -accelZ; + imu_state.gyroX = -totalGyroY; + imu_state.gyroY = totalGyroZ; + imu_state.gyroZ = totalGyroX; + + //printf("Switch accel: %.4f, %.4f, %.4f\n", imu_state.accelX, imu_state.accelY, imu_state.accelZ); + } + + } + + // handle buttons + { + // left: + if (jc->left_right == 1) { + jc->simple_state.buttons |= ((buttons_pressed >> 1) << JSOFFSET_UP) & JSMASK_UP; + jc->simple_state.buttons |= ((buttons_pressed) << JSOFFSET_DOWN) & JSMASK_DOWN; + jc->simple_state.buttons |= ((buttons_pressed >> 3) << JSOFFSET_LEFT) & JSMASK_LEFT; + jc->simple_state.buttons |= ((buttons_pressed >> 2) << JSOFFSET_RIGHT) & JSMASK_RIGHT; + jc->simple_state.buttons |= ((buttons_pressed >> 11) << JSOFFSET_LCLICK) & JSMASK_LCLICK; + jc->simple_state.buttons |= ((buttons_pressed >> 8) << JSOFFSET_MINUS) & JSMASK_MINUS; + jc->simple_state.buttons |= ((buttons_pressed >> 6) << JSOFFSET_L) & JSMASK_L; + jc->simple_state.buttons |= ((buttons_pressed >> 13) << JSOFFSET_CAPTURE) & JSMASK_CAPTURE; + jc->simple_state.lTrigger = (float)((buttons_pressed >> 7) & 1); + jc->simple_state.buttons |= ((int)(jc->simple_state.lTrigger) << JSOFFSET_ZL) & JSMASK_ZL; + jc->simple_state.buttons |= ((buttons_pressed >> 5) << JSOFFSET_SL) & JSMASK_SL; + jc->simple_state.buttons |= ((buttons_pressed >> 4) << JSOFFSET_SR) & JSMASK_SR; + + // just need to negate gyroZ + imu_state.gyroZ = -imu_state.gyroZ; + } + + // right: + if (jc->left_right == 2) { + jc->simple_state.buttons |= ((buttons_pressed >> 16) << JSOFFSET_W) & JSMASK_W; + jc->simple_state.buttons |= ((buttons_pressed >> 17) << JSOFFSET_N) & JSMASK_N; + jc->simple_state.buttons |= ((buttons_pressed >> 18) << JSOFFSET_S) & JSMASK_S; + jc->simple_state.buttons |= ((buttons_pressed >> 19) << JSOFFSET_E) & JSMASK_E; + jc->simple_state.buttons |= ((buttons_pressed >> 26) << JSOFFSET_RCLICK) & JSMASK_RCLICK; + jc->simple_state.buttons |= ((buttons_pressed >> 25) << JSOFFSET_PLUS) & JSMASK_PLUS; + jc->simple_state.buttons |= ((buttons_pressed >> 22) << JSOFFSET_R) & JSMASK_R; + jc->simple_state.buttons |= ((buttons_pressed >> 28) << JSOFFSET_HOME) & JSMASK_HOME; + jc->simple_state.rTrigger = (float)((buttons_pressed >> 23) & 1); + jc->simple_state.buttons |= ((int)(jc->simple_state.rTrigger) << JSOFFSET_ZR) & JSMASK_ZR; + jc->simple_state.buttons |= ((buttons_pressed >> 21) << JSOFFSET_SL) & JSMASK_SL; + jc->simple_state.buttons |= ((buttons_pressed >> 20) << JSOFFSET_SR) & JSMASK_SR; + + // for some reason we need to negate x and y, and z on the right joycon + imu_state.gyroX = -imu_state.gyroX; + imu_state.gyroY = -imu_state.gyroY; + imu_state.gyroZ = -imu_state.gyroZ; + + imu_state.accelX = -imu_state.accelX; + imu_state.accelY = -imu_state.accelY; + + } + + // pro controller: + if (jc->left_right == 3) { + jc->simple_state.buttons |= ((buttons_pressed >> 1) << JSOFFSET_UP) & JSMASK_UP; + jc->simple_state.buttons |= ((buttons_pressed) << JSOFFSET_DOWN) & JSMASK_DOWN; + jc->simple_state.buttons |= ((buttons_pressed >> 3) << JSOFFSET_LEFT) & JSMASK_LEFT; + jc->simple_state.buttons |= ((buttons_pressed >> 2) << JSOFFSET_RIGHT) & JSMASK_RIGHT; + jc->simple_state.buttons |= ((buttons_pressed >> 16) << JSOFFSET_W) & JSMASK_W; + jc->simple_state.buttons |= ((buttons_pressed >> 17) << JSOFFSET_N) & JSMASK_N; + jc->simple_state.buttons |= ((buttons_pressed >> 18) << JSOFFSET_S) & JSMASK_S; + jc->simple_state.buttons |= ((buttons_pressed >> 19) << JSOFFSET_E) & JSMASK_E; + jc->simple_state.buttons |= ((buttons_pressed >> 11) << JSOFFSET_LCLICK) & JSMASK_LCLICK; + jc->simple_state.buttons |= ((buttons_pressed >> 26) << JSOFFSET_RCLICK) & JSMASK_RCLICK; + jc->simple_state.buttons |= ((buttons_pressed >> 25) << JSOFFSET_PLUS) & JSMASK_PLUS; + jc->simple_state.buttons |= ((buttons_pressed >> 8) << JSOFFSET_MINUS) & JSMASK_MINUS; + jc->simple_state.buttons |= ((buttons_pressed >> 22) << JSOFFSET_R) & JSMASK_R; + jc->simple_state.buttons |= ((buttons_pressed >> 6) << JSOFFSET_L) & JSMASK_L; + jc->simple_state.buttons |= ((buttons_pressed >> 28) << JSOFFSET_HOME) & JSMASK_HOME; + jc->simple_state.buttons |= ((buttons_pressed >> 13) << JSOFFSET_CAPTURE) & JSMASK_CAPTURE; + jc->simple_state.rTrigger = (float)((buttons_pressed >> 23) & 1); + jc->simple_state.lTrigger = (float)((buttons_pressed >> 7) & 1); + jc->simple_state.buttons |= ((int)(jc->simple_state.lTrigger) << JSOFFSET_ZL) & JSMASK_ZL; + jc->simple_state.buttons |= ((int)(jc->simple_state.rTrigger) << JSOFFSET_ZR) & JSMASK_ZR; + + // just need to negate gyroZ + imu_state.gyroZ = -imu_state.gyroZ; + } + } + + jc->modifying_lock.lock(); + jc->push_sensor_samples(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ, + imu_state.accelX, imu_state.accelY, imu_state.accelZ, jc->delta_time); + + jc->get_calibrated_gyro(imu_state.gyroX, imu_state.gyroY, imu_state.gyroZ); + jc->modifying_lock.unlock(); + + jc->imu_state = imu_state; + + return true; +} diff --git a/project/dependencies/JoyShockLibrary/JoyShock.cpp b/project/dependencies/JoyShockLibrary/JoyShock.cpp new file mode 100644 index 0000000..e935137 --- /dev/null +++ b/project/dependencies/JoyShockLibrary/JoyShock.cpp @@ -0,0 +1,1564 @@ +#pragma once + +#include "JoyShockLibrary.h" +#include +#include "hidapi.h" +#include +#include +#include +#include +#include "tools.cpp" +#include + +#ifdef __GNUC__ +#define _wcsdup wcsdup +#endif + +enum ControllerType { n_switch, s_ds4, s_ds }; + +// PS5 stuff +#define DS_VENDOR 0x054C +#define DS_USB 0x0CE6 +#define DS_USB_V2 0x0DF2 // DualSense Edge + +// PS4 stuff +// http://www.psdevwiki.com/ps4/DS4-USB +// http://www.psdevwiki.com/ps4/DS4-BT +// http://eleccelerator.com/wiki/index.php?title=DualShock_4 +// and a little bit of https://github.com/chrippa/ds4drv +#define DS4_VENDOR 0x054C +#define DS4_USB 0x05C4 +#define DS4_USB_V2 0x09CC +#define DS4_USB_DONGLE 0x0BA0 +#define DS4_BT 0x081F +// DS4 compatible controllers +#define BROOK_DS4_VENDOR 0x0C12 +#define BROOK_DS4_USB 0x0E20 + +// Joycon and Pro conroller stuff is mostly from +// https://github.com/mfosse/JoyCon-Driver +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/ +#define JOYCON_VENDOR 0x057e +#define JOYCON_L_BT 0x2006 +#define JOYCON_R_BT 0x2007 +#define PRO_CONTROLLER 0x2009 +#define JOYCON_CHARGING_GRIP 0x200e +#define L_OR_R(lr) (lr == 1 ? 'L' : (lr == 2 ? 'R' : '?')) + +class JoyShock { + +public: + + hid_device * handle; + int intHandle = 0; + std::string path; + + wchar_t *serial; + + std::string name; + + int deviceNumber = 0;// left(0) or right(1) vjoy + + int left_right = 0;// 1: left joycon, 2: right joycon, 3: pro controller + + std::chrono::steady_clock::time_point last_polled; + float delta_time = 1.0; + + JOY_SHOCK_STATE simple_state = {}; + JOY_SHOCK_STATE last_simple_state = {}; + + IMU_STATE imu_state = {}; + IMU_STATE last_imu_state = {}; + + TOUCH_STATE touch_state = {}; + TOUCH_STATE last_touch_state = {}; + + GamepadMotion motion; + + float cumulative_gyro_x = 0.f; + float cumulative_gyro_y = 0.f; + float cumulative_gyro_z = 0.f; + int num_cumulative_gyro_samples = 0; + + int gyroSpace = 0; + + std::mutex modifying_lock; + + int8_t dstick; + uint8_t battery; + + int global_count = 0; + + // calibration data: + struct brcm_hdr { + uint8_t cmd; + uint8_t rumble[9]; + }; + + struct brcm_cmd_01 { + uint8_t subcmd; + uint32_t offset; + uint8_t size; + }; + + int timing_byte = 0x0; + + float acc_cal_coeff[3] = {0.0f, 0.0f, 0.0f}; + float gyro_cal_coeff[3] = {0.0f, 0.0f, 0.0f}; + float cal_x[1] = { 0.0f }; + float cal_y[1] = { 0.0f }; + + bool initialised = false; + + bool has_user_cal_stick_l = false; + bool has_user_cal_stick_r = false; + bool has_user_cal_sensor = false; + + ControllerType controller_type = ControllerType::n_switch; + bool is_usb = false; + + unsigned char small_rumble = 0; + unsigned char big_rumble = 0; + unsigned char led_r = 0; + unsigned char led_g = 0; + unsigned char led_b = 0; + + unsigned int body_colour = 0xFFFFFF; + unsigned int button_colour = 0xFFFFFF; + unsigned int left_grip_colour = 0xFFFFFF; + unsigned int right_grip_colour = 0xFFFFFF; + + int player_number = 0; + int reuse_counter = 0; + + bool cancel_thread = false; + bool delete_on_finish = false; + bool remove_on_finish = true; + std::thread* thread = nullptr; + + // for calibration: + bool use_continuous_calibration = false; + bool cue_motion_reset = false; + + unsigned char factory_stick_cal[0x12]; + unsigned char device_colours[0xC]; + unsigned char user_stick_cal[0x16]; + unsigned char sensor_model[0x6]; + unsigned char stick_model[0x24]; + unsigned char factory_sensor_cal[0x18]; + unsigned char user_sensor_cal[0x1A]; + uint16_t factory_sensor_cal_calm[0xC]; + uint16_t user_sensor_cal_calm[0xC]; + int16_t sensor_cal[0x2][0x3]; + uint16_t stick_cal_x_l[0x3]; + uint16_t stick_cal_y_l[0x3]; + uint16_t stick_cal_x_r[0x3]; + uint16_t stick_cal_y_r[0x3]; + + uint32_t crc_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + + //// https://docs.microsoft.com/en-us/openspecs/office_protocols/ms-abs/06966aa2-70da-4bf9-8448-3355f277cd77 + uint32_t crc_32(unsigned char* buf, int length) { + uint32_t result = 0xFFFFFFFF; + int index = 0; + while (index < length) { + result = crc_table[(result & 0xFF) ^ buf[index]] ^ (result >> 8); + index++; + } + return result ^ 0xFFFFFFFF; + } + + void enable_gyro_ds4_bt(unsigned char *buf, int bufLength) + { + // gyro is enabled by getting feature report 0x05 on BT controllers. + // in addition, this request is also responsible for getting current calibration info. + buf[0] = 0x05; // controller calibration request for BT + + hid_get_feature_report(handle, buf, 41); + //hid_write(handle, buf, 38); + //hid_read_timeout(handle, buf, bufLength, 100); + } + +public: + void init(struct hid_device_info *dev, hid_device* inHandle, int uniqueHandle, const std::string &inPath) { + this->path = inPath; + + if (dev->product_id == JOYCON_CHARGING_GRIP) { + + if (dev->interface_number == 0 || dev->interface_number == -1) { + this->name = std::string("Joy-Con (R)"); + this->left_right = 2;// right joycon + this->is_usb = true; + } + else if (dev->interface_number == 1) { + this->name = std::string("Joy-Con (L)"); + this->left_right = 1;// left joycon + this->is_usb = true; + } + } + + if (dev->product_id == JOYCON_L_BT) { + this->name = std::string("Joy-Con (L)"); + this->left_right = 1;// left joycon + } + else if (dev->product_id == JOYCON_R_BT) { + this->name = std::string("Joy-Con (R)"); + this->left_right = 2;// right joycon + } + else if (dev->product_id == PRO_CONTROLLER) { + this->name = std::string("Pro Controller"); + this->left_right = 3;// left joycon + } + + if (dev->product_id == DS4_BT || + dev->product_id == DS4_USB || + dev->product_id == DS4_USB_DONGLE || + dev->product_id == DS4_USB_V2) { + this->name = std::string("DualShock 4"); + this->left_right = 3; // left and right? + this->controller_type = ControllerType::s_ds4; + this->is_usb = (dev->product_id != DS4_BT); + } + + if (dev->product_id == BROOK_DS4_USB) { + this->name = std::string("DualShock 4"); + this->left_right = 3; // left and right? + this->controller_type = ControllerType::s_ds4; + this->is_usb = true; // this controller is wired + } + + if (dev->product_id == DS_USB || + dev->product_id == DS_USB_V2) { + this->name = std::string("DualSense"); + this->left_right = 3; // left and right? + this->controller_type = ControllerType::s_ds; + this->is_usb = true; // for now, only usb + } + + this->serial = _wcsdup(dev->serial_number); + this->intHandle = uniqueHandle; + + //printf("Found device %c: %ls %s\n", L_OR_R(this->left_right), this->serial, dev->path); + this->handle = inHandle; + + if (this->controller_type == ControllerType::s_ds4) { + unsigned char buf[64]; + memset(buf, 0, 64); + + enable_gyro_ds4_bt(buf, 64); + + hid_read_timeout(handle, buf, 64, 100); + // choose between BT and USB + if (buf[0] == 0x11) { + this->is_usb = false; + } + } + else if (this->controller_type == ControllerType::s_ds) { + unsigned char buf[64]; + memset(buf, 0, 64); + + // We can reuse the same command on the DS5 to enable Full Mode. + enable_gyro_ds4_bt(buf, 64); + + hid_read_timeout(handle, buf, 64, 100); + + // The DS's protocol is literally so similar to the DS4 that we can reuse the same reports to get the same results. + // Meet the new boss - the same as the old boss. + if (buf[0] == 0x31) { + this->is_usb = false; + } + + } + } + + JoyShock(struct hid_device_info* dev, hid_device* inHandle, int uniqueHandle, const std::string& inPath) { + init(dev, inHandle, uniqueHandle, inPath); + + // initialise continuous calibration windows + reset_continuous_calibration(); + } + + ~JoyShock() { + if (handle != nullptr) { + hid_close(handle); + } + } + + void push_cumulative_gyro(float gyroX, float gyroY, float gyroZ) { + modifying_lock.lock(); + if (num_cumulative_gyro_samples == 0) { + cumulative_gyro_x = 0.f; + cumulative_gyro_y = 0.f; + cumulative_gyro_z = 0.f; + } + cumulative_gyro_x += gyroX; + cumulative_gyro_y += gyroY; + cumulative_gyro_z += gyroZ; + num_cumulative_gyro_samples++; + modifying_lock.unlock(); + } + + void get_and_flush_cumulative_gyro(float& gyroX, float& gyroY, float& gyroZ) { + modifying_lock.lock(); + if (num_cumulative_gyro_samples == 0) { + gyroX = cumulative_gyro_x; + gyroX = cumulative_gyro_y; + gyroX = cumulative_gyro_z; + } + else { + gyroX = cumulative_gyro_x / num_cumulative_gyro_samples; + gyroY = cumulative_gyro_y / num_cumulative_gyro_samples; + gyroZ = cumulative_gyro_z / num_cumulative_gyro_samples; + num_cumulative_gyro_samples = 0; + // so that we don't return zeroes before we receive a new sample, store this for next time: + cumulative_gyro_x = gyroX; + cumulative_gyro_y = gyroY; + cumulative_gyro_z = gyroZ; + } + float gravX, gravY, gravZ; + motion.GetGravity(gravX, gravY, gravZ); + modifying_lock.unlock(); + switch (gyroSpace) + { + default: + case 0: + break; + case 1: + GamepadMotion::CalculateWorldSpaceGyro(gyroX, gyroY, gyroX, gyroY, gyroZ, gravX, gravY, gravZ); + gyroZ = 0.f; + break; + case 2: + GamepadMotion::CalculatePlayerSpaceGyro(gyroX, gyroY, gyroX, gyroY, gyroZ, gravX, gravY, gravZ); + gyroZ = 0.f; + break; + } + } + + void reset_continuous_calibration() { + modifying_lock.lock(); + motion.ResetContinuousCalibration(); + modifying_lock.unlock(); + } + + void push_sensor_samples(float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ, float deltaTime) { + motion.ProcessMotion(gyroX, gyroY, gyroZ, accelX, accelY, accelZ, deltaTime); + } + + void get_calibrated_gyro(float& gyroX, float& gyroY, float& gyroZ) + { + motion.GetCalibratedGyro(gyroX, gyroY, gyroZ); + } + + MOTION_STATE get_motion_state() + { + MOTION_STATE motionState = MOTION_STATE(); + modifying_lock.lock(); + motion.GetProcessedAcceleration(motionState.accelX, motionState.accelY, motionState.accelZ); + motion.GetOrientation(motionState.quatW, motionState.quatX, motionState.quatY, motionState.quatZ); + motion.GetGravity(motionState.gravX, motionState.gravY, motionState.gravZ); + modifying_lock.unlock(); + return motionState; + } + + IMU_STATE get_transformed_imu_state(IMU_STATE& imu_state) + { + float gyroX, gyroY, gyroZ, gravX, gravY, gravZ; + modifying_lock.lock(); + motion.GetGravity(gravX, gravY, gravZ); + gyroX = imu_state.gyroX; + gyroY = imu_state.gyroY; + gyroZ = imu_state.gyroZ; + modifying_lock.unlock(); + switch (gyroSpace) + { + default: + case 0: + break; + case 1: + GamepadMotion::CalculateWorldSpaceGyro(gyroX, gyroY, gyroX, gyroY, gyroZ, gravX, gravY, gravZ); + gyroZ = 0.f; + break; + case 2: + GamepadMotion::CalculatePlayerSpaceGyro(gyroX, gyroY, gyroX, gyroY, gyroZ, gravX, gravY, gravZ); + gyroZ = 0.f; + break; + } + IMU_STATE transformedState = IMU_STATE(); + transformedState.accelX = imu_state.accelX; + transformedState.accelY = imu_state.accelY; + transformedState.accelZ = imu_state.accelZ; + transformedState.gyroX = gyroX; + transformedState.gyroY = gyroY; + transformedState.gyroZ = gyroZ; + return transformedState; + } + + bool hid_exchange(hid_device *handle, unsigned char *buf, int len) { + if (!handle) return false; + + int res; + + res = hid_write(handle, buf, len); + + res = hid_read_timeout(handle, buf, 0x40, 1000); + if (res == 0) + { + return false; + } + return true; + } + + + bool send_command(int command, uint8_t *data, int len) { + unsigned char buf[0x40]; + memset(buf, 0, 0x40); + + if (is_usb) { + buf[0x00] = 0x80; + buf[0x01] = 0x92; + buf[0x03] = 0x31; + } + + buf[is_usb ? 0x8 : 0x0] = command; + if (data != nullptr && len != 0) { + memcpy(buf + (is_usb ? 0x9 : 0x1), data, len); + } + + if (!hid_exchange(this->handle, buf, len + (is_usb ? 0x9 : 0x1))) + { + return false; + } + + if (data) { + memcpy(data, buf, 0x40); + } + return true; + } + + bool send_subcommand(int command, int subcommand, uint8_t *data, int len) { + unsigned char buf[0x40]; + memset(buf, 0, 0x40); + + uint8_t rumble_base[9] = { std::uint8_t((++global_count) & 0xF), 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40 }; + memcpy(buf, rumble_base, 9); + + if (global_count > 0xF) { + global_count = 0x0; + } + + // set neutral rumble base only if the command is vibrate (0x01) + // if set when other commands are set, might cause the command to be misread and not executed + //if (subcommand == 0x01) { + // uint8_t rumble_base[9] = { (++global_count) & 0xF, 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40 }; + // memcpy(buf + 10, rumble_base, 9); + //} + + buf[9] = subcommand; + if (data && len != 0) { + memcpy(buf + 10, data, len); + } + + if (!send_command(command, buf, 10 + len)) + { + return false; + } + + if (data) { + memcpy(data, buf, 0x40); //TODO + } + return true; + } + + void rumble(int frequency, int intensity) { + + unsigned char buf[0x400]; + memset(buf, 0, 0x40); + + // intensity: (0, 8) + // frequency: (0, 255) + + // X AA BB Y CC DD + //[0 1 x40 x40 0 1 x40 x40] is neutral. + + + //for (int j = 0; j <= 8; j++) { + // buf[1 + intensity] = 0x1;//(i + j) & 0xFF; + //} + + buf[1 + 0 + intensity] = 0x1; + buf[1 + 4 + intensity] = 0x1; + + // Set frequency to increase + if (this->left_right == 1) { + buf[1 + 0] = frequency;// (0, 255) + } + else { + buf[1 + 4] = frequency;// (0, 255) + } + + // set non-blocking: + hid_set_nonblocking(this->handle, 1); + + send_command(0x10, (uint8_t*)buf, 0x9); + } + + bool get_switch_controller_info() { + bool result = false; + + memset(factory_stick_cal, 0, 0x12); + memset(device_colours, 0, 0xC); + memset(user_stick_cal, 0, 0x16); + memset(sensor_model, 0, 0x6); + memset(stick_model, 0, 0x12); + memset(factory_sensor_cal, 0, 0x18); + memset(user_sensor_cal, 0, 0x1A); + memset(factory_sensor_cal_calm, 0, 0xC); + memset(user_sensor_cal_calm, 0, 0xC); + memset(sensor_cal, 0, sizeof(sensor_cal)); + memset(stick_cal_x_l, 0, sizeof(stick_cal_x_l)); + memset(stick_cal_y_l, 0, sizeof(stick_cal_y_l)); + memset(stick_cal_x_r, 0, sizeof(stick_cal_x_r)); + memset(stick_cal_y_r, 0, sizeof(stick_cal_y_r)); + + + if (!get_spi_data(0x6020, 0x18, factory_sensor_cal)) { return false; } + if (!get_spi_data(0x603D, 0x12, factory_stick_cal)) { return false; } + if (!get_spi_data(0x6050, 0xC, device_colours)) { return false; } + if (!get_spi_data(0x6080, 0x6, sensor_model)) { return false; } + if (!get_spi_data(0x6086, 0x12, stick_model)) { return false; } + if (!get_spi_data(0x6098, 0x12, &stick_model[0x12])) { return false; } + if (!get_spi_data(0x8010, 0x16, user_stick_cal)) { return false; } + if (!get_spi_data(0x8026, 0x1A, user_sensor_cal)) { return false; } + + + // get stick calibration data: + + // factory calibration: + + if (this->left_right == 1 || this->left_right == 3) { + stick_cal_x_l[1] = (factory_stick_cal[4] << 8) & 0xF00 | factory_stick_cal[3]; + stick_cal_y_l[1] = (factory_stick_cal[5] << 4) | (factory_stick_cal[4] >> 4); + stick_cal_x_l[0] = stick_cal_x_l[1] - ((factory_stick_cal[7] << 8) & 0xF00 | factory_stick_cal[6]); + stick_cal_y_l[0] = stick_cal_y_l[1] - ((factory_stick_cal[8] << 4) | (factory_stick_cal[7] >> 4)); + stick_cal_x_l[2] = stick_cal_x_l[1] + ((factory_stick_cal[1] << 8) & 0xF00 | factory_stick_cal[0]); + stick_cal_y_l[2] = stick_cal_y_l[1] + ((factory_stick_cal[2] << 4) | (factory_stick_cal[2] >> 4)); + + } + + if (this->left_right == 2 || this->left_right == 3) { + stick_cal_x_r[1] = (factory_stick_cal[10] << 8) & 0xF00 | factory_stick_cal[9]; + stick_cal_y_r[1] = (factory_stick_cal[11] << 4) | (factory_stick_cal[10] >> 4); + stick_cal_x_r[0] = stick_cal_x_r[1] - ((factory_stick_cal[13] << 8) & 0xF00 | factory_stick_cal[12]); + stick_cal_y_r[0] = stick_cal_y_r[1] - ((factory_stick_cal[14] << 4) | (factory_stick_cal[13] >> 4)); + stick_cal_x_r[2] = stick_cal_x_r[1] + ((factory_stick_cal[16] << 8) & 0xF00 | factory_stick_cal[15]); + stick_cal_y_r[2] = stick_cal_y_r[1] + ((factory_stick_cal[17] << 4) | (factory_stick_cal[16] >> 4)); + + } + + + // if there is user calibration data: + if ((user_stick_cal[0] | user_stick_cal[1] << 8) == 0xA1B2) { + stick_cal_x_l[1] = (user_stick_cal[6] << 8) & 0xF00 | user_stick_cal[5]; + stick_cal_y_l[1] = (user_stick_cal[7] << 4) | (user_stick_cal[6] >> 4); + stick_cal_x_l[0] = stick_cal_x_l[1] - ((user_stick_cal[9] << 8) & 0xF00 | user_stick_cal[8]); + stick_cal_y_l[0] = stick_cal_y_l[1] - ((user_stick_cal[10] << 4) | (user_stick_cal[9] >> 4)); + stick_cal_x_l[2] = stick_cal_x_l[1] + ((user_stick_cal[3] << 8) & 0xF00 | user_stick_cal[2]); + stick_cal_y_l[2] = stick_cal_y_l[1] + ((user_stick_cal[4] << 4) | (user_stick_cal[3] >> 4)); + //FormJoy::myform1->textBox_lstick_ucal->Text = String::Format(L"L Stick User:\r\nCenter X,Y: ({0:X3}, {1:X3})\r\nX: [{2:X3} - {4:X3}] Y: [{3:X3} - {5:X3}]", + //stick_cal_x_l[1], stick_cal_y_l[1], stick_cal_x_l[0], stick_cal_y_l[0], stick_cal_x_l[2], stick_cal_y_l[2]); + } + else { + //FormJoy::myform1->textBox_lstick_ucal->Text = L"L Stick User:\r\nNo calibration"; + //printf("no user Calibration data for left stick.\n"); + } + + if ((user_stick_cal[0xB] | user_stick_cal[0xC] << 8) == 0xA1B2) { + stick_cal_x_r[1] = (user_stick_cal[14] << 8) & 0xF00 | user_stick_cal[13]; + stick_cal_y_r[1] = (user_stick_cal[15] << 4) | (user_stick_cal[14] >> 4); + stick_cal_x_r[0] = stick_cal_x_r[1] - ((user_stick_cal[17] << 8) & 0xF00 | user_stick_cal[16]); + stick_cal_y_r[0] = stick_cal_y_r[1] - ((user_stick_cal[18] << 4) | (user_stick_cal[17] >> 4)); + stick_cal_x_r[2] = stick_cal_x_r[1] + ((user_stick_cal[20] << 8) & 0xF00 | user_stick_cal[19]); + stick_cal_y_r[2] = stick_cal_y_r[1] + ((user_stick_cal[21] << 4) | (user_stick_cal[20] >> 4)); + //FormJoy::myform1->textBox_rstick_ucal->Text = String::Format(L"R Stick User:\r\nCenter X,Y: ({0:X3}, {1:X3})\r\nX: [{2:X3} - {4:X3}] Y: [{3:X3} - {5:X3}]", + //stick_cal_x_r[1], stick_cal_y_r[1], stick_cal_x_r[0], stick_cal_y_r[0], stick_cal_x_r[2], stick_cal_y_r[2]); + } + else { + //FormJoy::myform1->textBox_rstick_ucal->Text = L"R Stick User:\r\nNo calibration"; + //printf("no user Calibration data for right stick.\n"); + } + + // get gyro / accelerometer calibration data: + + // factory calibration: + + // Acc cal origin position + sensor_cal[0][0] = uint16_to_int16(factory_sensor_cal[0] | factory_sensor_cal[1] << 8); + sensor_cal[0][1] = uint16_to_int16(factory_sensor_cal[2] | factory_sensor_cal[3] << 8); + sensor_cal[0][2] = uint16_to_int16(factory_sensor_cal[4] | factory_sensor_cal[5] << 8); + + // Gyro cal origin position + sensor_cal[1][0] = uint16_to_int16(factory_sensor_cal[0xC] | factory_sensor_cal[0xD] << 8); + sensor_cal[1][1] = uint16_to_int16(factory_sensor_cal[0xE] | factory_sensor_cal[0xF] << 8); + sensor_cal[1][2] = uint16_to_int16(factory_sensor_cal[0x10] | factory_sensor_cal[0x11] << 8); + + + //hex_dump(user_sensor_cal, 0x14); + + // user calibration: + if ((user_sensor_cal[0x0] | user_sensor_cal[0x1] << 8) == 0xA1B2) { + //printf("User calibration available\n"); + //if (true) { + //FormJoy::myform1->textBox_6axis_ucal->Text = L"6-Axis User (XYZ):\r\nAcc: "; + //for (int i = 0; i < 0xC; i = i + 6) { + // FormJoy::myform1->textBox_6axis_ucal->Text += String::Format(L"{0:X4} {1:X4} {2:X4}\r\n ", + // user_sensor_cal[i + 2] | user_sensor_cal[i + 3] << 8, + // user_sensor_cal[i + 4] | user_sensor_cal[i + 5] << 8, + // user_sensor_cal[i + 6] | user_sensor_cal[i + 7] << 8); + //} + // Acc cal origin position + sensor_cal[0][0] = uint16_to_int16(user_sensor_cal[2] | user_sensor_cal[3] << 8); + sensor_cal[0][1] = uint16_to_int16(user_sensor_cal[4] | user_sensor_cal[5] << 8); + sensor_cal[0][2] = uint16_to_int16(user_sensor_cal[6] | user_sensor_cal[7] << 8); + //FormJoy::myform1->textBox_6axis_ucal->Text += L"\r\nGyro: "; + //for (int i = 0xC; i < 0x18; i = i + 6) { + // FormJoy::myform1->textBox_6axis_ucal->Text += String::Format(L"{0:X4} {1:X4} {2:X4}\r\n ", + // user_sensor_cal[i + 2] | user_sensor_cal[i + 3] << 8, + // user_sensor_cal[i + 4] | user_sensor_cal[i + 5] << 8, + // user_sensor_cal[i + 6] | user_sensor_cal[i + 7] << 8); + //} + // Gyro cal origin position + sensor_cal[1][0] = uint16_to_int16(user_sensor_cal[0xE] | user_sensor_cal[0xF] << 8); + sensor_cal[1][1] = uint16_to_int16(user_sensor_cal[0x10] | user_sensor_cal[0x11] << 8); + sensor_cal[1][2] = uint16_to_int16(user_sensor_cal[0x12] | user_sensor_cal[0x13] << 8); + } + else { + //FormJoy::myform1->textBox_6axis_ucal->Text = L"\r\n\r\nUser:\r\nNo calibration"; + } + + // Internal scaling and unit conversions as per https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md + // Use SPI calibration and convert them to Gs + acc_cal_coeff[0] = (float)(1.0 / (float)(16384 - uint16_to_int16(sensor_cal[0][0]))) * 4.0f; + acc_cal_coeff[1] = (float)(1.0 / (float)(16384 - uint16_to_int16(sensor_cal[0][1]))) * 4.0f; + acc_cal_coeff[2] = (float)(1.0 / (float)(16384 - uint16_to_int16(sensor_cal[0][2]))) * 4.0f; + + // Use SPI calibration and convert them to degrees per second + gyro_cal_coeff[0] = (float)(936.0 / (float)(13371 - uint16_to_int16(sensor_cal[1][0]))); + gyro_cal_coeff[1] = (float)(936.0 / (float)(13371 - uint16_to_int16(sensor_cal[1][1]))); + gyro_cal_coeff[2] = (float)(936.0 / (float)(13371 - uint16_to_int16(sensor_cal[1][2]))); + + // Device colours + body_colour = + (((int)device_colours[0]) << 16) + + (((int)device_colours[1]) << 8) + + (((int)device_colours[2])); + button_colour = + (((int)device_colours[3]) << 16) + + (((int)device_colours[4]) << 8) + + (((int)device_colours[5])); + left_grip_colour = + (((int)device_colours[6]) << 16) + + (((int)device_colours[7]) << 8) + + (((int)device_colours[8])); + right_grip_colour = + (((int)device_colours[9]) << 16) + + (((int)device_colours[10]) << 8) + + (((int)device_colours[11])); + + printf("Body: %#08x; Buttons: %#08x; Left Grip: %#08x; Right Grip: %#08x;\n", + body_colour, + button_colour, + left_grip_colour, + right_grip_colour); + + //hex_dump(reinterpret_cast(sensor_cal[0]), 6); + //hex_dump(reinterpret_cast(sensor_cal[1]), 6); + + return true; + } + + void enable_IMU(unsigned char *buf, int bufLength) { + memset(buf, 0, bufLength); + + // Enable IMU data + printf("Enabling IMU data...\n"); + if (controller_type == ControllerType::s_ds4) + { + if (is_usb) + { + init_ds4_bt(); + enable_gyro_ds4_bt(buf, bufLength); + } + else + { + init_ds4_usb(); + } + } + else + { + buf[0] = 0x01; // Enabled + send_subcommand(0x1, 0x40, buf, 1); + } + } + + bool init_usb() { + unsigned char buf[0x400]; + memset(buf, 0, 0x400); + + // set blocking: + // this insures we get the MAC Address + hid_set_nonblocking(this->handle, 0); + + //Get MAC Left + printf("Getting MAC...\n"); + memset(buf, 0x00, 0x40); + buf[0] = 0x80; + buf[1] = 0x01; + hid_exchange(this->handle, buf, 0x2); + + //if (buf[2] == 0x3) { + // printf("%s disconnected!\n", this->name.c_str()); + //} + //else { + // printf("Found %s, MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", this->name.c_str(), buf[9], buf[8], buf[7], buf[6], buf[5], buf[4]); + //} + + // set non-blocking: + //hid_set_nonblocking(jc->handle, 1); + + // Do handshaking + printf("Doing handshake...\n"); + memset(buf, 0x00, 0x40); + buf[0] = 0x80; + buf[1] = 0x02; + hid_exchange(this->handle, buf, 0x2); + + // Switch baudrate to 3Mbit + printf("Switching baudrate...\n"); + memset(buf, 0x00, 0x40); + buf[0] = 0x80; + buf[1] = 0x03; + hid_exchange(this->handle, buf, 0x2); + + //Do handshaking again at new baudrate so the firmware pulls pin 3 low? + printf("Doing handshake...\n"); + memset(buf, 0x00, 0x40); + buf[0] = 0x80; + buf[1] = 0x02; + hid_exchange(this->handle, buf, 0x2); + + //Only talk HID from now on + printf("Only talk HID...\n"); + memset(buf, 0x00, 0x40); + buf[0] = 0x80; + buf[1] = 0x04; + hid_exchange(this->handle, buf, 0x2); + + // Enable vibration + printf("Enabling vibration...\n"); + memset(buf, 0x00, 0x400); + buf[0] = 0x01; // Enabled + send_subcommand(0x1, 0x48, buf, 1); + + enable_IMU(buf, 0x400); + + printf("Getting calibration data...\n"); + bool result = get_switch_controller_info(); + + if (result) + { + printf("Successfully initialized %s!\n", this->name.c_str()); + } + else + { + printf("Could not initialise %s! Will try again later.\n", this->name.c_str()); + } + return result; + } + + bool init_bt() { + bool result = true; + unsigned char buf[0x40]; + memset(buf, 0, 0x40); + printf("Initialising Bluetooth connection...\n"); + + // set blocking to ensure command is recieved: + hid_set_nonblocking(this->handle, 0); + + // first, check if this is a USB connection + buf[0] = 0x80; + buf[1] = 0x01; + hid_write(this->handle, buf, 2); + // wait for up to 5 messages for a USB acknowledgement + for (int idx = 0; idx < 5; idx++) + { + if (hid_read_timeout(this->handle, buf, 0x40, 200) && buf[0] == 0x81) + { + //printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + // buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]); + printf("Attempting USB connection\n"); + + // it's usb! + is_usb = true; + + init_usb(); + return 1; + + break; + } + //printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + // buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]); + printf("Not a USB response...\n"); + } + memset(buf, 0, 0x40); + //if (hid_exchange(this->handle, buf, 2)) + //{ + // printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + // buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]); + // printf("Attempting USB connection\n"); + // // it's usb! + // is_usb = true; + // + // init_usb(); + // return 1; + //} + buf[1] = 0x00; + + // Enable vibration + printf("Enabling vibration...\n"); + buf[0] = 0x01; // Enabled + send_subcommand(0x1, 0x48, buf, 1); + + //printf("Set vibration\n"); + + // Enable IMU data + enable_IMU(buf, 0x40); + + // Set input report mode (to push at 60hz) + // x00 Active polling mode for IR camera data. Answers with more than 300 bytes ID 31 packet + // x01 Active polling mode + // x02 Active polling mode for IR camera data.Special IR mode or before configuring it ? + // x21 Unknown.An input report with this ID has pairing or mcu data or serial flash data or device info + // x23 MCU update input report ? + // 30 NPad standard mode. Pushes current state @60Hz. Default in SDK if arg is not in the list + // 31 NFC mode. Pushes large packets @60Hz + printf("Set input report mode to 0x30...\n"); + buf[0] = 0x30; + send_subcommand(0x01, 0x03, buf, 1); + + // @CTCaer + + // get calibration data: + printf("Getting calibration data...\n"); + result = get_switch_controller_info(); + + return result; + } + + void init_ds4_bt() { + printf("initialise, set colour\n"); + unsigned char buf[78]; + memset(buf, 0, 78); + + //buf[0] = 0x11; + //buf[1] = 0x80; + //buf[3] = 0xff; + + // https://github.com/Ryochan7/DS4Windows/blob/jay/DS4Windows/DS4Library/DS4Device.cs + buf[0] = 0x15; + buf[1] = 0xC0 | 1; + buf[2] = 0xA0; + buf[3] = 0xf7; + buf[4] = 0x04; + + //// https://github.com/chrippa/ds4drv/blob/master/ds4drv/device.py + //buf[0] = 0xa2; // 0x80; + ////buf[1] = 0xff; + //// trying to do colour stuff + //// http://eleccelerator.com/wiki/index.php?title=DualShock_4 + //// this is only for bt + //buf[1] = 0x11; + //buf[2] = 0xc0; + //buf[3] = 0x20; + //buf[4] = 0xf3; + //buf[5] = 0x04; + //// rumble + //buf[7] = 0xFF; + //buf[8] = 0x00; + //// colour + //buf[9] = 0x00; + //buf[10] = 0x00; + //buf[11] = 0x00; + //// flash time + //buf[12] = 0xff; + //buf[13] = 0x00; + //// now we need a CRC-32 of previous bytes + //uint32_t crc = crc_32(buf, 75); + //buf[75] = (crc >> 24) & 0xFF; + //buf[76] = (crc >> 16) & 0xFF; + //buf[77] = (crc >> 8) & 0xFF; + //buf[78] = crc & 0xFF; + + //// https://github.com/chrippa/ds4drv/blob/master/ds4drv/device.py + //buf[0] = 0x80; + //buf[1] = 0xff; + //// trying to do colour stuff + //// http://eleccelerator.com/wiki/index.php?title=DualShock_4 + //// this is only for bt + //buf[2] = 0x11; + //// rumble + //buf[6] = 0xFF; + //buf[7] = 0xFF; + //// colour + //buf[8] = 0xFF; // 0x00; + //buf[9] = 0x80; // 0x00; + //buf[10] = 0x00; + //// flash time + //buf[11] = 0xff; + //buf[12] = 0x00; + //// now we need a CRC-32 of previous bytes + //uint32_t crc = crc_32(buf, 75); + //buf[75] = (crc >> 24) & 0xFF; + //buf[76] = (crc >> 16) & 0xFF; + //buf[77] = (crc >> 8) & 0xFF; + //buf[78] = crc & 0xFF; + + // set blocking: + // this insures we get the MAC Address + hid_set_nonblocking(this->handle, 0); + + hid_write(handle, buf, 78); + + // initialise stuff + memset(factory_stick_cal, 0, 0x12); + memset(device_colours, 0, 0xC); + memset(user_stick_cal, 0, 0x16); + memset(sensor_model, 0, 0x6); + memset(stick_model, 0, 0x12); + memset(factory_sensor_cal, 0, 0x18); + memset(user_sensor_cal, 0, 0x1A); + memset(factory_sensor_cal_calm, 0, 0xC); + memset(user_sensor_cal_calm, 0, 0xC); + memset(sensor_cal, 0, sizeof(sensor_cal)); + memset(stick_cal_x_l, 0, sizeof(stick_cal_x_l)); + memset(stick_cal_y_l, 0, sizeof(stick_cal_y_l)); + memset(stick_cal_x_r, 0, sizeof(stick_cal_x_r)); + memset(stick_cal_y_r, 0, sizeof(stick_cal_y_r)); + stick_cal_x_l[0] = + stick_cal_y_l[0] = + stick_cal_x_r[0] = + stick_cal_y_r[0] = 0; + stick_cal_x_l[1] = + stick_cal_y_l[1] = + stick_cal_x_r[1] = + stick_cal_y_r[1] = 127; + stick_cal_x_l[2] = + stick_cal_y_l[2] = + stick_cal_x_r[2] = + stick_cal_y_r[2] = 255; + //// Acc cal origin position + //sensor_cal[0][0] = 0; + //sensor_cal[0][1] = 0; + //sensor_cal[0][2] = 0; + // + //// Gyro cal origin position + //sensor_cal[1][0] = 0; + //sensor_cal[1][1] = 0; + //sensor_cal[1][2] = 0; + + enable_gyro_ds4_bt(buf, 78); + + initialised = true; + } + + // placeholder to get things working quickly. overdue for a refactor + void init_ds_usb() { + // initialise stuff + memset(factory_stick_cal, 0, 0x12); + memset(device_colours, 0, 0xC); + memset(user_stick_cal, 0, 0x16); + memset(sensor_model, 0, 0x6); + memset(stick_model, 0, 0x12); + memset(factory_sensor_cal, 0, 0x18); + memset(user_sensor_cal, 0, 0x1A); + memset(factory_sensor_cal_calm, 0, 0xC); + memset(user_sensor_cal_calm, 0, 0xC); + memset(sensor_cal, 0, sizeof(sensor_cal)); + memset(stick_cal_x_l, 0, sizeof(stick_cal_x_l)); + memset(stick_cal_y_l, 0, sizeof(stick_cal_y_l)); + memset(stick_cal_x_r, 0, sizeof(stick_cal_x_r)); + memset(stick_cal_y_r, 0, sizeof(stick_cal_y_r)); + stick_cal_x_l[0] = + stick_cal_y_l[0] = + stick_cal_x_r[0] = + stick_cal_y_r[0] = 0; + stick_cal_x_l[1] = + stick_cal_y_l[1] = + stick_cal_x_r[1] = + stick_cal_y_r[1] = 127; + stick_cal_x_l[2] = + stick_cal_y_l[2] = + stick_cal_x_r[2] = + stick_cal_y_r[2] = 255; + + initialised = true; + } + + // this is mostly copied from init_usb() below, but modified to speak DS4 + void init_ds4_usb() { + unsigned char buf[31]; + memset(buf, 0, 31); + + // report id? + buf[0] = 0x05; + // I dunno what this is + buf[1] = 0xf7; + buf[2] = 0x04; + //// http://eleccelerator.com/wiki/index.php?title=DualShock_4 + //// https://github.com/chrippa/ds4drv/blob/master/ds4drv/device.py + //// rumble + //buf[4] = 0x00; + //buf[5] = 0x00; + //// colour + //buf[6] = 0x00; + ////buf[7] = 0xff; + //buf[7] = 0x00; + //buf[8] = 0x00; + //// flash time + //buf[9] = 0xff; + //buf[10] = 0x00; + // now we need a CRC-32 of previous bytes + //uint32_t = crc_32(buf, 75); + //buf[75] = + + // set blocking: + // this insures we get the MAC Address + hid_set_nonblocking(this->handle, 0); + + hid_write(handle, buf, 31); + + // initialise stuff + memset(factory_stick_cal, 0, 0x12); + memset(device_colours, 0, 0xC); + memset(user_stick_cal, 0, 0x16); + memset(sensor_model, 0, 0x6); + memset(stick_model, 0, 0x12); + memset(factory_sensor_cal, 0, 0x18); + memset(user_sensor_cal, 0, 0x1A); + memset(factory_sensor_cal_calm, 0, 0xC); + memset(user_sensor_cal_calm, 0, 0xC); + memset(sensor_cal, 0, sizeof(sensor_cal)); + memset(stick_cal_x_l, 0, sizeof(stick_cal_x_l)); + memset(stick_cal_y_l, 0, sizeof(stick_cal_y_l)); + memset(stick_cal_x_r, 0, sizeof(stick_cal_x_r)); + memset(stick_cal_y_r, 0, sizeof(stick_cal_y_r)); + stick_cal_x_l[0] = + stick_cal_y_l[0] = + stick_cal_x_r[0] = + stick_cal_y_r[0] = 0; + stick_cal_x_l[1] = + stick_cal_y_l[1] = + stick_cal_x_r[1] = + stick_cal_y_r[1] = 127; + stick_cal_x_l[2] = + stick_cal_y_l[2] = + stick_cal_x_r[2] = + stick_cal_y_r[2] = 255; + //// Acc cal origin position + //sensor_cal[0][0] = 0; + //sensor_cal[0][1] = 0; + //sensor_cal[0][2] = 0; + // + //// Gyro cal origin position + //sensor_cal[1][0] = 0; + //sensor_cal[1][1] = 0; + //sensor_cal[1][2] = 0; + + initialised = true; + } + + void deinit_ds4_bt() { + // TODO. For now, init, which stops rumbling and disables light + init_ds4_bt(); + + initialised = false; + } + + // TODO: implement this + void deinit_ds4_usb() { + unsigned char buf[40]; + memset(buf, 0, 40); + + // report id? + buf[0] = 0x05; + // don't know what this is + buf[1] = 0xff; + // rumble + buf[4] = 0x00; + buf[5] = 0x00; + // colour + buf[6] = 0x00; + buf[7] = 0x00; + buf[8] = 0x00; + // flash time + buf[9] = 0x00; + buf[10] = 0x00; + // now we need a CRC-32 of previous bytes + //uint32_t = crc_32(buf, 75); + //buf[75] = + + // set non-blocking + hid_set_nonblocking(this->handle, 1); + + hid_write(handle, buf, 31); + + initialised = false; + } + + void deinit_usb() { + unsigned char buf[0x40]; + memset(buf, 0x00, 0x40); + + //Let the Joy-Con talk BT again + buf[0] = 0x80; + buf[1] = 0x05; + + hid_set_nonblocking(this->handle, 1); + hid_write(handle, buf, 0x2); + + initialised = false; + } + + void set_ds5_rumble_light(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB, + unsigned char playerlights) { + if(!is_usb) { + set_ds5_rumble_light_bt(smallRumble, bigRumble, colourR, colourG, colourB, playerlights); + } + else { + set_ds5_rumble_light_usb(smallRumble, bigRumble, colourR, colourG, colourB, playerlights); + } + + } + + void set_ds4_rumble_light(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB) { + if (!is_usb) { + set_ds4_rumble_light_bt(smallRumble, bigRumble, colourR, colourG, colourB); + } + else { + set_ds4_rumble_light_usb(smallRumble, bigRumble, colourR, colourG, colourB); + } + } + + void set_ds4_rumble_light_usb(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB) { + // todo: based on bluetoothness, switch report id to 0x11, offset everything by 2 -- basically use init stuff as basis + unsigned char buf[40]; + memset(buf, 0, 40); + + // report id? + buf[0] = 0x05; + // don't know what this is + buf[1] = 0xff; + // rumble + buf[4] = smallRumble; + buf[5] = bigRumble; + // colour + buf[6] = colourR; + buf[7] = colourG; + buf[8] = colourB; + // flash time + buf[9] = 0xff; + buf[10] = 0x00; + // now we need a CRC-32 of previous bytes + //uint32_t = crc_32(buf, 75); + //buf[75] = + + hid_write(handle, buf, 31); + } + + void set_ds4_rumble_light_bt(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB) { + unsigned char buf[79]; + memset(buf, 0, 79); + + // https://github.com/chrippa/ds4drv/blob/master/ds4drv/device.py + //buf[0] = 0xa2; // 0x80; + //buf[1] = 0xff; + // trying to do colour stuff + // http://eleccelerator.com/wiki/index.php?title=DualShock_4 + // this is only for bt + + buf[0] = 0xa2; // Output report header, needs to be included in crc32 + buf[1] = 0x11; // Output report 0x11 + buf[2] = 0xc0; // HID + CRC according to hid-sony + buf[3] = 0x20; // ???? + buf[4] = 0x07; // Set blink + leds + motor + buf[5] = 0x00; + buf[6] = 0x00; + // rumble + buf[7] = smallRumble; + buf[8] = bigRumble; + // colour + buf[9] = colourR; + buf[10] = colourG; + buf[11] = colourB; + // flash time + buf[12] = 0xff; + buf[13] = 0x00; + // now we need a CRC-32 of previous bytes + + /* + // test + buf[0] = 0xa2; // Output report header, needs to be included in crc32 + buf[1] = 0x11; // Output report 0x11 + buf[2] = 0xc0; // HID + CRC according to hid-sony + buf[3] = 0x00; // ???? + buf[4] = 0x07; // Set blink + leds + motor + buf[5] = 0x00; + buf[6] = 0x00; + buf[7] = 0xff; + buf[8] = 0xff; + buf[9] = 0xff; + buf[10] = 0xff; + buf[11] = 0xff; + buf[12] = 0xff; + buf[22] = 0x43; + buf[23] = 0x43; + buf[25] = 0x4d; + buf[26] = 0x85; +*/ + + uint32_t crc = crc_32(buf, 75); + memcpy(&buf[75], &crc, 4); + //buf[75] = (crc >> 24) & 0xFF; + //buf[76] = (crc >> 16) & 0xFF; + //buf[77] = (crc >> 8) & 0xFF; + //buf[78] = crc & 0xFF; + + hid_write(handle, &buf[1], 78); + } + + void set_ds5_rumble_light_usb(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB, + unsigned char playerlights) { // DS5 actually has player lights. + unsigned char buf[79]; + memset(buf, 0, 79); + + // https://github.com/Ryochan7/DS4Windows/blob/jay/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs + // DS4Windows to the rescue. + // Also thanks to Neilk1 for sharing his doc on the DS5 protocol. + + // Header & Report Information + buf[0] = 0xa2; // Output report header, needs to be included in crc32 + buf[1] = 0x02; // DualSense output report is 0x02 for USB + //buf[1] = 0x02; // DATA (0x02) + + + buf[2] = 0x03; + + buf[3] = 0x54; // Toggle LED Strips, player lights, motor effect. Ignore Mic LED + + // Rumble emulation bytes. + buf[4] = smallRumble; + buf[5] = bigRumble; + + // 7-10 are mostly just audio settings. + + // Mute Button state. 0x00 = off, 0x01 = solid, 0x02 = pulsating. + buf[10] = 0x00; + + // Skip to about 41, since we are ignoring trigger effect data. + // Enable LED brightness + buf[40] = 0x02; // ??? + buf[41] = 0x02; + buf[44] = 0x02; + + // Controls the player lights, which the DS5 has. + // Last two bits are unused - unset them to avoid issues. + buf[45] = playerlights; + buf[45] &= ~(1 << 7); + buf[45] &= ~(1 << 8); + + // colour + buf[46] = colourR; + buf[47] = colourG; + buf[48] = colourB; + + // USB does not send CRC32 + + //uint32_t crc = crc_32(buf, 74); + //memcpy(&buf[74], &crc, 4); + //buf[75] = (crc >> 24) & 0xFF; + //buf[76] = (crc >> 16) & 0xFF; + //buf[77] = (crc >> 8) & 0xFF; + //buf[78] = crc & 0xFF; + + hid_write(handle, &buf[1], 74); + } + + // Calling the Dualsense anything but the DS5 is confusing, since DS also = DualShock, and the DualSense is the PS5 Controller anyway + void set_ds5_rumble_light_bt(unsigned char smallRumble, unsigned char bigRumble, + unsigned char colourR, + unsigned char colourG, + unsigned char colourB, + unsigned char playerlights) { // DS5 actually has player lights. + unsigned char buf[79]; + memset(buf, 0, 79); + + // https://github.com/Ryochan7/DS4Windows/blob/jay/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs + // DS4Windows to the rescue. + // Also thanks to Neilk1 for sharing his doc on the DS5 protocol. + + // Header & Report Information + buf[0] = 0xa2; // Output report header, needs to be included in crc32 + buf[1] = 0x31; // DualSense output report is 0x31 + buf[2] = 0x02; // DATA (0x02) + + // Comment stolen from DS4Windows: + // 0x01 Set the main motors (also requires flag 0x02) + // 0x02 Set the main motors (also requires flag 0x01) + // 0x04 Set the right trigger motor + // 0x08 Set the left trigger motor + // 0x10 Enable modification of audio volume + // 0x20 Enable internal speaker (even while headset is connected) + // 0x40 Enable modification of microphone volume + // 0x80 Enable internal mic (even while headset is connected) + buf[3] = 0x03; + + // Comment stolen from DS4Windows: + // 0x01 Toggling microphone LED, 0x02 Toggling Audio/Mic Mute + // 0x04 Toggling LED strips on the sides of the Touchpad, 0x08 Turn off all LED lights + // 0x10 Toggle player LED lights below Touchpad, 0x20 ??? + // 0x40 Adjust overall motor/effect power, 0x80 ??? + buf[4] = 0x54; // Toggle LED Strips, player lights, motor effect. Ignore Mic LED + + // Rumble emulation bytes. + buf[5] = smallRumble; + buf[6] = bigRumble; + + // 7-10 are mostly just audio settings. + + // Mute Button state. 0x00 = off, 0x01 = solid, 0x02 = pulsating. + buf[11] = 0x00; + + // Skip to about 41, since we are ignoring trigger effect data. + // Enable LED brightness + buf[41] = 0x02; // ??? + buf[44] = 0x02; + buf[45] = 0x02; + + // Last two bits are unused - unset them to avoid issues. + buf[46] = playerlights; + buf[46] &= ~(1 << 7); + buf[46] &= ~(1 << 8); + + // colour + buf[47] = colourR; + buf[48] = colourG; + buf[49] = colourB; + + uint32_t crc = crc_32(buf, 75); + memcpy(&buf[75], &crc, 4); + //buf[75] = (crc >> 24) & 0xFF; + //buf[76] = (crc >> 16) & 0xFF; + //buf[77] = (crc >> 8) & 0xFF; + //buf[78] = crc & 0xFF; + + hid_write(handle, &buf[1], 78); + } + + //// mfosse credits Hypersect (Ryan Juckett), but I've removed deadzones so the consuming application can deal with them + //// http://blog.hypersect.com/interpreting-analog-sticks/ + void CalcAnalogStick2 + ( + float &pOutX, // out: resulting stick X value + float &pOutY, // out: resulting stick Y value + uint16_t x, // in: initial stick X value + uint16_t y, // in: initial stick Y value + uint16_t x_calc[3], // calc -X, CenterX, +X + uint16_t y_calc[3] // calc -Y, CenterY, +Y + ) + { + + float x_f, y_f; + + // convert to float based on calibration and valid ranges per +/-axis + x = clamp(x, x_calc[0], x_calc[2]); + y = clamp(y, y_calc[0], y_calc[2]); + if (x >= x_calc[1]) { + x_f = (float)(x - x_calc[1]) / (float)(x_calc[2] - x_calc[1]); + } + else { + x_f = -((float)(x - x_calc[1]) / (float)(x_calc[0] - x_calc[1])); + } + if (y >= y_calc[1]) { + y_f = (float)(y - y_calc[1]) / (float)(y_calc[2] - y_calc[1]); + } + else { + y_f = -((float)(y - y_calc[1]) / (float)(y_calc[0] - y_calc[1])); + } + + pOutX = x_f; + pOutY = y_f; + } + + // SPI (@CTCaer): + bool get_spi_data(uint32_t offset, const uint8_t read_len, uint8_t *test_buf) { + int res; + uint8_t buf[0x100]; + while (1) { + memset(buf, 0, sizeof(buf)); + auto hdr = (brcm_hdr *)buf; + auto pkt = (brcm_cmd_01 *)(hdr + 1); + hdr->cmd = 1; + hdr->rumble[0] = timing_byte; + + buf[1] = timing_byte; + + timing_byte++; + if (timing_byte > 0xF) { + timing_byte = 0x0; + } + pkt->subcmd = 0x10; + pkt->offset = offset; + pkt->size = read_len; + + for (int i = 11; i < 22; ++i) { + buf[i] = buf[i + 3]; + } + + res = hid_write(handle, buf, sizeof(*hdr) + sizeof(*pkt)); + + res = hid_read_timeout(handle, buf, sizeof(buf), 1000); + if (res == 0) + { + return false; + } + + if ((*(uint16_t*)&buf[0xD] == 0x1090) && (*(uint32_t*)&buf[0xF] == offset)) { + break; + } + } + if (res >= 0x14 + read_len) { + for (int i = 0; i < read_len; i++) { + test_buf[i] = buf[0x14 + i]; + } + } + + return true; + } + + int write_spi_data(uint32_t offset, const uint8_t write_len, uint8_t* test_buf) { + int res; + uint8_t buf[0x100]; + int error_writing = 0; + while (1) { + memset(buf, 0, sizeof(buf)); + auto hdr = (brcm_hdr *)buf; + auto pkt = (brcm_cmd_01 *)(hdr + 1); + hdr->cmd = 1; + hdr->rumble[0] = timing_byte; + timing_byte++; + if (timing_byte > 0xF) { + timing_byte = 0x0; + } + pkt->subcmd = 0x11; + pkt->offset = offset; + pkt->size = write_len; + for (int i = 0; i < write_len; i++) { + buf[0x10 + i] = test_buf[i]; + } + res = hid_write(handle, buf, sizeof(*hdr) + sizeof(*pkt) + write_len); + + res = hid_read(handle, buf, sizeof(buf)); + + if (*(uint16_t*)&buf[0xD] == 0x1180) + break; + + error_writing++; + if (error_writing == 125) { + return 1; + } + } + + return 0; + + } +}; diff --git a/project/dependencies/JoyShockLibrary/JoyShockLibrary.cpp b/project/dependencies/JoyShockLibrary/JoyShockLibrary.cpp new file mode 100644 index 0000000..306c327 --- /dev/null +++ b/project/dependencies/JoyShockLibrary/JoyShockLibrary.cpp @@ -0,0 +1,1164 @@ +// JoyShockLibrary.cpp : Defines the exported functions for the DLL application. +// + +#include "JoyShockLibrary.h" +#include +#include "hidapi.h" +#include +#include +#include +#include +#include +#include +#include "GamepadMotion.hpp" +#include "JoyShock.cpp" +#include "InputHelpers.cpp" + +std::shared_timed_mutex _callbackLock; +std::shared_timed_mutex _connectedLock; +void(*_pollCallback)(int, JOY_SHOCK_STATE, JOY_SHOCK_STATE, IMU_STATE, IMU_STATE, float) = nullptr; +void(*_pollTouchCallback)(int, TOUCH_STATE, TOUCH_STATE, float) = nullptr; +void(*_connectCallback)(int) = nullptr; +void(*_disconnectCallback)(int, bool) = nullptr; +std::unordered_map _joyshocks; +std::unordered_map _byPath; +std::mutex _pathHandleLock; +std::unordered_map _pathHandle; +// https://stackoverflow.com/questions/41206861/atomic-increment-and-return-counter +static std::atomic _joyshockHandleCounter; + +static int GetUniqueHandle(const std::string &path) +{ + _pathHandleLock.lock(); + auto iter = _pathHandle.find(path); + + if (iter != _pathHandle.end()) + { + _pathHandleLock.unlock(); + return iter->second; + } + const int handle = _joyshockHandleCounter++; + _pathHandle.emplace(path, handle); + _pathHandleLock.unlock(); + + return handle; +} + +// https://stackoverflow.com/questions/25144887/map-unordered-map-prefer-find-and-then-at-or-try-at-catch-out-of-range +// not thread-safe -- because you probably want to do something with the object you get out of it, I've left locking to the caller +static JoyShock* GetJoyShockFromHandle(int handle) { + auto iter = _joyshocks.find(handle); + + if (iter != _joyshocks.end()) + { + return iter->second; + // iter is item pair in the map. The value will be accessible as `iter->second`. + } + return nullptr; +} + +void pollIndividualLoop(JoyShock *jc) { + if (!jc->handle) { return; } + + hid_set_nonblocking(jc->handle, 0); + //hid_set_nonblocking(jc->handle, 1); // temporary, to see if it helps. this means we'll have a crazy spin + + int numTimeOuts = 0; + int numNoIMU = 0; + bool hasIMU = false; + bool lockedThread = false; + int noIMULimit; + switch (jc->controller_type) + { + case ControllerType::s_ds4: + noIMULimit = 250; + break; + case ControllerType::s_ds: + noIMULimit = 250; + break; + case ControllerType::n_switch: + default: + noIMULimit = 67; + } + float wakeupTimer = 0.0f; + + while (!jc->cancel_thread) { + // get input: + unsigned char buf[64]; + memset(buf, 0, 64); + + // 10 seconds of no signal means forget this controller + int reuseCounter = jc->reuse_counter; + int res = hid_read_timeout(jc->handle, buf, 64, 1000); + + if (res == -1) + { + // disconnected! + printf("Controller %d disconnected\n", jc->intHandle); + + _connectedLock.lock(); + lockedThread = true; + const bool gettingReused = jc->reuse_counter != reuseCounter; + jc->delete_on_finish = true; + if (gettingReused) + { + jc->remove_on_finish = false; + jc->delete_on_finish = false; + lockedThread = false; + _connectedLock.unlock(); + } + break; + } + + if (res == 0) + { + numTimeOuts++; + if (numTimeOuts >= 10) + { + printf("Controller %d timed out\n", jc->intHandle); + + // just make sure we get this thing deleted before someone else tries to start a new connection + _connectedLock.lock(); + lockedThread = true; + const bool gettingReused = jc->reuse_counter != reuseCounter; + jc->delete_on_finish = true; + if (gettingReused) + { + jc->remove_on_finish = false; + jc->delete_on_finish = false; + lockedThread = false; + _connectedLock.unlock(); + } + break; + } + else + { + // try wake up the controller with the appropriate message + if (jc->controller_type != ControllerType::n_switch) + { + // TODO + } + else + { + if (jc->is_usb) + { + printf("Attempting to re-initialise controller %d\n", jc->intHandle); + if (jc->init_usb()) + { + numTimeOuts = 0; + } + } + else + { + printf("Attempting to re-initialise controller %d\n", jc->intHandle); + if (jc->init_bt()) + { + numTimeOuts = 0; + } + } + } + } + } + else + { + numTimeOuts = 0; + // we want to be able to do these check-and-calls without fear of interruption by another thread. there could be many threads (as many as connected controllers), + // and the callback could be time-consuming (up to the user), so we use a readers-writer-lock. + if (handle_input(jc, buf, 64, hasIMU)) { // but the user won't necessarily have a callback at all, so we'll skip the lock altogether in that case + // accumulate gyro + IMU_STATE imuState = jc->get_transformed_imu_state(jc->imu_state); + jc->push_cumulative_gyro(imuState.gyroX, imuState.gyroY, imuState.gyroZ); + if (_pollCallback != nullptr || _pollTouchCallback != nullptr) + { + std::shared_lock lock(_callbackLock); + if (_pollCallback != nullptr) { + _pollCallback(jc->intHandle, jc->simple_state, jc->last_simple_state, imuState, jc->get_transformed_imu_state(jc->last_imu_state), jc->delta_time); + } + // touchpad will have its own callback so that it doesn't change the existing api + if (jc->controller_type != ControllerType::n_switch && _pollTouchCallback != nullptr) { + _pollTouchCallback(jc->intHandle, jc->touch_state, jc->last_touch_state, jc->delta_time); + } + } + // count how many have no IMU result. We want to periodically attempt to enable IMU if it's not present + if (!hasIMU) + { + numNoIMU++; + if (numNoIMU == noIMULimit) + { + memset(buf, 0, 64); + jc->enable_IMU(buf, 64); + numNoIMU = 0; + } + } + else + { + numNoIMU = 0; + } + + // dualshock 4 bluetooth might need waking up + if (jc->controller_type == ControllerType::s_ds4 && !jc->is_usb) + { + wakeupTimer += jc->delta_time; + if (wakeupTimer > 30.0f) + { + jc->init_ds4_bt(); + wakeupTimer = 0.0f; + } + } + } + } + } + + if (jc->cancel_thread) + { + printf("\tending cancelled thread\n"); + } + else + { + printf("\ttiming out thread\n"); + } + + // remove + if (jc->remove_on_finish) + { + printf("\t\tremoving jc\n"); + if (!lockedThread) + { + _connectedLock.lock(); + } + _joyshocks.erase(jc->intHandle); + _byPath.erase(jc->path); + if (!lockedThread) + { + _connectedLock.unlock(); + } + } + + const int intHandle = jc->intHandle; + // disconnect this device + if (jc->delete_on_finish) + { + printf("\t\tdeleting jc\n"); + delete jc; + } + + if (lockedThread) + { + _connectedLock.unlock(); + } + + // notify that we disconnected this device, and say whether or not it was a timeout (if not a timeout, then an explicit disconnect) + { + std::shared_lock lock(_callbackLock); + if (_disconnectCallback != nullptr) + { + _disconnectCallback(intHandle, numTimeOuts >= 10); + } + } +} + +int JslConnectDevices() +{ + // for writing to console: + //freopen("CONOUT$", "w", stdout); + + // most of the joycon and pro controller stuff here is thanks to mfosse's vjoy feeder + // Enumerate and print the HID devices on the system + struct hid_device_info *devs, *cur_dev; + + std::vector createdIds; + + _connectedLock.lock(); + + int res = hid_init(); + + devs = hid_enumerate(0x0, 0x0); + cur_dev = devs; + while (cur_dev) { + bool isSupported = false; + bool isSwitch = false; + switch (cur_dev->vendor_id) + { + case JOYCON_VENDOR: + isSupported = cur_dev->product_id == JOYCON_L_BT || + cur_dev->product_id == JOYCON_R_BT || + cur_dev->product_id == PRO_CONTROLLER || + cur_dev->product_id == JOYCON_CHARGING_GRIP; + isSwitch = true; + break; + case DS_VENDOR: + isSupported = cur_dev->product_id == DS4_USB || + cur_dev->product_id == DS4_USB_V2 || + cur_dev->product_id == DS4_USB_DONGLE || + cur_dev->product_id == DS4_BT || + cur_dev->product_id == DS_USB; + break; + case BROOK_DS4_VENDOR: + isSupported = cur_dev->product_id == BROOK_DS4_USB; + break; + default: + break; + } + if (!isSupported) + { + cur_dev = cur_dev->next; + continue; + } + + const std::string path = std::string(cur_dev->path); + auto iter = _byPath.find(path); + JoyShock* currentJc = nullptr; + bool isSameController = false; + if (iter != _byPath.end()) + { + currentJc = iter->second; + isSameController = isSwitch == (currentJc->controller_type == ControllerType::n_switch); + } + printf("path: %s\n", cur_dev->path); + + hid_device* handle = hid_open_path(cur_dev->path); + if (handle != nullptr) + { + printf("\topened new handle\n"); + if (isSameController) + { + printf("\tending old thread and joining\n"); + // we'll get a new thread, so we need to delete the old one, but we need to actually wait for it, I think, because it'll be affected by init and all that... + // but we can't wait for it! That could deadlock if it happens to be about to disconnect or time out! + std::thread* thread = currentJc->thread; + currentJc->delete_on_finish = false; + currentJc->remove_on_finish = false; + currentJc->cancel_thread = true; + currentJc->reuse_counter++; + + // finishing thread may be about to hit a lock + _connectedLock.unlock(); + thread->join(); + _connectedLock.lock(); + + delete thread; + currentJc->thread = nullptr; + // don't immediately cancel the next thread: + currentJc->cancel_thread = false; + currentJc->delete_on_finish = false; + currentJc->remove_on_finish = true; + + printf("\tinitialising with new handle\n"); + // keep calibration stuff, but reset other stuff just in case it's actually a new controller + currentJc->init(cur_dev, handle, GetUniqueHandle(path), path); + currentJc = nullptr; + } + else + { + printf("\tcreating new JoyShock\n"); + JoyShock* jc = new JoyShock(cur_dev, handle, GetUniqueHandle(path), path); + _joyshocks.emplace(jc->intHandle, jc); + _byPath.emplace(path, jc); + createdIds.push_back(jc->intHandle); + } + + if (currentJc != nullptr) + { + printf("\tdeinitialising old controller\n"); + // it's been replaced! get rid of it + if (currentJc->controller_type == ControllerType::s_ds4) { + if (currentJc->is_usb) { + currentJc->deinit_ds4_usb(); + } + else { + currentJc->deinit_ds4_bt(); + } + } + else if (currentJc->controller_type == ControllerType::s_ds) { + + } + else if (currentJc->is_usb) { + currentJc->deinit_usb(); + } + printf("\tending old thread\n"); + std::thread* thread = currentJc->thread; + currentJc->delete_on_finish = true; + currentJc->remove_on_finish = false; + currentJc->cancel_thread = true; + thread->detach(); + delete thread; + currentJc->thread = nullptr; + } + } + + cur_dev = cur_dev->next; + } + hid_free_enumeration(devs); + + // init joyshocks: + for (std::pair pair : _joyshocks) + { + JoyShock* jc = pair.second; + + if (jc->initialised) + { + continue; + } + + if (jc->controller_type == ControllerType::s_ds4) { + if (!jc->is_usb) { + jc->init_ds4_bt(); + } + else { + jc->init_ds4_usb(); + } + } // dualsense + else if (jc->controller_type == ControllerType::s_ds) + { + jc->initialised = true; + } // charging grip + else if (jc->is_usb) { + //printf("USB\n"); + jc->init_usb(); + } + else { + //printf("BT\n"); + jc->init_bt(); + } + // all get time now for polling + jc->last_polled = std::chrono::steady_clock::now(); + jc->delta_time = 0.0; + + jc->deviceNumber = 0; // left + } + + unsigned char buf[64]; + + // set lights: + //printf("setting LEDs...\n"); + int switchIndex = 1; + int dualSenseIndex = 1; + for (std::pair pair : _joyshocks) + { + JoyShock *jc = pair.second; + + // restore colours if we have them set for this controller + switch (jc->controller_type) + { + case ControllerType::s_ds4: + jc->set_ds4_rumble_light(0, 0, jc->led_r, jc->led_g, jc->led_b); + break; + case ControllerType::s_ds: + jc->set_ds5_rumble_light(0, 0, jc->led_r, jc->led_g, jc->led_b, dualSenseIndex++); + break; + case ControllerType::n_switch: + jc->player_number = switchIndex++; + memset(buf, 0x00, 0x40); + buf[0] = (unsigned char)jc->player_number; + jc->send_subcommand(0x01, 0x30, buf, 1); + break; + } + + // threads for polling + if (jc->thread == nullptr) + { + printf("\tstarting new thread\n"); + jc->thread = new std::thread(pollIndividualLoop, jc); + } + } + + const int totalDevices = (int)_joyshocks.size(); + + _connectedLock.unlock(); + + // notify that we created the new object (now that we're not in a lock that might prevent reading data) + { + std::shared_lock lock(_callbackLock); + if (_connectCallback != nullptr) + { + for (int newConnectionHandle : createdIds) + { + _connectCallback(newConnectionHandle); + } + } + } + + return totalDevices; +} + +int JslGetConnectedDeviceHandles(int* deviceHandleArray, int size) +{ + int i = 0; + _connectedLock.lock_shared(); + for (std::pair pair : _joyshocks) + { + if (i >= size) { + break; + } + deviceHandleArray[i] = pair.first; + i++; + } + _connectedLock.unlock_shared(); + return i; // return num actually found +} + +void JslDisconnectAndDisposeAll() +{ + // no more callback + JslSetCallback(nullptr); + JslSetTouchCallback(nullptr); + + _connectedLock.lock(); + + for (std::pair pair : _joyshocks) + { + JoyShock* jc = pair.second; + if (jc->controller_type == ControllerType::s_ds4) { + if (jc->is_usb) { + jc->deinit_ds4_usb(); + } + else { + jc->deinit_ds4_bt(); + } + } + else if (jc->controller_type == ControllerType::s_ds) { + + } // TODO: Charging grip? bluetooth? + else if (jc->is_usb) { + jc->deinit_usb(); + } + // cleanup + std::thread* thread = jc->thread; + jc->delete_on_finish = true; + jc->remove_on_finish = false; + jc->cancel_thread = true; + thread->detach(); + delete thread; + } + _joyshocks.clear(); + _byPath.clear(); + + _connectedLock.unlock(); + + // Finalize the hidapi library + int res = hid_exit(); +} + +bool JslStillConnected(int deviceId) +{ + std::shared_lock lock(_connectedLock); + return GetJoyShockFromHandle(deviceId) != nullptr; +} + +// get buttons as bits in the following order, using North South East West to name face buttons to avoid ambiguity between Xbox and Nintendo layouts: +// 0x00001: up +// 0x00002: down +// 0x00004: left +// 0x00008: right +// 0x00010: plus +// 0x00020: minus +// 0x00040: left stick click +// 0x00080: right stick click +// 0x00100: L +// 0x00200: R +// ZL and ZR are reported as analogue inputs (GetLeftTrigger, GetRightTrigger), because DS4 and XBox controllers use analogue triggers, but we also have them as raw buttons +// 0x00400: ZL +// 0x00800: ZR +// 0x01000: S +// 0x02000: E +// 0x04000: W +// 0x08000: N +// 0x10000: home / PS +// 0x20000: capture / touchpad-click +// 0x40000: SL +// 0x80000: SR +// if you want the whole state, this is the best way to do it +JOY_SHOCK_STATE JslGetSimpleState(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state; + } + return {}; +} +IMU_STATE JslGetIMUState(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->get_transformed_imu_state(jc->imu_state); + } + return {}; +} +MOTION_STATE JslGetMotionState(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->get_motion_state(); + } + return {}; +} +TOUCH_STATE JslGetTouchState(int deviceId, bool previous) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return previous ? jc->last_touch_state : jc->touch_state; + } + return {}; +} + +bool JslGetTouchpadDimension(int deviceId, int &sizeX, int &sizeY) +{ + std::shared_lock lock(_connectedLock); + // I am assuming a single touchpad (or all touchpads are the same dimension)? + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) + { + switch (jc->controller_type) + { + case ControllerType::s_ds4: + case ControllerType::s_ds: + sizeX = 1920; + sizeY = 943; + break; + default: + sizeX = 0; + sizeY = 0; + break; + } + return true; + } + return false; +} + +int JslGetButtons(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.buttons; + } + return 0; +} + +// get thumbsticks +float JslGetLeftX(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.stickLX; + } + return 0.0f; +} +float JslGetLeftY(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.stickLY; + } + return 0.0f; +} +float JslGetRightX(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.stickRX; + } + return 0.0f; +} +float JslGetRightY(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.stickRY; + } + return 0.0f; +} + +// get triggers. Switch controllers don't have analogue triggers, but will report 0.0 or 1.0 so they can be used in the same way as others +float JslGetLeftTrigger(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.lTrigger; + } + return 0.0f; +} +float JslGetRightTrigger(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->simple_state.rTrigger; + } + return 0.0f; +} + +// get gyro +float JslGetGyroX(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->get_transformed_imu_state(jc->imu_state).gyroX; + } + return 0.0f; +} +float JslGetGyroY(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->get_transformed_imu_state(jc->imu_state).gyroY; + } + return 0.0f; +} +float JslGetGyroZ(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->get_transformed_imu_state(jc->imu_state).gyroZ; + } + return 0.0f; +} + +void JslGetAndFlushAccumulatedGyro(int deviceId, float& gyroX, float& gyroY, float& gyroZ) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->get_and_flush_cumulative_gyro(gyroX, gyroY, gyroZ); + return; + } + gyroX = gyroY = gyroZ = 0.f; +} + +void JslSetGyroSpace(int deviceId, int gyroSpace) +{ + if (gyroSpace < 0) { + gyroSpace = 0; + } + if (gyroSpace > 2) { + gyroSpace = 2; + } + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->modifying_lock.lock(); + jc->gyroSpace = gyroSpace; + jc->modifying_lock.unlock(); + } +} + +// get accelerometor +float JslGetAccelX(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->imu_state.accelX; + } + return 0.0f; +} +float JslGetAccelY(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->imu_state.accelY; + } + return 0.0f; +} +float JslGetAccelZ(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->imu_state.accelZ; + } + return 0.0f; +} + +// get touchpad +int JslGetTouchId(int deviceId, bool secondTouch) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + if (!secondTouch) { + return jc->touch_state.t0Id; + } + else { + return jc->touch_state.t1Id; + } + } + return false; +} +bool JslGetTouchDown(int deviceId, bool secondTouch) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + if (!secondTouch) { + return jc->touch_state.t0Down; + } + else { + return jc->touch_state.t1Down; + } + } + return false; +} + +float JslGetTouchX(int deviceId, bool secondTouch) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + if (!secondTouch) { + return jc->touch_state.t0X; + } + else { + return jc->touch_state.t1X; + } + } + return 0.0f; +} +float JslGetTouchY(int deviceId, bool secondTouch) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + if (!secondTouch) { + return jc->touch_state.t0Y; + } + else { + return jc->touch_state.t1Y; + } + } + return 0.0f; +} + +// analog parameters have different resolutions depending on device +float JslGetStickStep(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + if (jc->controller_type != ControllerType::n_switch) { + return 1.0f / 128.0; + } + else { + if (jc->left_right == 2) // right joycon has no calibration for left stick + { + return 1.0f / (jc->stick_cal_x_r[2] - jc->stick_cal_x_r[1]); + } + else { + return 1.0f / (jc->stick_cal_x_l[2] - jc->stick_cal_x_l[1]); + } + } + } + return 0.0f; +} +float JslGetTriggerStep(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->controller_type != ControllerType::n_switch ? 1 / 256.0f : 1.0f; + } + return 1.0f; +} +float JslGetPollRate(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->controller_type != ControllerType::n_switch ? 250.0f : 66.6667f; + } + return 0.0f; +} +float JslGetTimeSinceLastUpdate(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + auto time_now = std::chrono::steady_clock::now(); + return (float)(std::chrono::duration_cast(time_now - jc->last_polled).count() / 1000000.0); + } + return 0.0f; +} + +// calibration +void JslResetContinuousCalibration(int deviceId) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->reset_continuous_calibration(); + } +} +void JslStartContinuousCalibration(int deviceId) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->use_continuous_calibration = true; + jc->cue_motion_reset = true; + } +} +void JslPauseContinuousCalibration(int deviceId) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->use_continuous_calibration = false; + } +} +void JslSetAutomaticCalibration(int deviceId, bool enabled) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->modifying_lock.lock(); + jc->motion.SetCalibrationMode(enabled ? GamepadMotionHelpers::CalibrationMode::SensorFusion | GamepadMotionHelpers::CalibrationMode::Stillness : GamepadMotionHelpers::CalibrationMode::Manual); + jc->modifying_lock.unlock(); + } +} +void JslGetCalibrationOffset(int deviceId, float& xOffset, float& yOffset, float& zOffset) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + // not technically modifying, but also not a simple getter + jc->modifying_lock.lock(); + jc->motion.GetCalibrationOffset(xOffset, yOffset, zOffset); + jc->modifying_lock.unlock(); + } +} +void JslSetCalibrationOffset(int deviceId, float xOffset, float yOffset, float zOffset) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + jc->modifying_lock.lock(); + jc->motion.SetCalibrationOffset(xOffset, yOffset, zOffset, 1); + jc->modifying_lock.unlock(); + } +} +JSL_AUTO_CALIBRATION JslGetAutoCalibrationStatus(int deviceId) { + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + JSL_AUTO_CALIBRATION calibration; + + calibration.autoCalibrationEnabled = jc->motion.GetCalibrationMode() != GamepadMotionHelpers::CalibrationMode::Manual; + calibration.confidence = jc->motion.GetAutoCalibrationConfidence(); + calibration.isSteady = jc->motion.GetAutoCalibrationIsSteady(); + + return calibration; + } + + return {}; +} + +// this function will get called for each input event from each controller +void JslSetCallback(void(*callback)(int, JOY_SHOCK_STATE, JOY_SHOCK_STATE, IMU_STATE, IMU_STATE, float)) { + // exclusive lock + _callbackLock.lock(); + _pollCallback = callback; + _callbackLock.unlock(); +} + +// this function will get called for each input event, even if touch data didn't update +void JslSetTouchCallback(void(*callback)(int, TOUCH_STATE, TOUCH_STATE, float)) { + _callbackLock.lock(); + _pollTouchCallback = callback; + _callbackLock.unlock(); +} + +// this function will get called for each device when it is newly connected +void JslSetConnectCallback(void(*callback)(int)) { + // exclusive lock + _callbackLock.lock(); + _connectCallback = callback; + _callbackLock.unlock(); +} + +// this function will get called for each device when it is disconnected (and whether it was a timeout (true)) +void JslSetDisconnectCallback(void(*callback)(int, bool)) { + // exclusive lock + _callbackLock.lock(); + _disconnectCallback = callback; + _callbackLock.unlock(); +} + +// super-getter for reading a whole lot of state at once +JSL_SETTINGS JslGetControllerInfoAndSettings(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + JSL_SETTINGS settings; + + settings.gyroSpace = jc->gyroSpace; + settings.playerNumber = jc->player_number; + settings.splitType = jc->left_right; + settings.isConnected = true; + settings.isCalibrating = jc->use_continuous_calibration; + settings.autoCalibrationEnabled = jc->motion.GetCalibrationMode() != GamepadMotionHelpers::CalibrationMode::Manual; + + switch (jc->controller_type) + { + case ControllerType::s_ds4: + settings.controllerType = JS_TYPE_DS4; + break; + case ControllerType::s_ds: + settings.controllerType = JS_TYPE_DS; + break; + default: + case ControllerType::n_switch: + settings.controllerType = jc->left_right; + settings.colour = jc->body_colour; + break; + } + + if (jc->controller_type != ControllerType::n_switch) + { + // get led colour + settings.colour = (int)(jc->led_b) | ((int)(jc->led_g) << 8) | ((int)(jc->led_r) << 16); + } + + return settings; + } + return {}; +} + +// what split type of controller is this? +int JslGetControllerType(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + switch (jc->controller_type) + { + case ControllerType::s_ds4: + return JS_TYPE_DS4; + case ControllerType::s_ds: + return JS_TYPE_DS; + default: + case ControllerType::n_switch: + return jc->left_right; + } + } + return 0; +} +// what split type of controller is this? +int JslGetControllerSplitType(int deviceId) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->left_right; + } + return 0; +} +// what colour is the controller (not all controllers support this; those that don't will report white) +int JslGetControllerColour(int deviceId) +{ + std::shared_lock lock(_connectedLock); + // this just reports body colour. Switch controllers also give buttons colour, and in Pro's case, left and right grips + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr) { + return jc->body_colour; + } + return 0xFFFFFF; +} +// set controller light colour (not all controllers have a light whose colour can be set, but that just means nothing will be done when this is called -- no harm) +void JslSetLightColour(int deviceId, int colour) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr && jc->controller_type == ControllerType::s_ds4) { + jc->modifying_lock.lock(); + jc->led_r = (colour >> 16) & 0xff; + jc->led_g = (colour >> 8) & 0xff; + jc->led_b = colour & 0xff; + jc->set_ds4_rumble_light( + jc->small_rumble, + jc->big_rumble, + jc->led_r, + jc->led_g, + jc->led_b); + jc->modifying_lock.unlock(); + } + else if(jc != nullptr && jc->controller_type == ControllerType::s_ds) { + jc->modifying_lock.lock(); + jc->led_r = (colour >> 16) & 0xff; + jc->led_g = (colour >> 8) & 0xff; + jc->led_b = colour & 0xff; + jc->set_ds5_rumble_light( + jc->small_rumble, + jc->big_rumble, + jc->led_r, + jc->led_g, + jc->led_b, + jc->player_number); + jc->modifying_lock.unlock(); + } +} +// set controller rumble +void JslSetRumble(int deviceId, int smallRumble, int bigRumble) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr && jc->controller_type == ControllerType::s_ds4) { + jc->modifying_lock.lock(); + jc->small_rumble = smallRumble; + jc->big_rumble = bigRumble; + jc->set_ds4_rumble_light( + jc->small_rumble, + jc->big_rumble, + jc->led_r, + jc->led_g, + jc->led_b); + jc->modifying_lock.unlock(); + } + else if (jc != nullptr && jc->controller_type == ControllerType::s_ds) { + jc->modifying_lock.lock(); + jc->small_rumble = smallRumble; + jc->big_rumble = bigRumble; + jc->set_ds5_rumble_light( + jc->small_rumble, + jc->big_rumble, + jc->led_r, + jc->led_g, + jc->led_b, + jc->player_number); + jc->modifying_lock.unlock(); + } +} +// set controller player number indicator (not all controllers have a number indicator which can be set, but that just means nothing will be done when this is called -- no harm) +void JslSetPlayerNumber(int deviceId, int number) +{ + std::shared_lock lock(_connectedLock); + JoyShock* jc = GetJoyShockFromHandle(deviceId); + if (jc != nullptr && jc->controller_type == ControllerType::n_switch) { + jc->modifying_lock.lock(); + jc->player_number = number; + unsigned char buf[64]; + memset(buf, 0x00, 0x40); + buf[0] = (unsigned char)number; + jc->send_subcommand(0x01, 0x30, buf, 1); + jc->modifying_lock.unlock(); + } + else if(jc != nullptr && jc->controller_type == ControllerType::s_ds) { + jc->modifying_lock.lock(); + jc->player_number = number; + jc->set_ds5_rumble_light( + jc->small_rumble, + jc->big_rumble, + jc->led_r, + jc->led_g, + jc->led_b, + jc->player_number); + jc->modifying_lock.unlock(); + } +} diff --git a/project/dependencies/JoyShockLibrary/JoyShockLibrary.h b/project/dependencies/JoyShockLibrary/JoyShockLibrary.h new file mode 100644 index 0000000..101940f --- /dev/null +++ b/project/dependencies/JoyShockLibrary/JoyShockLibrary.h @@ -0,0 +1,248 @@ +// JoyShockLibrary.h - Contains declarations of functions +#pragma once + +#if _MSC_VER // this is defined when compiling with Visual Studio +#define JOY_SHOCK_API __declspec(dllexport) // Visual Studio needs annotating exported functions with this +#else +#define JOY_SHOCK_API // XCode does not need annotating exported functions, so define is empty +#endif + +#define JS_TYPE_JOYCON_LEFT 1 +#define JS_TYPE_JOYCON_RIGHT 2 +#define JS_TYPE_PRO_CONTROLLER 3 +#define JS_TYPE_DS4 4 +#define JS_TYPE_DS 5 + +#define JS_SPLIT_TYPE_LEFT 1 +#define JS_SPLIT_TYPE_RIGHT 2 +#define JS_SPLIT_TYPE_FULL 3 + +#define JSMASK_UP 0x00001 +#define JSMASK_DOWN 0x00002 +#define JSMASK_LEFT 0x00004 +#define JSMASK_RIGHT 0x00008 +#define JSMASK_PLUS 0x00010 +#define JSMASK_OPTIONS 0x00010 +#define JSMASK_MINUS 0x00020 +#define JSMASK_SHARE 0x00020 +#define JSMASK_LCLICK 0x00040 +#define JSMASK_RCLICK 0x00080 +#define JSMASK_L 0x00100 +#define JSMASK_R 0x00200 +#define JSMASK_ZL 0x00400 +#define JSMASK_ZR 0x00800 +#define JSMASK_S 0x01000 +#define JSMASK_E 0x02000 +#define JSMASK_W 0x04000 +#define JSMASK_N 0x08000 +#define JSMASK_HOME 0x10000 +#define JSMASK_PS 0x10000 +#define JSMASK_CAPTURE 0x20000 +#define JSMASK_TOUCHPAD_CLICK 0x20000 +#define JSMASK_MIC 0x40000 +#define JSMASK_SL 0x40000 +#define JSMASK_SR 0x80000 + +#define JSOFFSET_UP 0 +#define JSOFFSET_DOWN 1 +#define JSOFFSET_LEFT 2 +#define JSOFFSET_RIGHT 3 +#define JSOFFSET_PLUS 4 +#define JSOFFSET_OPTIONS 4 +#define JSOFFSET_MINUS 5 +#define JSOFFSET_SHARE 5 +#define JSOFFSET_LCLICK 6 +#define JSOFFSET_RCLICK 7 +#define JSOFFSET_L 8 +#define JSOFFSET_R 9 +#define JSOFFSET_ZL 10 +#define JSOFFSET_ZR 11 +#define JSOFFSET_S 12 +#define JSOFFSET_E 13 +#define JSOFFSET_W 14 +#define JSOFFSET_N 15 +#define JSOFFSET_HOME 16 +#define JSOFFSET_PS 16 +#define JSOFFSET_CAPTURE 17 +#define JSOFFSET_TOUCHPAD_CLICK 17 +#define JSOFFSET_MIC 18 +#define JSOFFSET_SL 18 +#define JSOFFSET_SR 19 + +// PS5 Player maps for the DS Player Lightbar +#define DS5_PLAYER_1 4 +#define DS5_PLAYER_2 10 +#define DS5_PLAYER_3 21 +#define DS5_PLAYER_4 27 +#define DS5_PLAYER_5 31 + +typedef struct JOY_SHOCK_STATE { + int buttons = 0; + float lTrigger = 0.f; + float rTrigger = 0.f; + float stickLX = 0.f; + float stickLY = 0.f; + float stickRX = 0.f; + float stickRY = 0.f; +} JOY_SHOCK_STATE; + +typedef struct IMU_STATE { + float accelX = 0.f; + float accelY = 0.f; + float accelZ = 0.f; + float gyroX = 0.f; + float gyroY = 0.f; + float gyroZ = 0.f; +} IMU_STATE; + +typedef struct MOTION_STATE { + float quatW = 0.f; + float quatX = 0.f; + float quatY = 0.f; + float quatZ = 0.f; + float accelX = 0.f; + float accelY = 0.f; + float accelZ = 0.f; + float gravX = 0.f; + float gravY = 0.f; + float gravZ = 0.f; +} MOTION_STATE; + +typedef struct TOUCH_STATE { + int t0Id = 0; + int t1Id = 0; + bool t0Down = false; + bool t1Down = false; + float t0X = 0.f; + float t0Y = 0.f; + float t1X = 0.f; + float t1Y = 0.f; +} TOUCH_STATE; + +typedef struct JSL_AUTO_CALIBRATION { + float confidence = 0.f; + bool autoCalibrationEnabled = false; + bool isSteady = false; +} JSL_AUTO_CALIBRATION; + +typedef struct JSL_SETTINGS { + int gyroSpace = 0; + int colour = 0; + int playerNumber = 0; + int controllerType = 0; + int splitType = 0; + bool isCalibrating = false; + bool autoCalibrationEnabled = false; + bool isConnected = false; +} JSL_SETTINGS; + +extern "C" JOY_SHOCK_API int JslConnectDevices(); +extern "C" JOY_SHOCK_API int JslGetConnectedDeviceHandles(int* deviceHandleArray, int size); +extern "C" JOY_SHOCK_API void JslDisconnectAndDisposeAll(); +extern "C" JOY_SHOCK_API bool JslStillConnected(int deviceId); + +// get buttons as bits in the following order, using North South East West to name face buttons to avoid ambiguity between Xbox and Nintendo layouts: +// 0x00001: up +// 0x00002: down +// 0x00004: left +// 0x00008: right +// 0x00010: plus +// 0x00020: minus +// 0x00040: left stick click +// 0x00080: right stick click +// 0x00100: L +// 0x00200: R +// ZL and ZR are reported as analogue inputs (GetLeftTrigger, GetRightTrigger), because DS4 and XBox controllers use analogue triggers, but we also have them as raw buttons +// 0x00400: ZL +// 0x00800: ZR +// 0x01000: S +// 0x02000: E +// 0x04000: W +// 0x08000: N +// 0x10000: home / PS +// 0x20000: capture / touchpad-click +// 0x40000: SL +// 0x80000: SR +// These are the best way to get all the buttons/triggers/sticks, gyro/accelerometer (IMU), orientation/acceleration/gravity (Motion), or touchpad +extern "C" JOY_SHOCK_API JOY_SHOCK_STATE JslGetSimpleState(int deviceId); +extern "C" JOY_SHOCK_API IMU_STATE JslGetIMUState(int deviceId); +extern "C" JOY_SHOCK_API MOTION_STATE JslGetMotionState(int deviceId); +extern "C" JOY_SHOCK_API TOUCH_STATE JslGetTouchState(int deviceId, bool previous = false); +extern "C" JOY_SHOCK_API bool JslGetTouchpadDimension(int deviceId, int &sizeX, int &sizeY); + +extern "C" JOY_SHOCK_API int JslGetButtons(int deviceId); + +// get thumbsticks +extern "C" JOY_SHOCK_API float JslGetLeftX(int deviceId); +extern "C" JOY_SHOCK_API float JslGetLeftY(int deviceId); +extern "C" JOY_SHOCK_API float JslGetRightX(int deviceId); +extern "C" JOY_SHOCK_API float JslGetRightY(int deviceId); + +// get triggers. Switch controllers don't have analogue triggers, but will report 0.0 or 1.0 so they can be used in the same way as others +extern "C" JOY_SHOCK_API float JslGetLeftTrigger(int deviceId); +extern "C" JOY_SHOCK_API float JslGetRightTrigger(int deviceId); + +// get gyro +extern "C" JOY_SHOCK_API float JslGetGyroX(int deviceId); +extern "C" JOY_SHOCK_API float JslGetGyroY(int deviceId); +extern "C" JOY_SHOCK_API float JslGetGyroZ(int deviceId); + +// get accumulated average gyro since this function was last called or last flushed values +extern "C" JOY_SHOCK_API void JslGetAndFlushAccumulatedGyro(int deviceId, float& gyroX, float& gyroY, float& gyroZ); + +// set gyro space. JslGetGyro*, JslGetAndFlushAccumulatedGyro, JslGetIMUState, and the IMU_STATEs reported in the callback functions will use one of 3 transformations: +// 0 = local space -> no transformation is done on gyro input +// 1 = world space -> gyro input is transformed based on the calculated gravity direction to account for the player's preferred controller orientation +// 2 = player space -> a simple combination of local and world space that is as adaptive as world space but is as robust as local space +extern "C" JOY_SHOCK_API void JslSetGyroSpace(int deviceId, int gyroSpace); + +// get accelerometor +extern "C" JOY_SHOCK_API float JslGetAccelX(int deviceId); +extern "C" JOY_SHOCK_API float JslGetAccelY(int deviceId); +extern "C" JOY_SHOCK_API float JslGetAccelZ(int deviceId); + +// get touchpad +extern "C" JOY_SHOCK_API int JslGetTouchId(int deviceId, bool secondTouch = false); +extern "C" JOY_SHOCK_API bool JslGetTouchDown(int deviceId, bool secondTouch = false); + +extern "C" JOY_SHOCK_API float JslGetTouchX(int deviceId, bool secondTouch = false); +extern "C" JOY_SHOCK_API float JslGetTouchY(int deviceId, bool secondTouch = false); + +// analog parameters have different resolutions depending on device +extern "C" JOY_SHOCK_API float JslGetStickStep(int deviceId); +extern "C" JOY_SHOCK_API float JslGetTriggerStep(int deviceId); +extern "C" JOY_SHOCK_API float JslGetPollRate(int deviceId); +extern "C" JOY_SHOCK_API float JslGetTimeSinceLastUpdate(int deviceId); + +// calibration +extern "C" JOY_SHOCK_API void JslResetContinuousCalibration(int deviceId); +extern "C" JOY_SHOCK_API void JslStartContinuousCalibration(int deviceId); +extern "C" JOY_SHOCK_API void JslPauseContinuousCalibration(int deviceId); +extern "C" JOY_SHOCK_API void JslSetAutomaticCalibration(int deviceId, bool enabled); +extern "C" JOY_SHOCK_API void JslGetCalibrationOffset(int deviceId, float& xOffset, float& yOffset, float& zOffset); +extern "C" JOY_SHOCK_API void JslSetCalibrationOffset(int deviceId, float xOffset, float yOffset, float zOffset); +extern "C" JOY_SHOCK_API JSL_AUTO_CALIBRATION JslGetAutoCalibrationStatus(int deviceId); + +// this function will get called for each input event from each controller +extern "C" JOY_SHOCK_API void JslSetCallback(void(*callback)(int, JOY_SHOCK_STATE, JOY_SHOCK_STATE, IMU_STATE, IMU_STATE, float)); +// this function will get called for each input event, even if touch data didn't update +extern "C" JOY_SHOCK_API void JslSetTouchCallback(void(*callback)(int, TOUCH_STATE, TOUCH_STATE, float)); +// this function will get called for each device when it is newly connected +extern "C" JOY_SHOCK_API void JslSetConnectCallback(void(*callback)(int)); +// this function will get called for each device when it is disconnected +extern "C" JOY_SHOCK_API void JslSetDisconnectCallback(void(*callback)(int, bool)); + +// super-getter for reading a whole lot of state at once +extern "C" JOY_SHOCK_API JSL_SETTINGS JslGetControllerInfoAndSettings(int deviceId); +// what kind of controller is this? +extern "C" JOY_SHOCK_API int JslGetControllerType(int deviceId); +// is this a left, right, or full controller? +extern "C" JOY_SHOCK_API int JslGetControllerSplitType(int deviceId); +// what colour is the controller (not all controllers support this; those that don't will report white) +extern "C" JOY_SHOCK_API int JslGetControllerColour(int deviceId); +// set controller light colour (not all controllers have a light whose colour can be set, but that just means nothing will be done when this is called -- no harm) +extern "C" JOY_SHOCK_API void JslSetLightColour(int deviceId, int colour); +// set controller rumble +extern "C" JOY_SHOCK_API void JslSetRumble(int deviceId, int smallRumble, int bigRumble); +// set controller player number indicator (not all controllers have a number indicator which can be set, but that just means nothing will be done when this is called -- no harm) +extern "C" JOY_SHOCK_API void JslSetPlayerNumber(int deviceId, int number); diff --git a/project/dependencies/JoyShockLibrary/hidapi/hidapi.h b/project/dependencies/JoyShockLibrary/hidapi/hidapi.h new file mode 100644 index 0000000..e5bc2dc --- /dev/null +++ b/project/dependencies/JoyShockLibrary/hidapi/hidapi.h @@ -0,0 +1,391 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +#ifdef _WIN32 + #define HID_API_EXPORT __declspec(dllexport) + #define HID_API_CALL +#else + #define HID_API_EXPORT /**< API export macro */ + #define HID_API_CALL /**< API call macro */ +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. Valid on both Linux implementations + in all cases, and valid on the Windows implementation + only if the device contains more than one interface. */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first OUT endpoint, if + one exists. If it does not, it will send the data through + the Control Endpoint (Endpoint 0). + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read within + the timeout period, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param device A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a string describing the last error which occurred. + + @ingroup API + @param device A device handle returned from hid_open(). + + @returns + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/project/dependencies/JoyShockLibrary/stdafx.cpp b/project/dependencies/JoyShockLibrary/stdafx.cpp new file mode 100644 index 0000000..2080ba0 Binary files /dev/null and b/project/dependencies/JoyShockLibrary/stdafx.cpp differ diff --git a/project/dependencies/JoyShockLibrary/stdafx.h b/project/dependencies/JoyShockLibrary/stdafx.h new file mode 100644 index 0000000..b937b12 Binary files /dev/null and b/project/dependencies/JoyShockLibrary/stdafx.h differ diff --git a/project/dependencies/JoyShockLibrary/targetver.h b/project/dependencies/JoyShockLibrary/targetver.h new file mode 100644 index 0000000..567cd34 Binary files /dev/null and b/project/dependencies/JoyShockLibrary/targetver.h differ diff --git a/project/dependencies/JoyShockLibrary/tools.cpp b/project/dependencies/JoyShockLibrary/tools.cpp new file mode 100644 index 0000000..b0dc669 --- /dev/null +++ b/project/dependencies/JoyShockLibrary/tools.cpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +//#include + +#pragma warning(disable: 4996) + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +int16_t unsignedToSigned16(uint16_t n) { + uint16_t A = n; + uint16_t B = 0xFFFF - A; + if (A < B) { + return (int16_t)A; + } else { + return (int16_t)(-1 * B); + } +} + +int16_t uint16_to_int16(uint16_t a) { + int16_t b; + char* aPointer = (char*)&a, *bPointer = (char*)&b; + memcpy(bPointer, aPointer, sizeof(a)); + return b; +} + +uint16_t combine_uint8_t(uint8_t a, uint8_t b) { + uint16_t c = ((uint16_t)a << 8) | b; + return c; +} + +int16_t combine_gyro_data(uint8_t a, uint8_t b) { + uint16_t c = combine_uint8_t(a, b); + int16_t d = uint16_to_int16(c); + return d; +} + +float clamp(float a, float min, float max) { + if (a < min) { + return min; + } else if (a > max) { + return max; + } else { + return a; + } +} + +uint16_t clamp(uint16_t a, uint16_t min, uint16_t max) { + if (a < min) { + return min; + } + else if (a > max) { + return max; + } + else { + return a; + } +} + +unsigned createMask(unsigned a, unsigned b) { + unsigned r = 0; + for (unsigned i = a; i <= b; i++) + r |= 1 << i; + + return r; +} + +void hex_dump(unsigned char *buf, int len) { + for (int i = 0; i < len; i++) { + printf("%02x ", buf[i]); + } + printf("\n"); +} + +void hex_dump2(unsigned char *buf, int len) { + for (int i = 0; i < len; i++) { + printf("%02x ", buf[i]); + } +} + +void hex_dump_0(unsigned char *buf, int len) { + for (int i = 0; i < len; i++) { + if (buf[i] != 0) { + printf("%02x ", buf[i]); + } + } +} + +void int_dump(unsigned char *buf, int len) { + for (int i = 0; i < len; i++) { + printf("%i ", buf[i]); + } + printf("\n"); +} diff --git a/project/handmade_win32.cpp b/project/handmade_win32.cpp index fe9a31e..8e0aa01 100644 --- a/project/handmade_win32.cpp +++ b/project/handmade_win32.cpp @@ -16,6 +16,10 @@ #include "win32.h" +// Using this to get dualsense controllers +#include "JoyShockLibrary/JoyShockLibrary.h" + + NS_WIN32_BEGIN // TODO(Ed) : This is a global for now. @@ -301,7 +305,17 @@ WinMain( ) { using namespace win32; - xinput_load_library_bindings(); + // xinput_load_library_bindings(); + + using JSL_DeviceHandle = int; + u32 jsl_num_devices = JslConnectDevices(); + + JSL_DeviceHandle device_handles[4] {}; + u32 jsl_getconnected_found = JslGetConnectedDeviceHandles( device_handles, jsl_num_devices ); + if ( jsl_getconnected_found != jsl_num_devices ) + { + OutputDebugStringA( "Error: JSLGetConnectedDeviceHandles didn't find as many as were stated with JslConnectDevices\n"); + } // MessageBox( 0, L"First message!", L"Handmade Hero", MB_Ok_Btn | MB_Icon_Information ); @@ -374,6 +388,7 @@ WinMain( DispatchMessage( & msg_info ); } + // XInput Polling // TODO(Ed) : Should we poll this more frequently? for ( DWORD controller_index = 0; controller_index < XUSER_MAX_COUNT; ++ controller_index ) { @@ -406,6 +421,30 @@ WinMain( } } + // JSL Input Polling + for ( u32 jsl_device_index = 0; jsl_device_index < jsl_num_devices; ++ jsl_device_index ) + { + if ( ! JslStillConnected( device_handles[ jsl_device_index ] ) ) + { + OutputDebugStringA( "Error: JSLStillConnected returned false\n" ); + continue; + } + + JOY_SHOCK_STATE state = JslGetSimpleState( device_handles[ jsl_device_index ] ); + dpad_up = state.buttons & JSMASK_UP; + dpad_down = state.buttons & JSMASK_DOWN; + dpad_left = state.buttons & JSMASK_LEFT; + dpad_right = state.buttons & JSMASK_RIGHT; + start = state.buttons & JSMASK_PLUS; + back = state.buttons & JSMASK_MINUS; + left_shoulder = state.buttons & JSMASK_L; + right_shoulder = state.buttons & JSMASK_R; + btn_a_button = state.buttons & JSMASK_S; + btn_b_button = state.buttons & JSMASK_E; + btn_x_button = state.buttons & JSMASK_W; + btn_y_button = state.buttons & JSMASK_N; + } + x_offset += dpad_right; x_offset -= dpad_left; y_offset += dpad_up; diff --git a/project/platform/grime.h b/project/platform/grime.h index 77f1c17..e41033d 100644 --- a/project/platform/grime.h +++ b/project/platform/grime.h @@ -1,3 +1,5 @@ +#pragma once + #pragma region Platform Detection /* Platform architecture */ diff --git a/project/platform/macros.h b/project/platform/macros.h index 4699512..65c363f 100644 --- a/project/platform/macros.h +++ b/project/platform/macros.h @@ -1,3 +1,5 @@ +#pragma once + // Keywords #define global static // Global variables diff --git a/project/platform/types.h b/project/platform/types.h index 693db15..ea28c27 100644 --- a/project/platform/types.h +++ b/project/platform/types.h @@ -1,3 +1,5 @@ +#pragma once + #pragma region Basic Types #define U8_MIN 0u diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 2e3ff07..6e3aa5d 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -338,10 +338,15 @@ $includes = @( $path_deps, $path_platform ) + +# Microsoft $lib_gdi32 = 'Gdi32.lib' $lib_xinput = 'Xinput.lib' $lib_user32 = 'User32.lib' +# Github +$lib_jsl = Join-Path $path_deps 'JoyShockLibrary/x64/JoyShockLibrary.lib' + $unit = Join-Path $path_project 'handmade_win32.cpp' $executable = Join-Path $path_build 'handmade_win32.exe' @@ -351,6 +356,9 @@ $linker_args = @( $lib_gdi32, # $lib_xinput, $lib_user32, + + $lib_jsl, + $flag_link_win_subsystem_windows ) diff --git a/scripts/handmade.rdbg b/scripts/handmade.rdbg index 41871f5..c40d359 100644 Binary files a/scripts/handmade.rdbg and b/scripts/handmade.rdbg differ diff --git a/scripts/update_deps.ps1 b/scripts/update_deps.ps1 index 93f14f0..db6f03f 100644 --- a/scripts/update_deps.ps1 +++ b/scripts/update_deps.ps1 @@ -1,28 +1,65 @@ clear-host $path_root = & git rev-parse --show-toplevel +$path_data = Join-Path $path_root "data" $path_project = Join-Path $path_root "project" $path_deps = Join-Path $path_project "dependencies" $path_deps_windows = Join-Path $path_deps "windows" $path_temp = Join-Path $path_deps "temp" $path_platform = Join-Path $path_project "platform" -# Define the URL of the zip file and the destination directory -$url = "https://github.com/Ed94/gencpp/releases/download/latest/gencpp_singleheader.zip" -$destinationZip = Join-Path $path_temp "gencpp_singleheader.zip" - -# Create directories if they don't exist -if (-not (Test-Path $path_deps)) { +# Clear out the current content first +if (Test-Path $path_deps) { + Remove-Item $path_deps -Recurse -Force New-Item -ItemType Directory -Path $path_deps } -if (-not (Test-Path $path_temp)) { - New-Item -ItemType Directory -Path $path_temp -} +New-Item -ItemType Directory -Path $path_temp + +$url_gencpp = "https://github.com/Ed94/gencpp/releases/download/latest/gencpp_singleheader.zip" +$path_gencpp_zip = Join-Path $path_temp "gencpp_singleheader.zip" #region gencpp -Invoke-WebRequest -Uri $url -OutFile $destinationZip -Expand-Archive -Path $destinationZip -DestinationPath $path_temp +Invoke-WebRequest -Uri $url_gencpp -OutFile $path_gencpp_zip +Expand-Archive -Path $path_gencpp_zip -DestinationPath $path_temp Move-Item -Path (Join-Path $path_temp "gen.hpp") -Destination $path_deps -Force #endregion gencpp +#region JoyShockLibrary +$url_jsl_repo = "https://github.com/JibbSmart/JoyShockLibrary.git" +# $url_jsl_zip = "https://github.com/JibbSmart/JoyShockLibrary/releases/download/v3.0/JSL_3_0.zip" +$url_jsl_zip = "https://github.com/Ed94/JoyShockLibrary/releases/download/not_for_public_use/JSL.zip" +$path_jsl_repo = Join-Path $path_temp "JoyShockLibraryRepo" +$path_jsl_repo_code = Join-Path $path_jsl_repo "JoyShockLibrary" +$path_jsl_lib_zip = Join-Path $path_temp "JSL_3_0.zip" +$path_jsl = Join-Path $path_deps "JoyShockLibrary" +$path_jsl_hidapi = Join-Path $path_jsl "hidapi" +$path_jsl_lib = Join-Path $path_jsl "x64" + +# Grab code from repo +& git clone $url_jsl_repo $path_jsl_repo +Move-Item -Path $path_jsl_repo_code -Destination $path_deps -Force + +# Clean up the junk +@( $path_jsl, $path_jsl_hidapi ) | ForEach-Object { + Get-ChildItem -Path $path_jsl -Recurse -File | Where-Object { + ($_.Extension -ne ".h" -and $_.Extension -ne ".cpp") + } | Remove-Item -Force +} +Remove-Item (join-path $path_jsl_hidapi 'objs') -Recurse -Force + +# Get precompiled binaries +Invoke-WebRequest -Uri $url_jsl_zip -OutFile $path_jsl_lib_zip +Expand-Archive -Path $path_jsl_lib_zip -DestinationPath $path_temp + +if (-not (Test-Path $path_jsl_lib)) { + New-Item -ItemType Directory -Path $path_jsl_lib +} + +$jsl_lib_files = (Get-ChildItem (Join-Path $path_temp "JSL\x64") -Recurse -Include *.dll, *.lib) +Move-Item $jsl_lib_files -Destination $path_jsl_lib -Force + +$path_jsl_dll = Join-Path $path_jsl_lib "JoyShockLibrary.dll" +Move-Item $path_jsl_dll $path_data -Force +#endregion JoyShockLibrary + Remove-Item $path_temp -Recurse -Force