mirror of
https://github.com/cathery/sys-con.git
synced 2024-12-26 21:15:00 +00:00
329 lines
9.9 KiB
C++
329 lines
9.9 KiB
C++
#include "Controllers/XboxOneController.h"
|
|
#include <cmath>
|
|
#include "../../source/log.h"
|
|
|
|
static ControllerConfig _xboxoneControllerConfig{};
|
|
|
|
#define TRIGGER_MAXVALUE 1023
|
|
|
|
//Following input packets were referenced from https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
|
|
// and https://github.com/360Controller/360Controller/blob/master/360Controller/_60Controller.cpp
|
|
|
|
//Enables LED on the PowerA controller but disables input?
|
|
static constexpr uint8_t xboxone_powerA_ledOn[] = {
|
|
0x04, 0x20, 0x01, 0x00};
|
|
|
|
//does something maybe
|
|
static constexpr uint8_t xboxone_test_init1[] = {
|
|
0x01, 0x20, 0x01, 0x09, 0x00, 0x04, 0x20, 0x3a,
|
|
0x00, 0x00, 0x00, 0x98, 0x00};
|
|
|
|
//required for all xbox one controllers
|
|
static constexpr uint8_t xboxone_fw2015_init[] = {
|
|
0x05, 0x20, 0x00, 0x01, 0x00};
|
|
|
|
static constexpr uint8_t xboxone_hori_init[] = {
|
|
0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
|
|
0x00, 0x00, 0x00, 0x80, 0x00};
|
|
|
|
static constexpr uint8_t xboxone_pdp_init1[] = {
|
|
0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
|
|
|
|
static constexpr uint8_t xboxone_pdp_init2[] = {
|
|
0x06, 0x20, 0x00, 0x02, 0x01, 0x00};
|
|
|
|
static constexpr uint8_t xboxone_rumblebegin_init[] = {
|
|
0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
|
|
0x1D, 0x1D, 0xFF, 0x00, 0x00};
|
|
|
|
static constexpr uint8_t xboxone_rumbleend_init[] = {
|
|
0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
struct VendorProductPacket
|
|
{
|
|
uint16_t VendorID;
|
|
uint16_t ProductID;
|
|
const uint8_t *Packet;
|
|
uint8_t Length;
|
|
};
|
|
|
|
static constexpr VendorProductPacket init_packets[]{
|
|
{0x0e6f, 0x0165, xboxone_hori_init, sizeof(xboxone_hori_init)},
|
|
{0x0f0d, 0x0067, xboxone_hori_init, sizeof(xboxone_hori_init)},
|
|
|
|
{0x0000, 0x0000, xboxone_test_init1, sizeof(xboxone_test_init1)},
|
|
|
|
{0x0000, 0x0000, xboxone_fw2015_init, sizeof(xboxone_fw2015_init)},
|
|
{0x0e6f, 0x0000, xboxone_pdp_init1, sizeof(xboxone_pdp_init1)},
|
|
{0x0e6f, 0x0000, xboxone_pdp_init2, sizeof(xboxone_pdp_init2)},
|
|
{0x24c6, 0x0000, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init)},
|
|
{0x24c6, 0x0000, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init)},
|
|
};
|
|
|
|
XboxOneController::XboxOneController(std::unique_ptr<IUSBDevice> &&interface)
|
|
: IController(std::move(interface))
|
|
{
|
|
}
|
|
|
|
XboxOneController::~XboxOneController()
|
|
{
|
|
Exit();
|
|
}
|
|
|
|
Result XboxOneController::Initialize()
|
|
{
|
|
Result rc;
|
|
|
|
rc = OpenInterfaces();
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
|
|
rc = SendInitBytes();
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
return rc;
|
|
}
|
|
void XboxOneController::Exit()
|
|
{
|
|
CloseInterfaces();
|
|
}
|
|
|
|
Result XboxOneController::OpenInterfaces()
|
|
{
|
|
Result rc;
|
|
rc = m_device->Open();
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
|
|
//This will open each interface and try to acquire Xbox One controller's in and out endpoints, if it hasn't already
|
|
std::vector<std::unique_ptr<IUSBInterface>> &interfaces = m_device->GetInterfaces();
|
|
for (auto &&interface : interfaces)
|
|
{
|
|
rc = interface->Open();
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
|
|
if (interface->GetDescriptor()->bInterfaceProtocol != 208)
|
|
continue;
|
|
|
|
if (interface->GetDescriptor()->bNumEndpoints < 2)
|
|
continue;
|
|
|
|
if (!m_inPipe)
|
|
{
|
|
for (uint8_t i = 0; i != 15; ++i)
|
|
{
|
|
IUSBEndpoint *inEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_IN, i);
|
|
if (inEndpoint)
|
|
{
|
|
rc = inEndpoint->Open();
|
|
if (R_FAILED(rc))
|
|
return 5555;
|
|
|
|
m_inPipe = inEndpoint;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_outPipe)
|
|
{
|
|
for (uint8_t i = 0; i != 15; ++i)
|
|
{
|
|
IUSBEndpoint *outEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_OUT, i);
|
|
if (outEndpoint)
|
|
{
|
|
rc = outEndpoint->Open();
|
|
if (R_FAILED(rc))
|
|
return 6666;
|
|
|
|
m_outPipe = outEndpoint;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_inPipe || !m_outPipe)
|
|
return 69;
|
|
|
|
return rc;
|
|
}
|
|
void XboxOneController::CloseInterfaces()
|
|
{
|
|
//m_device->Reset();
|
|
m_device->Close();
|
|
}
|
|
|
|
Result XboxOneController::GetInput()
|
|
{
|
|
uint8_t input_bytes[64];
|
|
|
|
Result rc = m_inPipe->Read(input_bytes, sizeof(input_bytes));
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
|
|
uint8_t type = input_bytes[0];
|
|
|
|
if (type == XBONEINPUT_BUTTON) //Button data
|
|
{
|
|
m_buttonData = *reinterpret_cast<XboxOneButtonData *>(input_bytes);
|
|
}
|
|
else if (type == XBONEINPUT_GUIDEBUTTON) //Guide button Result
|
|
{
|
|
m_GuidePressed = input_bytes[4];
|
|
|
|
//Xbox one S needs to be sent an ack report for guide buttons
|
|
//TODO: needs testing
|
|
if (input_bytes[1] == 0x30)
|
|
{
|
|
rc = WriteAckGuideReport(input_bytes[2]);
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
Result XboxOneController::SendInitBytes()
|
|
{
|
|
Result rc;
|
|
uint16_t vendor = m_device->GetVendor();
|
|
uint16_t product = m_device->GetProduct();
|
|
for (int i = 0; i != (sizeof(init_packets) / sizeof(VendorProductPacket)); ++i)
|
|
{
|
|
if (init_packets[i].VendorID != 0 && init_packets[i].VendorID != vendor)
|
|
continue;
|
|
if (init_packets[i].ProductID != 0 && init_packets[i].ProductID != product)
|
|
continue;
|
|
|
|
rc = m_outPipe->Write(init_packets[i].Packet, init_packets[i].Length);
|
|
if (R_FAILED(rc))
|
|
break;
|
|
else
|
|
WriteToLog("Sent a specific init packet %i for controller v%u p%u", i, vendor, product);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
float XboxOneController::NormalizeTrigger(uint16_t value)
|
|
{
|
|
uint16_t deadzone = (TRIGGER_MAXVALUE * _xboxoneControllerConfig.triggerDeadzonePercent) / 100;
|
|
//If the given value is below the trigger zone, save the calc and return 0, otherwise adjust the value to the deadzone
|
|
return value < deadzone
|
|
? 0
|
|
: static_cast<float>(value - deadzone) / (TRIGGER_MAXVALUE - deadzone);
|
|
}
|
|
|
|
void XboxOneController::NormalizeAxis(int16_t x,
|
|
int16_t y,
|
|
uint8_t deadzonePercent,
|
|
float *x_out,
|
|
float *y_out)
|
|
{
|
|
float x_val = x;
|
|
float y_val = y;
|
|
// Determine how far the stick is pushed.
|
|
//This will never exceed 32767 because if the stick is
|
|
//horizontally maxed in one direction, vertically it must be neutral(0) and vice versa
|
|
float real_magnitude = std::sqrt(x_val * x_val + y_val * y_val);
|
|
float real_deadzone = (32767 * deadzonePercent) / 100;
|
|
// Check if the controller is outside a circular dead zone.
|
|
if (real_magnitude > real_deadzone)
|
|
{
|
|
// Clip the magnitude at its expected maximum value.
|
|
float magnitude = std::min(32767.0f, real_magnitude);
|
|
// Adjust magnitude relative to the end of the dead zone.
|
|
magnitude -= real_deadzone;
|
|
// Normalize the magnitude with respect to its expected range giving a
|
|
// magnitude value of 0.0 to 1.0
|
|
//ratio = (currentValue / maxValue) / realValue
|
|
float ratio = (magnitude / (32767 - real_deadzone)) / real_magnitude;
|
|
|
|
*x_out = x_val * ratio;
|
|
*y_out = y_val * ratio;
|
|
}
|
|
else
|
|
{
|
|
// If the controller is in the deadzone zero out the magnitude.
|
|
*x_out = *y_out = 0.0f;
|
|
}
|
|
}
|
|
|
|
//Pass by value should hopefully be optimized away by RVO
|
|
NormalizedButtonData XboxOneController::GetNormalizedButtonData()
|
|
{
|
|
NormalizedButtonData normalData;
|
|
|
|
normalData.triggers[0] = NormalizeTrigger(m_buttonData.trigger_left);
|
|
normalData.triggers[1] = NormalizeTrigger(m_buttonData.trigger_right);
|
|
|
|
NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, _xboxoneControllerConfig.leftStickDeadzonePercent,
|
|
&normalData.sticks[0].axis_x, &normalData.sticks[0].axis_y);
|
|
NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, _xboxoneControllerConfig.rightStickDeadzonePercent,
|
|
&normalData.sticks[1].axis_x, &normalData.sticks[1].axis_y);
|
|
|
|
bool buttons[NUM_CONTROLLERBUTTONS]{
|
|
m_buttonData.y,
|
|
m_buttonData.b,
|
|
m_buttonData.a,
|
|
m_buttonData.x,
|
|
m_buttonData.stick_left_click,
|
|
m_buttonData.stick_right_click,
|
|
m_buttonData.bumper_left,
|
|
m_buttonData.bumper_right,
|
|
normalData.triggers[0] > 0,
|
|
normalData.triggers[1] > 0,
|
|
m_buttonData.back,
|
|
m_buttonData.start,
|
|
m_buttonData.dpad_up,
|
|
m_buttonData.dpad_right,
|
|
m_buttonData.dpad_down,
|
|
m_buttonData.dpad_left,
|
|
m_buttonData.sync,
|
|
m_GuidePressed,
|
|
};
|
|
|
|
for (int i = 0; i != NUM_CONTROLLERBUTTONS; ++i)
|
|
{
|
|
ControllerButton button = _xboxoneControllerConfig.buttons[i];
|
|
normalData.buttons[(button != NOT_SET ? button : i)] = buttons[i];
|
|
}
|
|
|
|
return normalData;
|
|
}
|
|
|
|
Result XboxOneController::WriteAckGuideReport(uint8_t sequence)
|
|
{
|
|
Result rc;
|
|
uint8_t report[] = {
|
|
0x01, 0x20,
|
|
sequence,
|
|
0x09, 0x00, 0x07, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
rc = m_outPipe->Write(report, sizeof(report));
|
|
return rc;
|
|
}
|
|
|
|
Result XboxOneController::SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude)
|
|
{
|
|
const uint8_t rumble_data[]{
|
|
0x09, 0x00, 0x00,
|
|
0x09, 0x00, 0x0f, 0x00, 0x00,
|
|
strong_magnitude,
|
|
weak_magnitude,
|
|
0xff, 0x00, 0x00};
|
|
return m_outPipe->Write(rumble_data, sizeof(rumble_data));
|
|
}
|
|
|
|
void XboxOneController::LoadConfig(const ControllerConfig *config)
|
|
{
|
|
_xboxoneControllerConfig = *config;
|
|
}
|
|
|
|
ControllerConfig *XboxOneController::GetConfig()
|
|
{
|
|
return &_xboxoneControllerConfig;
|
|
} |