1
0
mirror of https://github.com/cathery/sys-con.git synced 2024-09-28 14:40:46 +00:00

Initial commit

This commit is contained in:
cathery 2019-10-31 21:00:42 +03:00
commit 7796e2e88d
38 changed files with 3152 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
buildApplet/*
buildSysmodule/*
*.nro
*.nacp
*.elf
*.nsp
*.npdm
*.nso
.vscode
*.code-workspace
*.flag

View File

@ -0,0 +1,23 @@
#pragma once
enum ControllerType
{
CONTROLLER_UNDEFINED,
CONTROLLER_XBOX360,
CONTROLLER_XBOXONE,
};
enum VendorIDs
{
VENDOR_MICROSOFT = 0x45e,
};
enum ProductIDs
{
PRODUCT_XBOX360 = 0x28e,
PRODUCT_XBOXONE2013 = 0x2d1,
PRODUCT_XBOXONE2015 = 0x2dd,
PRODUCT_XBOXONEELITE = 0x2e3,
PRODUCT_XBOXONES = 0x2ea,
PRODUCT_XBOXADAPTIVE = 0xb0a,
};

View File

@ -0,0 +1,64 @@
#pragma once
#include <map>
//Catch-all header to include all the controllers
#include "Controllers/Xbox360Controller.h"
#include "Controllers/XboxOneController.h"
std::vector<uint16_t> GetVendors()
{
return {VENDOR_MICROSOFT};
}
std::vector<uint16_t> GetVendorProducts(uint16_t vendor_id)
{
switch (vendor_id)
{
case VENDOR_MICROSOFT:
return {PRODUCT_XBOX360,
PRODUCT_XBOXONE2013,
PRODUCT_XBOXONE2015,
PRODUCT_XBOXONEELITE,
PRODUCT_XBOXONES,
PRODUCT_XBOXADAPTIVE};
}
return {};
}
std::unique_ptr<IController> ConstructControllerFromType(ControllerType type, std::unique_ptr<IUSBDevice> &&device)
{
//surely there must be a better way to pass a class type from a function
switch (type)
{
case CONTROLLER_XBOX360:
return std::make_unique<Xbox360Controller>(std::move(device));
case CONTROLLER_XBOXONE:
return std::make_unique<XboxOneController>(std::move(device));
default:
break;
}
return std::unique_ptr<IController>{};
}
ControllerType GetControllerTypeFromIds(uint16_t vendor_id, uint16_t product_id)
{
switch (vendor_id)
{
case VENDOR_MICROSOFT:
switch (product_id)
{
case PRODUCT_XBOX360:
return CONTROLLER_XBOX360;
case PRODUCT_XBOXONE2013:
case PRODUCT_XBOXONE2015:
case PRODUCT_XBOXONEELITE:
case PRODUCT_XBOXONES:
case PRODUCT_XBOXADAPTIVE:
return CONTROLLER_XBOXONE;
}
break;
default:
break;
}
return CONTROLLER_UNDEFINED;
}

View File

@ -0,0 +1,146 @@
#pragma once
#include "IController.h"
//References used:
//https://cs.chromium.org/chromium/src/device/gamepad/dualshock4_controller.cc
struct Dualshock3ButtonData
{
uint8_t type;
uint8_t length;
bool dpad_up : 1;
bool dpad_down : 1;
bool dpad_left : 1;
bool dpad_right : 1;
bool start : 1;
bool back : 1;
bool stick_left_click : 1;
bool stick_right_click : 1;
bool bumper_left : 1;
bool bumper_right : 1;
bool guide : 1;
bool dummy1 : 1; // Always 0.
bool a : 1;
bool b : 1;
bool x : 1;
bool y : 1;
uint8_t trigger_left;
uint8_t trigger_right;
int16_t stick_left_x;
int16_t stick_left_y;
int16_t stick_right_x;
int16_t stick_right_y;
// Always 0.
uint32_t dummy2;
uint16_t dummy3;
};
/*
typedef enum _DS3_FEATURE_VALUE
{
Ds3FeatureDeviceAddress = 0x03F2,
Ds3FeatureStartDevice = 0x03F4,
Ds3FeatureHostAddress = 0x03F5
} DS3_FEATURE_VALUE;
#define DS3_HID_COMMAND_ENABLE_SIZE 0x04
#define DS3_HID_OUTPUT_REPORT_SIZE 0x30
#define DS3_VENDOR_ID 0x054C
#define DS3_PRODUCT_ID 0x0268
#define DS4_HID_OUTPUT_REPORT_SIZE 0x20
#define DS4_VENDOR_ID 0x054C
#define DS4_PRODUCT_ID 0x05C4
#define DS4_2_PRODUCT_ID 0x09CC
#define DS4_WIRELESS_ADAPTER_PRODUCT_ID 0x0BA0
#define PS_MOVE_NAVI_PRODUCT_ID 0x042F
const uint8_t PS3_REPORT_BUFFER[] = {
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
//Used to set the LEDs on the controllers
const uint8_t LEDS[] = {
0x01, // LED1
0x02, // LED2
0x04, // LED3
0x08, // LED4
0x09, // LED5
0x0A, // LED6
0x0C, // LED7
0x0D, // LED8
0x0E, // LED9
0x0F // LED10
};
//Button data
for( int i=0 ; i<3 ; i++ ) HID_PS3_Data.button[i] = data[i+0x2];
for( int i=0 ; i<4 ; i++ ) HID_PS3_Data.analog_stick[i] = data[i+0x6];
for( int i=0 ; i<12 ; i++ ) HID_PS3_Data.button_analog[i] = data[i+0x0e];
printf("Button state 0x%02x 0x%02x 0x%02x \n", data->button[0], data->button[1], data->button[2] );
printf("Analog state 0x%02x 0x%02x 0x%02x 0x%02x \n", data->analog_stick[0], data->analog_stick[1], data->analog_stick[2], data->analog_stick[3] );
printf("Analog state ");
for( int i=0 ; i<12 ; i++ ) printf("%02x ", data->button_analog[i]);
*/
class Dualshock3Controller : public IController
{
private:
IUSBEndpoint *m_inPipe = nullptr;
IUSBEndpoint *m_outPipe = nullptr;
Dualshock3ButtonData m_buttonData;
int16_t kLeftThumbDeadzone = 0; //7849;
int16_t kRightThumbDeadzone = 0; //8689;
uint16_t kTriggerMax = 0; //1023;
uint16_t kTriggerDeadzone = 0; //120;
public:
Dualshock3Controller(std::unique_ptr<IUSBDevice> &&interface);
virtual ~Dualshock3Controller();
virtual Status Initialize();
virtual void Exit();
Status OpenInterfaces();
void CloseInterfaces();
virtual Status GetInput();
virtual NormalizedButtonData GetNormalizedButtonData();
virtual ControllerType GetType() { return CONTROLLER_XBOX360; }
inline const Dualshock3ButtonData &GetButtonData() { return m_buttonData; };
float NormalizeTrigger(uint16_t value);
void NormalizeAxis(int16_t x, int16_t y, int16_t deadzone, float *x_out, float *y_out);
Status SendInitBytes();
Status SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude);
};

View File

@ -0,0 +1,99 @@
#pragma once
#include "IController.h"
//References used:
//https://cs.chromium.org/chromium/src/device/gamepad/xbox_controller_mac.mm
struct Xbox360ButtonData
{
uint8_t type;
uint8_t length;
bool dpad_up : 1;
bool dpad_down : 1;
bool dpad_left : 1;
bool dpad_right : 1;
bool start : 1;
bool back : 1;
bool stick_left_click : 1;
bool stick_right_click : 1;
bool bumper_left : 1;
bool bumper_right : 1;
bool guide : 1;
bool dummy1 : 1; // Always 0.
bool a : 1;
bool b : 1;
bool x : 1;
bool y : 1;
uint8_t trigger_left;
uint8_t trigger_right;
int16_t stick_left_x;
int16_t stick_left_y;
int16_t stick_right_x;
int16_t stick_right_y;
// Always 0.
uint32_t dummy2;
uint16_t dummy3;
};
struct Xbox360RumbleData
{
uint8_t command;
uint8_t size;
uint8_t dummy1;
uint8_t big;
uint8_t little;
uint8_t dummy2[3];
};
enum Xbox360InputPacketType
{
XBOX360INPUT_BUTTON = 0,
XBOX360INPUT_LED = 1,
XBOX360INPUT_RUMBLE = 3,
};
class Xbox360Controller : public IController
{
private:
IUSBEndpoint *m_inPipe = nullptr;
IUSBEndpoint *m_outPipe = nullptr;
Xbox360ButtonData m_buttonData;
int16_t kLeftThumbDeadzone = 0; //7849;
int16_t kRightThumbDeadzone = 0; //8689;
uint16_t kTriggerMax = 0; //1023;
uint16_t kTriggerDeadzone = 0; //120;
public:
Xbox360Controller(std::unique_ptr<IUSBDevice> &&interface);
virtual ~Xbox360Controller();
virtual Status Initialize();
virtual void Exit();
Status OpenInterfaces();
void CloseInterfaces();
virtual Status GetInput();
virtual NormalizedButtonData GetNormalizedButtonData();
virtual ControllerType GetType() { return CONTROLLER_XBOX360; }
inline const Xbox360ButtonData &GetButtonData() { return m_buttonData; };
float NormalizeTrigger(uint16_t value);
void NormalizeAxis(int16_t x, int16_t y, int16_t deadzone, float *x_out, float *y_out);
Status SendInitBytes();
Status SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude);
};

View File

@ -0,0 +1,113 @@
#pragma once
#include "IController.h"
//References used:
//https://github.com/quantus/xbox-one-controller-protocol
//https://cs.chromium.org/chromium/src/device/gamepad/xbox_controller_mac.mm
struct XboxOneButtonData
{
uint8_t type;
uint8_t const_0;
uint16_t id;
bool sync : 1;
bool dummy1 : 1; // Always 0.
bool start : 1;
bool back : 1;
bool a : 1;
bool b : 1;
bool x : 1;
bool y : 1;
bool dpad_up : 1;
bool dpad_down : 1;
bool dpad_left : 1;
bool dpad_right : 1;
bool bumper_left : 1;
bool bumper_right : 1;
bool stick_left_click : 1;
bool stick_right_click : 1;
uint16_t trigger_left;
uint16_t trigger_right;
int16_t stick_left_x;
int16_t stick_left_y;
int16_t stick_right_x;
int16_t stick_right_y;
};
struct XboxOneGuideData
{
uint8_t down;
uint8_t dummy1;
};
struct XboxOneRumbleData
{
uint8_t command;
uint8_t dummy1;
uint8_t counter;
uint8_t size;
uint8_t mode;
uint8_t rumble_mask;
uint8_t trigger_left;
uint8_t trigger_right;
uint8_t strong_magnitude;
uint8_t weak_magnitude;
uint8_t duration;
uint8_t period;
uint8_t extra;
};
enum XboxOneInputPacketType
{
XBONEINPUT_BUTTON = 0x20,
XBONEINPUT_HEARTBEAT = 0x03,
XBONEINPUT_GUIDEBUTTON = 0x07,
XBONEINPUT_WAITCONNECT = 0x02,
};
class XboxOneController : public IController
{
private:
IUSBEndpoint *m_inPipe = nullptr;
IUSBEndpoint *m_outPipe = nullptr;
XboxOneButtonData m_buttonData;
int16_t kLeftThumbDeadzone = 0; //7849;
int16_t kRightThumbDeadzone = 0; //8689;
uint16_t kTriggerMax = 0; //1023;
uint16_t kTriggerDeadzone = 0; //120;
uint8_t m_rumbleDataCounter = 0;
public:
XboxOneController(std::unique_ptr<IUSBDevice> &&interface);
virtual ~XboxOneController();
virtual Status Initialize();
virtual void Exit();
Status OpenInterfaces();
void CloseInterfaces();
virtual Status GetInput();
virtual NormalizedButtonData GetNormalizedButtonData();
virtual ControllerType GetType() { return CONTROLLER_XBOXONE; }
inline const XboxOneButtonData &GetButtonData() { return m_buttonData; };
float NormalizeTrigger(uint16_t value);
void NormalizeAxis(int16_t x, int16_t y, int16_t deadzone, float *x_out, float *y_out);
Status SendInitBytes();
Status WriteAckGuideReport(uint8_t sequence);
Status SetRumble(uint8_t strong_magnitude,uint8_t weak_magnitude);
};

View File

@ -0,0 +1,72 @@
#pragma once
#include "IUSBDevice.h"
#include "ControllerTypes.h"
#include <memory>
#include "Status.h"
struct NormalizedButtonData
{
//ABXY; BAYX; square triangle cross circle; etc.
bool bottom_action;
bool right_action;
bool left_action;
bool top_action;
//dpad directions
bool dpad_up;
bool dpad_down;
bool dpad_left;
bool dpad_right;
// back start; select start; view and menu; share and options; etc.
bool back;
bool start;
//bumpers
bool left_bumper;
bool right_bumper;
//stick buttons
bool left_stick_click;
bool right_stick_click;
//reserved for switch's capture and home buttons
bool capture;
bool home;
//reserved for xbox's large led button or dualshock's PS button
bool guide;
//trigger values from 0.0f to 1.0f
float left_trigger;
float right_trigger;
//stick position values from -1.0f to 1.0f
float left_stick_x;
float left_stick_y;
float right_stick_x;
float right_stick_y;
};
class IController
{
protected:
std::unique_ptr<IUSBDevice> m_device;
public:
IController(std::unique_ptr<IUSBDevice> &&interface) : m_device(std::move(interface)) {}
virtual ~IController() = default;
virtual Status Initialize() = 0;
//Since Exit is used to clean up resources, no status report should be needed
virtual void Exit() = 0;
virtual Status GetInput() = 0;
virtual NormalizedButtonData GetNormalizedButtonData() = 0;
inline IUSBDevice *GetDevice() { return m_device.get(); }
virtual ControllerType GetType() = 0;
virtual Status SetRumble(uint8_t strong_magnitude,uint8_t weak_magnitude) = 0;
};

View File

@ -0,0 +1,25 @@
#pragma once
#include <cstdio>
#include "Status.h"
#include "IUSBInterface.h"
#include <memory>
#include <vector>
class IUSBDevice
{
protected:
std::vector<std::unique_ptr<IUSBInterface>> m_interfaces{};
public:
virtual ~IUSBDevice() = default;
//Open and close the device.
virtual Status Open() = 0;
virtual void Close() = 0;
//Reset the device.
virtual void Reset() = 0;
//Get the raw reference to interfaces vector.
virtual std::vector<std::unique_ptr<IUSBInterface>> &GetInterfaces() { return m_interfaces; }
};

View File

@ -0,0 +1,40 @@
#pragma once
#include "Status.h"
#include <cstddef>
class IUSBEndpoint
{
public:
enum Direction
{
USB_ENDPOINT_IN = 0x80,
USB_ENDPOINT_OUT = 0x00,
};
struct EndpointDescriptor
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
};
virtual ~IUSBEndpoint() = default;
//Open and close the endpoint.
virtual Status Open() = 0;
virtual void Close() = 0;
//This will read from the inBuffer pointer for the specified size and write it to the endpoint.
virtual Status Write(void *inBuffer, size_t bufferSize) = 0;
//This will read from the endpoint and put the data in the outBuffer pointer for the specified size.
virtual Status Read(void *outBuffer, size_t bufferSize) = 0;
//Get endpoint's direction. (IN or OUT)
virtual IUSBEndpoint::Direction GetDirection() = 0;
//Get the endpoint descriptor
virtual EndpointDescriptor *GetDescriptor() = 0;
};

View File

@ -0,0 +1,31 @@
#pragma once
#include "Status.h"
#include "IUSBEndpoint.h"
#include <memory>
class IUSBInterface
{
protected:
public:
struct InterfaceDescriptor
{
uint8_t bLength;
uint8_t bDescriptorType; ///< Must match USB_DT_INTERFACE.
uint8_t bInterfaceNumber; ///< See also USBDS_DEFAULT_InterfaceNumber.
uint8_t bAlternateSetting; ///< Must match 0.
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface; ///< Ignored.
};
virtual ~IUSBInterface() = default;
virtual Status Open() = 0;
virtual void Close() = 0;
virtual IUSBEndpoint *GetEndpoint(IUSBEndpoint::Direction direction, uint8_t index);
virtual InterfaceDescriptor *GetDescriptor();
virtual Status Reset() = 0;
};

View File

@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
//Function error code status type
typedef uint32_t Status;
//Checks whether a status code indicates success
#define S_SUCCEEDED(status) ((status) == 0)
//Checks whether a status code indicates failure
#define S_FAILED(status) ((status) != 0)

View File

@ -0,0 +1,240 @@
#include "Controllers/Dualshock3Controller.h"
#include <cmath>
Dualshock3Controller::Dualshock3Controller(std::unique_ptr<IUSBDevice> &&interface)
: IController(std::move(interface))
{
}
Dualshock3Controller::~Dualshock3Controller()
{
Exit();
}
Status Dualshock3Controller::Initialize()
{
Status rc;
rc = OpenInterfaces();
if (S_FAILED(rc))
return rc;
rc = SendInitBytes();
if (S_FAILED(rc))
return rc;
return rc;
}
void Dualshock3Controller::Exit()
{
CloseInterfaces();
}
Status Dualshock3Controller::OpenInterfaces()
{
Status rc;
rc = m_device->Open();
if (S_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 (S_FAILED(rc))
return rc;
//TODO: check for numEndpoints before trying to open them!
if (interface->GetDescriptor()->bNumEndpoints >= 2)
{
IUSBEndpoint *inEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_IN, 1);
if (inEndpoint->GetDescriptor()->bLength != 0)
{
rc = inEndpoint->Open();
if (S_FAILED(rc))
return 5555;
m_inPipe = inEndpoint;
}
IUSBEndpoint *outEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_OUT, 1);
if (outEndpoint->GetDescriptor()->bLength != 0)
{
rc = outEndpoint->Open();
if (S_FAILED(rc))
return 6666;
m_outPipe = outEndpoint;
}
}
}
if (!m_inPipe || !m_outPipe)
return 69;
return rc;
}
void Dualshock3Controller::CloseInterfaces()
{
m_device->Reset();
m_device->Close();
}
Status Dualshock3Controller::GetInput()
{
return 9;
/*
uint8_t input_bytes[64];
Status rc = m_inPipe->Read(input_bytes, sizeof(input_bytes));
if (S_FAILED(rc))
return rc;
uint8_t type = input_bytes[0];
if (type == XBONEINPUT_BUTTON) //Button data
{
m_buttonData = *reinterpret_cast<Dualshock3ButtonData *>(input_bytes);
}
else if (type == XBONEINPUT_GUIDEBUTTON) //Guide button status
{
m_buttonData.sync = 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 (S_FAILED(rc))
return rc;
}
//TODO: add ack check and send ack report!
}
return rc;
*/
}
Status Dualshock3Controller::SendInitBytes()
{
/*
UCHAR hidCommandEnable[DS3_HID_COMMAND_ENABLE_SIZE] =
{
0x42, 0x0C, 0x00, 0x00
};
Context,
BmRequestHostToDevice,
BmRequestClass,
SetReport,
Ds3FeatureStartDevice,
0,
hidCommandEnable,
DS3_HID_COMMAND_ENABLE_SIZE
*/
uint8_t init_bytes[]{
0x05,
0x20, 0x00, 0x01, 0x00};
Status rc = m_outPipe->Write(init_bytes, sizeof(init_bytes));
return rc;
}
float Dualshock3Controller::NormalizeTrigger(uint16_t value)
{
//If the given value is below the trigger zone, save the calc and return 0, otherwise adjust the value to the deadzone
return value < kTriggerDeadzone
? 0
: static_cast<float>(value - kTriggerDeadzone) /
(kTriggerMax - kTriggerDeadzone);
}
void Dualshock3Controller::NormalizeAxis(int16_t x,
int16_t y,
int16_t deadzone,
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);
// Check if the controller is outside a circular dead zone.
if (real_magnitude > 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 -= 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 - deadzone)) / real_magnitude;
// Y is negated because xbox controllers have an opposite sign from
// the 'standard controller' recommendations.
*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 Dualshock3Controller::GetNormalizedButtonData()
{
NormalizedButtonData normalData;
normalData.bottom_action = m_buttonData.a;
normalData.right_action = m_buttonData.b;
normalData.left_action = m_buttonData.x;
normalData.top_action = m_buttonData.y;
normalData.dpad_up = m_buttonData.dpad_up;
normalData.dpad_down = m_buttonData.dpad_down;
normalData.dpad_left = m_buttonData.dpad_left;
normalData.dpad_right = m_buttonData.dpad_right;
normalData.back = m_buttonData.back;
normalData.start = m_buttonData.start;
normalData.left_bumper = m_buttonData.bumper_left;
normalData.right_bumper = m_buttonData.bumper_right;
normalData.left_stick_click = m_buttonData.stick_left_click;
normalData.right_stick_click = m_buttonData.stick_right_click;
normalData.capture = false;
normalData.home = false;
//normalData.guide = m_buttonData.sync;
normalData.left_trigger = NormalizeTrigger(m_buttonData.trigger_left);
normalData.right_trigger = NormalizeTrigger(m_buttonData.trigger_right);
NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, kLeftThumbDeadzone,
&normalData.left_stick_x, &normalData.left_stick_y);
NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, kRightThumbDeadzone,
&normalData.right_stick_x, &normalData.right_stick_y);
return normalData;
}
Status Dualshock3Controller::SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude)
{
return 9;
/*
uint8_t rumble_data[]{
0x09, 0x00,
m_rumbleDataCounter++,
0x09, 0x00, 0x0f, 0x00, 0x00,
strong_magnitude,
weak_magnitude,
0xff, 0x00, 0x00};
return m_outPipe->Write(rumble_data, sizeof(rumble_data));
*/
}

View File

@ -0,0 +1,198 @@
#include "Controllers/Xbox360Controller.h"
#include <cmath>
Xbox360Controller::Xbox360Controller(std::unique_ptr<IUSBDevice> &&interface)
: IController(std::move(interface))
{
}
Xbox360Controller::~Xbox360Controller()
{
Exit();
}
Status Xbox360Controller::Initialize()
{
Status rc;
rc = OpenInterfaces();
if (S_FAILED(rc))
return rc;
return rc;
}
void Xbox360Controller::Exit()
{
CloseInterfaces();
}
Status Xbox360Controller::OpenInterfaces()
{
Status rc;
rc = m_device->Open();
if (S_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 (S_FAILED(rc))
return rc;
if (interface->GetDescriptor()->bInterfaceProtocol != 1)
continue;
if (!m_inPipe)
{
IUSBEndpoint *inEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_IN, 0);
if (inEndpoint->GetDescriptor()->bLength != 0)
{
rc = inEndpoint->Open();
if (S_FAILED(rc))
return 55555;
m_inPipe = inEndpoint;
}
}
if (!m_outPipe)
{
IUSBEndpoint *outEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_OUT, 0);
if (outEndpoint->GetDescriptor()->bLength != 0)
{
rc = outEndpoint->Open();
if (S_FAILED(rc))
return 66666;
m_outPipe = outEndpoint;
}
}
}
if (!m_inPipe || !m_outPipe)
return 369;
return rc;
}
void Xbox360Controller::CloseInterfaces()
{
m_device->Reset();
m_device->Close();
}
Status Xbox360Controller::GetInput()
{
uint8_t input_bytes[64];
Status rc = m_inPipe->Read(input_bytes, sizeof(input_bytes));
uint8_t type = input_bytes[0];
if (type == XBOX360INPUT_BUTTON) //Button data
{
m_buttonData = *reinterpret_cast<Xbox360ButtonData *>(input_bytes);
}
return rc;
}
Status Xbox360Controller::SendInitBytes()
{
uint8_t init_bytes[]{
0x05,
0x20, 0x00, 0x01, 0x00};
Status rc = m_outPipe->Write(init_bytes, sizeof(init_bytes));
return rc;
}
float Xbox360Controller::NormalizeTrigger(uint16_t value)
{
//If the given value is below the trigger zone, save the calc and return 0, otherwise adjust the value to the deadzone
return value < kTriggerDeadzone
? 0
: static_cast<float>(value - kTriggerDeadzone) /
(kTriggerMax - kTriggerDeadzone);
}
void Xbox360Controller::NormalizeAxis(int16_t x,
int16_t y,
int16_t deadzone,
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);
// Check if the controller is outside a circular dead zone.
if (real_magnitude > 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 -= 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 - deadzone)) / real_magnitude;
// Y is negated because xbox controllers have an opposite sign from
// the 'standard controller' recommendations.
*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 Xbox360Controller::GetNormalizedButtonData()
{
NormalizedButtonData normalData;
normalData.bottom_action = m_buttonData.a;
normalData.right_action = m_buttonData.b;
normalData.left_action = m_buttonData.x;
normalData.top_action = m_buttonData.y;
normalData.dpad_up = m_buttonData.dpad_up;
normalData.dpad_down = m_buttonData.dpad_down;
normalData.dpad_left = m_buttonData.dpad_left;
normalData.dpad_right = m_buttonData.dpad_right;
normalData.back = m_buttonData.back;
normalData.start = m_buttonData.start;
normalData.left_bumper = m_buttonData.bumper_left;
normalData.right_bumper = m_buttonData.bumper_right;
normalData.left_stick_click = m_buttonData.stick_left_click;
normalData.right_stick_click = m_buttonData.stick_right_click;
normalData.capture = false;
normalData.home = false;
normalData.guide = m_buttonData.guide;
normalData.left_trigger = NormalizeTrigger(m_buttonData.trigger_left);
normalData.right_trigger = NormalizeTrigger(m_buttonData.trigger_right);
NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, kLeftThumbDeadzone,
&normalData.left_stick_x, &normalData.left_stick_y);
NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, kRightThumbDeadzone,
&normalData.right_stick_x, &normalData.right_stick_y);
return normalData;
}
Status Xbox360Controller::SetRumble(uint8_t strong_magnitude,uint8_t weak_magnitude)
{
uint8_t rumbleData[]{0x00,sizeof(Xbox360RumbleData), 0x00, strong_magnitude, weak_magnitude, 0x00, 0x00, 0x00};
return m_outPipe->Write(rumbleData, sizeof(rumbleData));
}

View File

@ -0,0 +1,230 @@
#include "Controllers/XboxOneController.h"
#include <cmath>
XboxOneController::XboxOneController(std::unique_ptr<IUSBDevice> &&interface)
: IController(std::move(interface))
{
}
XboxOneController::~XboxOneController()
{
Exit();
}
Status XboxOneController::Initialize()
{
Status rc;
rc = OpenInterfaces();
if (S_FAILED(rc))
return rc;
rc = SendInitBytes();
if (S_FAILED(rc))
return rc;
return rc;
}
void XboxOneController::Exit()
{
CloseInterfaces();
}
Status XboxOneController::OpenInterfaces()
{
Status rc;
rc = m_device->Open();
if (S_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 (S_FAILED(rc))
return rc;
//TODO: check for numEndpoints before trying to open them!
if (interface->GetDescriptor()->bNumEndpoints >= 2)
{
IUSBEndpoint *inEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_IN, 1);
if (inEndpoint->GetDescriptor()->bLength != 0)
{
rc = inEndpoint->Open();
if (S_FAILED(rc))
return 5555;
m_inPipe = inEndpoint;
}
IUSBEndpoint *outEndpoint = interface->GetEndpoint(IUSBEndpoint::USB_ENDPOINT_OUT, 1);
if (outEndpoint->GetDescriptor()->bLength != 0)
{
rc = outEndpoint->Open();
if (S_FAILED(rc))
return 6666;
m_outPipe = outEndpoint;
}
}
}
if (!m_inPipe || !m_outPipe)
return 69;
return rc;
}
void XboxOneController::CloseInterfaces()
{
m_device->Reset();
m_device->Close();
}
Status XboxOneController::GetInput()
{
uint8_t input_bytes[64];
Status rc = m_inPipe->Read(input_bytes, sizeof(input_bytes));
if (S_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 status
{
m_buttonData.sync = 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 (S_FAILED(rc))
return rc;
}
//TODO: add ack check and send ack report!
}
return rc;
}
Status XboxOneController::SendInitBytes()
{
uint8_t init_bytes[]{
0x05,
0x20, 0x00, 0x01, 0x00};
Status rc = m_outPipe->Write(init_bytes, sizeof(init_bytes));
return rc;
}
float XboxOneController::NormalizeTrigger(uint16_t value)
{
//If the given value is below the trigger zone, save the calc and return 0, otherwise adjust the value to the deadzone
return value < kTriggerDeadzone
? 0
: static_cast<float>(value - kTriggerDeadzone) /
(kTriggerMax - kTriggerDeadzone);
}
void XboxOneController::NormalizeAxis(int16_t x,
int16_t y,
int16_t deadzone,
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);
// Check if the controller is outside a circular dead zone.
if (real_magnitude > 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 -= 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 - deadzone)) / real_magnitude;
// Y is negated because xbox controllers have an opposite sign from
// the 'standard controller' recommendations.
*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.bottom_action = m_buttonData.a;
normalData.right_action = m_buttonData.b;
normalData.left_action = m_buttonData.x;
normalData.top_action = m_buttonData.y;
normalData.dpad_up = m_buttonData.dpad_up;
normalData.dpad_down = m_buttonData.dpad_down;
normalData.dpad_left = m_buttonData.dpad_left;
normalData.dpad_right = m_buttonData.dpad_right;
normalData.back = m_buttonData.back;
normalData.start = m_buttonData.start;
normalData.left_bumper = m_buttonData.bumper_left;
normalData.right_bumper = m_buttonData.bumper_right;
normalData.left_stick_click = m_buttonData.stick_left_click;
normalData.right_stick_click = m_buttonData.stick_right_click;
normalData.capture = false;
normalData.home = false;
normalData.guide = m_buttonData.sync;
normalData.left_trigger = NormalizeTrigger(m_buttonData.trigger_left);
normalData.right_trigger = NormalizeTrigger(m_buttonData.trigger_right);
NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, kLeftThumbDeadzone,
&normalData.left_stick_x, &normalData.left_stick_y);
NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, kRightThumbDeadzone,
&normalData.right_stick_x, &normalData.right_stick_y);
return normalData;
}
Status XboxOneController::WriteAckGuideReport(uint8_t sequence)
{
Status 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;
}
Status XboxOneController::SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude)
{
uint8_t rumble_data[]{
0x09, 0x00,
m_rumbleDataCounter++,
0x09, 0x00, 0x0f, 0x00, 0x00,
strong_magnitude,
weak_magnitude,
0xff, 0x00, 0x00};
return m_outPipe->Write(rumble_data, sizeof(rumble_data));
}

209
MakefileApplet Normal file
View File

@ -0,0 +1,209 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.json
# - config.json
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
TARGET := sys-con
BUILD := buildApplet
SOURCES := source SwitchUSB/source ControllerUSB/source ControllerUSB/source/Controllers
DATA := data
INCLUDES := include SwitchUSB/include ControllerUSB/include
#ROMFS := romfs
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__ -D__APPLET__
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNX)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/MakefileApplet
#---------------------------------------------------------------------------------
clean:
@echo clean ...
ifeq ($(strip $(APP_JSON)),)
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
else
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
endif
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
else
all : $(OUTPUT).nsp
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

222
MakefileSysmodule Normal file
View File

@ -0,0 +1,222 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.json
# - config.json
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
TARGET := sys-con
BUILD := buildSysmodule
SOURCES := source SwitchUSB/source ControllerUSB/source ControllerUSB/source/Controllers
DATA := data
INCLUDES := include SwitchUSB/include ControllerUSB/include
#ROMFS := romfs
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNX)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/MakefileSysmodule
#---------------------------------------------------------------------------------
clean:
@echo clean ...
ifeq ($(strip $(APP_JSON)),)
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
else
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
endif
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
else
all : $(OUTPUT).nsp
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# sys-con
A sysmodule to provide USB support for third-party controllers

View File

@ -0,0 +1,37 @@
#pragma once
#include "switch.h"
#include "IController.h"
#include "SwitchControllerHandler.h"
#include "SwitchVirtualGamepadHandler.h"
#include <thread>
//Wrapper for AbstractedPad for switch versions [5.0.0 - 8.1.0]
//DOESN'T WORK PROPERLY. See inside SwitchAbstractedPadHandler::FillAbstractedState()
class SwitchAbstractedPadHandler : public SwitchVirtualGamepadHandler
{
private:
s8 m_abstractedPadID;
HiddbgAbstractedPadState m_state;
public:
//Initialize the class with specified controller
SwitchAbstractedPadHandler(std::unique_ptr<IController> &&controller);
~SwitchAbstractedPadHandler();
//Initialize controller handler, AbstractedPadState
Result Initialize() override;
void Exit() override;
//This will be called periodically by the input threads
void UpdateInput() override;
//This will be called periodically by the output threads
void UpdateOutput() override;
//Separately init and close the HDL state
Result InitAbstractedPadState();
Result ExitAbstractedPadState();
void FillAbstractedState(const NormalizedButtonData &data);
Result UpdateAbstractedState();
};

View File

@ -0,0 +1,28 @@
#pragma once
#include "switch.h"
#include "IController.h"
#include <thread>
class SwitchControllerHandler
{
private:
std::unique_ptr<IController> m_controller;
public:
//Initialize the class with specified controller
SwitchControllerHandler(std::unique_ptr<IController> &&controller);
~SwitchControllerHandler();
//Initialize controller and open a separate thread for input
Result Initialize();
//Clean up everything associated with the controller, reset device, close threads, etc
void Exit();
void ConvertAxisToSwitchAxis(float x, float y, float deadzone, s32 *x_out, s32 *y_out);
Result SetControllerVibration(float strong_mag, float weak_mag);
//Get the raw controller pointer
inline IController *GetController() { return m_controller.get(); }
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "switch.h"
#include "IController.h"
#include "SwitchControllerHandler.h"
#include "SwitchVirtualGamepadHandler.h"
#include <thread>
//Wrapper for HDL functions for switch versions [7.0.0+]
class SwitchHDLHandler : public SwitchVirtualGamepadHandler
{
private:
u32 m_vibrationDeviceHandle;
u64 m_hdlHandle;
HiddbgHdlsDeviceInfo m_deviceInfo;
HiddbgHdlsState m_hdlState;
public:
//Initialize the class with specified controller
SwitchHDLHandler(std::unique_ptr<IController> &&controller);
~SwitchHDLHandler();
//Initialize controller handler, HDL state
Result Initialize() override;
void Exit() override;
//This will be called periodically by the input threads
void UpdateInput() override;
//This will be called periodically by the output threads
void UpdateOutput() override;
//Separately init and close the HDL state
Result InitHdlState();
Result ExitHdlState();
//Fills out the HDL state with the specified button data
void FillHdlState(const NormalizedButtonData &data);
//Passes the HDL state to HID so that it could register the inputs
Result UpdateHdlState();
inline u32 *GetVibrationHandle() { return &m_vibrationDeviceHandle; }
};

View File

@ -0,0 +1,26 @@
#pragma once
#include "IUSBDevice.h"
#include "switch.h"
#include "SwitchUSBInterface.h"
#include <vector>
class SwitchUSBDevice : public IUSBDevice
{
public:
SwitchUSBDevice();
~SwitchUSBDevice();
//Initialize the class with the SetInterfaces call.
SwitchUSBDevice(UsbHsInterface *interfaces, int length);
//There are no devices to open on the switch, so instead this returns success if there are any interfaces
virtual Result Open();
//Closes all the interfaces associated with the class
virtual void Close();
// Resets the device
virtual void Reset();
//Create interfaces from the given array of specified element length
void SetInterfaces(UsbHsInterface *interfaces, int length);
};

View File

@ -0,0 +1,36 @@
#pragma once
#include "IUSBEndpoint.h"
#include "switch.h"
#include <memory>
class SwitchUSBEndpoint : public IUSBEndpoint
{
private:
UsbHsClientEpSession m_epSession{};
UsbHsClientIfSession m_ifSession;
usb_endpoint_descriptor m_descriptor;
public:
//Pass the necessary information to be able to open the endpoint
SwitchUSBEndpoint(UsbHsClientIfSession &if_session, usb_endpoint_descriptor &desc);
~SwitchUSBEndpoint();
//Open and close the endpoint
virtual Result Open();
virtual void Close();
//buffer should point to the data array, and only the specified size will be read.
virtual Result Write(void *inBuffer, size_t bufferSize);
//The data received will be put in the outBuffer array for the length of the specified size.
virtual Result Read(void *outBuffer, size_t bufferSize);
//Gets the direction of this endpoint (IN or OUT)
virtual IUSBEndpoint::Direction GetDirection();
//get the endpoint descriptor
virtual IUSBEndpoint::EndpointDescriptor *GetDescriptor();
// Get the current EpSession (after it was opened)
inline UsbHsClientEpSession &GetSession() { return m_epSession; }
};

View File

@ -0,0 +1,40 @@
#pragma once
#include "IUSBInterface.h"
#include "SwitchUSBEndpoint.h"
#include "switch.h"
#include <memory>
#include <vector>
class SwitchUSBInterface : public IUSBInterface
{
private:
UsbHsClientIfSession m_session{};
UsbHsInterface m_interface{};
std::vector<std::unique_ptr<IUSBEndpoint>> m_inEndpoints;
std::vector<std::unique_ptr<IUSBEndpoint>> m_outEndpoints;
public:
//Pass the specified interface to allow for opening the session
SwitchUSBInterface(UsbHsInterface &interface);
~SwitchUSBInterface();
// Open and close the interface
virtual Result Open();
virtual void Close();
// There are a total of 15 endpoints on a switch interface for each direction, get them by passing the desired parameters
virtual IUSBEndpoint *GetEndpoint(IUSBEndpoint::Direction direction, uint8_t index);
// Reset the device
virtual Result Reset();
//Get the unique session ID for this interface
inline s32 GetID() { return m_session.ID; }
//Get the raw interface
inline UsbHsInterface &GetInterface() { return m_interface; }
//Get the raw session
inline UsbHsClientIfSession &GetSession() { return m_session; }
virtual InterfaceDescriptor *GetDescriptor() { return reinterpret_cast<InterfaceDescriptor *>(&m_interface.inf.interface_desc); }
};

View File

@ -0,0 +1,52 @@
#pragma once
#include "switch.h"
#include "IController.h"
#include "SwitchControllerHandler.h"
#include <thread>
//This class is a base class for SwitchHDLHandler and SwitchAbstractedPaadHandler.
class SwitchVirtualGamepadHandler
{
protected:
SwitchControllerHandler m_controllerHandler;
bool m_keepInputThreadRunning;
std::thread m_inputThread;
bool m_keepOutputThreadRunning;
std::thread m_outputThread;
public:
SwitchVirtualGamepadHandler(std::unique_ptr<IController> &&controller);
virtual ~SwitchVirtualGamepadHandler();
//Don't allow to move this class in any way because it holds a thread member that reads from the instance's address
SwitchVirtualGamepadHandler(SwitchVirtualGamepadHandler &&other) = delete;
SwitchVirtualGamepadHandler(SwitchVirtualGamepadHandler &other) = delete;
SwitchVirtualGamepadHandler &operator=(SwitchVirtualGamepadHandler &&other) = delete;
SwitchVirtualGamepadHandler &operator=(SwitchVirtualGamepadHandler &other) = delete;
//Override this if you want a custom init procedure
virtual Result Initialize();
//Override this if you want a custom exit procedure
virtual void Exit();
//Separately init the input-reading thread
void InitInputThread();
//Separately close the input-reading thread
void ExitInputThread();
//Separately init the rumble sending thread
void InitOutputThread();
//Separately close the rumble sending thread
void ExitOutputThread();
//The function to call indefinitely by the input thread
virtual void UpdateInput() = 0;
//The function to call indefinitely by the output thread
virtual void UpdateOutput() = 0;
//Get the raw controller handler pointer
inline SwitchControllerHandler *GetControllerHandler() { return &m_controllerHandler; }
inline IController *GetController() { return m_controllerHandler.GetController(); }
};

View File

@ -0,0 +1,7 @@
#pragma once
#include "switch.h"
//This file exists for the sole purpose of fixing existing libnx bugs while the new release isn't out yet
//regular usbHsEpClose incorrectly accesses more memory than it should, causing a crash
void usbHsEpCloseFixed(UsbHsClientEpSession *s);

View File

@ -0,0 +1,141 @@
#include "SwitchAbstractedPadHandler.h"
#include <cmath>
SwitchAbstractedPadHandler::SwitchAbstractedPadHandler(std::unique_ptr<IController> &&controller)
: SwitchVirtualGamepadHandler(std::move(controller))
{
}
SwitchAbstractedPadHandler::~SwitchAbstractedPadHandler()
{
Exit();
}
Result SwitchAbstractedPadHandler::Initialize()
{
Result rc = m_controllerHandler.Initialize();
if (R_FAILED(rc))
return rc;
rc = InitAbstractedPadState();
if (R_FAILED(rc))
return rc;
InitInputThread();
return rc;
}
void SwitchAbstractedPadHandler::Exit()
{
ExitAbstractedPadState();
m_controllerHandler.Exit();
ExitInputThread();
//ExitRumbleThread();
}
Result SwitchAbstractedPadHandler::InitAbstractedPadState()
{
Result rc;
/*
u64 pads[8] = {0};
HiddbgAbstractedPadState states[8] = {0};
s32 tmpout = 0;
rc = hiddbgGetAbstractedPadsState(pads, states, sizeof(pads) / sizeof(u64), &tmpout);
*/
m_state = {0};
m_abstractedPadID = 0;
m_state.type = BIT(0);
m_state.npadInterfaceType = NpadInterfaceType_USB;
m_state.flags = 0xff;
m_state.state.batteryCharge = 4;
m_state.singleColorBody = RGBA8_MAXALPHA(107, 107, 107);
m_state.singleColorButtons = RGBA8_MAXALPHA(0, 0, 0);
rc = hiddbgSetAutoPilotVirtualPadState(m_abstractedPadID, &m_state);
if (R_FAILED(rc))
return rc;
return rc;
/*
m_hdlHandle = 0;
m_deviceInfo = {0};
m_hdlState = {0};
// Set the controller type to Pro-Controller, and set the npadInterfaceType.
m_deviceInfo.deviceType = HidDeviceType_FullKey3;
m_deviceInfo.npadInterfaceType = NpadInterfaceType_USB;
// Set the controller colors. The grip colors are for Pro-Controller on [9.0.0+].
m_deviceInfo.singleColorBody = RGBA8_MAXALPHA(107, 107, 107);
m_deviceInfo.singleColorButtons = RGBA8_MAXALPHA(0, 0, 0);
m_deviceInfo.colorLeftGrip = RGBA8_MAXALPHA(23, 125, 62);
m_deviceInfo.colorRightGrip = RGBA8_MAXALPHA(23, 125, 62);
m_hdlState.batteryCharge = 4; // Set battery charge to full.
m_hdlState.joysticks[JOYSTICK_LEFT].dx = 0x1234;
m_hdlState.joysticks[JOYSTICK_LEFT].dy = -0x1234;
m_hdlState.joysticks[JOYSTICK_RIGHT].dx = 0x5678;
m_hdlState.joysticks[JOYSTICK_RIGHT].dy = -0x5678;
return hiddbgAttachHdlsVirtualDevice(&m_hdlHandle, &m_deviceInfo);
*/
}
Result SwitchAbstractedPadHandler::ExitAbstractedPadState()
{
return hiddbgUnsetAutoPilotVirtualPadState(m_abstractedPadID);
}
void SwitchAbstractedPadHandler::FillAbstractedState(const NormalizedButtonData &data)
{
m_state.state.buttons = 0;
m_state.state.buttons |= (data.right_action ? KEY_A : 0);
m_state.state.buttons |= (data.bottom_action ? KEY_B : 0);
m_state.state.buttons |= (data.top_action ? KEY_X : 0);
m_state.state.buttons |= (data.left_action ? KEY_Y : 0);
//Breaks when buttons has a value of more than 25 or more
//if buttons is 0x2000 or more, it also calls a interface change event, breaking any possibility of disconnecting a controller properly
//None of this happens on the main thread, this is a problem only when running from a separate thread
/*
m_state.state.buttons |= (data.left_stick_click ? KEY_LSTICK : 0);
m_state.state.buttons |= (data.right_stick_click ? KEY_RSTICK : 0);
m_state.state.buttons |= (data.left_bumper ? KEY_L : 0);
m_state.state.buttons |= (data.right_bumper ? KEY_R : 0);
m_state.state.buttons |= ((data.left_trigger > 0.0f) ? KEY_ZL : 0);
m_state.state.buttons |= ((data.right_trigger > 0.0f) ? KEY_ZR : 0);
m_state.state.buttons |= (data.start ? KEY_PLUS : 0);
m_state.state.buttons |= (data.back ? KEY_MINUS : 0);
m_state.state.buttons |= (data.dpad_left ? KEY_DLEFT : 0);
m_state.state.buttons |= (data.dpad_up ? KEY_DUP : 0);
m_state.state.buttons |= (data.dpad_right ? KEY_DRIGHT : 0);
m_state.state.buttons |= (data.dpad_down ? KEY_DDOWN : 0);
*/
m_controllerHandler.ConvertAxisToSwitchAxis(data.left_stick_x, data.left_stick_y, 0, &m_state.state.joysticks[JOYSTICK_LEFT].dx, &m_state.state.joysticks[JOYSTICK_LEFT].dy);
m_controllerHandler.ConvertAxisToSwitchAxis(data.right_stick_x, data.right_stick_y, 0, &m_state.state.joysticks[JOYSTICK_RIGHT].dx, &m_state.state.joysticks[JOYSTICK_RIGHT].dy);
}
Result SwitchAbstractedPadHandler::UpdateAbstractedState()
{
return hiddbgSetAutoPilotVirtualPadState(m_abstractedPadID, &m_state);
}
void SwitchAbstractedPadHandler::UpdateInput()
{
Result rc;
rc = GetController()->GetInput();
if (R_FAILED(rc))
return;
FillAbstractedState(GetController()->GetNormalizedButtonData());
rc = UpdateAbstractedState();
if (R_FAILED(rc))
return;
}
void SwitchAbstractedPadHandler::UpdateOutput()
{
svcSleepThread(1e+7L);
}

View File

@ -0,0 +1,47 @@
#include "SwitchControllerHandler.h"
#include <cmath>
SwitchControllerHandler::SwitchControllerHandler(std::unique_ptr<IController> &&controller)
: m_controller(std::move(controller))
{
}
SwitchControllerHandler::~SwitchControllerHandler()
{
Exit();
}
Result SwitchControllerHandler::Initialize()
{
Result rc = m_controller->Initialize();
if (R_FAILED(rc))
return rc;
return rc;
}
void SwitchControllerHandler::Exit()
{
m_controller->Exit();
}
void SwitchControllerHandler::ConvertAxisToSwitchAxis(float x, float y, float deadzone, s32 *x_out, s32 *y_out)
{
float floatRange = 2.0f;
float newRange = (JOYSTICK_MAX - JOYSTICK_MIN);
*x_out = (((x + 1.0f) * newRange) / floatRange) + JOYSTICK_MIN;
*y_out = (((y + 1.0f) * newRange) / floatRange) + JOYSTICK_MIN;
/*
OldRange = (OldMax - OldMin)
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
*/
}
Result SwitchControllerHandler::SetControllerVibration(float strong_mag, float weak_mag)
{
strong_mag = std::max<float>(0.0f, std::min<float>(strong_mag, 1.0f));
weak_mag = std::max<float>(0.0f, std::min<float>(weak_mag, 1.0f));
return m_controller->SetRumble(static_cast<uint8_t>(strong_mag * 255.0f), static_cast<uint8_t>(weak_mag * 255.0f));
}

View File

@ -0,0 +1,149 @@
#include "SwitchHDLHandler.h"
#include <cmath>
SwitchHDLHandler::SwitchHDLHandler(std::unique_ptr<IController> &&controller)
: SwitchVirtualGamepadHandler(std::move(controller))
{
}
SwitchHDLHandler::~SwitchHDLHandler()
{
Exit();
}
Result SwitchHDLHandler::Initialize()
{
Result rc = m_controllerHandler.Initialize();
if (R_FAILED(rc))
return rc;
rc = InitHdlState();
if (R_FAILED(rc))
return rc;
if (R_SUCCEEDED(hidInitializeVibrationDevices(&m_vibrationDeviceHandle, 1, CONTROLLER_PLAYER_2, static_cast<HidControllerType>(TYPE_PROCONTROLLER | TYPE_SYSTEM_EXT))))
{
InitOutputThread();
}
InitInputThread();
return rc;
}
void SwitchHDLHandler::Exit()
{
m_controllerHandler.Exit();
ExitInputThread();
ExitOutputThread();
ExitHdlState();
}
Result SwitchHDLHandler::InitHdlState()
{
m_hdlHandle = 0;
m_deviceInfo = {0};
m_hdlState = {0};
// Set the controller type to Pro-Controller, and set the npadInterfaceType.
m_deviceInfo.deviceType = HidDeviceType_FullKey3;
m_deviceInfo.npadInterfaceType = NpadInterfaceType_USB;
// Set the controller colors. The grip colors are for Pro-Controller on [9.0.0+].
m_deviceInfo.singleColorBody = RGBA8_MAXALPHA(107, 107, 107);
m_deviceInfo.singleColorButtons = RGBA8_MAXALPHA(0, 0, 0);
m_deviceInfo.colorLeftGrip = RGBA8_MAXALPHA(23, 125, 62);
m_deviceInfo.colorRightGrip = RGBA8_MAXALPHA(23, 125, 62);
m_hdlState.batteryCharge = 4; // Set battery charge to full.
m_hdlState.joysticks[JOYSTICK_LEFT].dx = 0x1234;
m_hdlState.joysticks[JOYSTICK_LEFT].dy = -0x1234;
m_hdlState.joysticks[JOYSTICK_RIGHT].dx = 0x5678;
m_hdlState.joysticks[JOYSTICK_RIGHT].dy = -0x5678;
return hiddbgAttachHdlsVirtualDevice(&m_hdlHandle, &m_deviceInfo);
}
Result SwitchHDLHandler::ExitHdlState()
{
return hiddbgDetachHdlsVirtualDevice(m_hdlHandle);
}
//Sets the state of the class's HDL controller to the state stored in class's hdl.state
Result SwitchHDLHandler::UpdateHdlState()
{
//Checks if the virtual device was erased, in which case re-attach the device
bool found_flag = false;
HiddbgHdlsNpadAssignment list;
hiddbgDumpHdlsNpadAssignmentState(&list);
for (int i = 0; i != list.total_entries; ++i)
{
if (list.entries[i].HdlsHandle == m_hdlHandle)
{
found_flag = true;
break;
}
}
if (!found_flag)
hiddbgAttachHdlsVirtualDevice(&m_hdlHandle, &m_deviceInfo);
return hiddbgSetHdlsState(m_hdlHandle, &m_hdlState);
}
void SwitchHDLHandler::FillHdlState(const NormalizedButtonData &data)
{
m_hdlState.buttons = 0;
m_hdlState.buttons |= (data.right_action ? KEY_A : 0);
m_hdlState.buttons |= (data.bottom_action ? KEY_B : 0);
m_hdlState.buttons |= (data.top_action ? KEY_X : 0);
m_hdlState.buttons |= (data.left_action ? KEY_Y : 0);
m_hdlState.buttons |= (data.left_stick_click ? KEY_LSTICK : 0);
m_hdlState.buttons |= (data.right_stick_click ? KEY_RSTICK : 0);
m_hdlState.buttons |= (data.left_bumper ? KEY_L : 0);
m_hdlState.buttons |= (data.right_bumper ? KEY_R : 0);
m_hdlState.buttons |= ((data.left_trigger > 0.0f) ? KEY_ZL : 0);
m_hdlState.buttons |= ((data.right_trigger > 0.0f) ? KEY_ZR : 0);
m_hdlState.buttons |= (data.start ? KEY_PLUS : 0);
m_hdlState.buttons |= (data.back ? KEY_MINUS : 0);
m_hdlState.buttons |= (data.dpad_left ? KEY_DLEFT : 0);
m_hdlState.buttons |= (data.dpad_up ? KEY_DUP : 0);
m_hdlState.buttons |= (data.dpad_right ? KEY_DRIGHT : 0);
m_hdlState.buttons |= (data.dpad_down ? KEY_DDOWN : 0);
m_hdlState.buttons |= (data.capture ? KEY_CAPTURE : 0);
m_hdlState.buttons |= (data.home ? KEY_HOME : 0);
m_hdlState.buttons |= (data.guide ? KEY_HOME : 0);
m_controllerHandler.ConvertAxisToSwitchAxis(data.left_stick_x, data.left_stick_y, 0, &m_hdlState.joysticks[JOYSTICK_LEFT].dx, &m_hdlState.joysticks[JOYSTICK_LEFT].dy);
m_controllerHandler.ConvertAxisToSwitchAxis(data.right_stick_x, data.right_stick_y, 0, &m_hdlState.joysticks[JOYSTICK_RIGHT].dx, &m_hdlState.joysticks[JOYSTICK_RIGHT].dy);
}
void SwitchHDLHandler::UpdateInput()
{
Result rc;
rc = GetController()->GetInput();
if (R_FAILED(rc))
return;
FillHdlState(GetController()->GetNormalizedButtonData());
rc = UpdateHdlState();
if (R_FAILED(rc))
return;
}
void SwitchHDLHandler::UpdateOutput()
{
//Implement rumble here
Result rc;
HidVibrationValue value;
rc = hidGetActualVibrationValue(&m_vibrationDeviceHandle, &value);
if (R_FAILED(rc))
return;
rc = GetController()->SetRumble(static_cast<uint8_t>(value.amp_high * 255.0f), static_cast<uint8_t>(value.amp_low * 255.0f));
svcSleepThread(1e+7L);
}

View File

@ -0,0 +1,59 @@
#include "SwitchUSBDevice.h"
#include "libnxFix.h"
#include <cstring> //for memset
#include "malloc.h" //for memalign
SwitchUSBDevice::SwitchUSBDevice(UsbHsInterface *interfaces, int length)
//: m_interfaces(std::vector<std::unique_ptr<IUSBInterface>>())
{
SetInterfaces(interfaces, length);
}
SwitchUSBDevice::~SwitchUSBDevice()
{
Close();
}
SwitchUSBDevice::SwitchUSBDevice()
{
}
Result SwitchUSBDevice::Open()
{
if (m_interfaces.size() != 0)
return 0;
else
return 51;
}
void SwitchUSBDevice::Close()
{
for (auto &&interface : m_interfaces)
{
interface->Close();
}
}
void SwitchUSBDevice::Reset()
{
//I'm expecting all interfaces to point to one device decsriptor
// as such resetting on any of them should do the trick
//TODO: needs testing
for (auto &&interface : m_interfaces)
{
interface->Reset();
}
//if (m_interfaces.size() != 0)
//m_interfaces[0]->Reset();
}
void SwitchUSBDevice::SetInterfaces(UsbHsInterface *interfaces, int length)
{
m_interfaces.clear();
m_interfaces.reserve(length);
for (int i = 0; i != length; ++i)
{
m_interfaces.push_back(std::make_unique<SwitchUSBInterface>(interfaces[i]));
}
}

View File

@ -0,0 +1,85 @@
#include "SwitchUSBEndpoint.h"
#include <cstring>
#include <cstdio>
#include <malloc.h>
SwitchUSBEndpoint::SwitchUSBEndpoint(UsbHsClientIfSession &if_session, usb_endpoint_descriptor &desc)
: m_ifSession(if_session),
m_descriptor(desc)
{
}
SwitchUSBEndpoint::~SwitchUSBEndpoint()
{
Close();
}
Result SwitchUSBEndpoint::Open()
{
Result rc = usbHsIfOpenUsbEp(&m_ifSession, &m_epSession, 1, m_descriptor.wMaxPacketSize, &m_descriptor);
if (R_FAILED(rc))
return 73011;
return rc;
}
void SwitchUSBEndpoint::Close()
{
usbHsEpClose(&m_epSession);
}
Result SwitchUSBEndpoint::Write(void *inBuffer, size_t bufferSize)
{
Result rc = -1;
void *tmpbuf = memalign(0x1000, bufferSize);
if (tmpbuf != nullptr)
{
u32 transferredSize = 0;
memset(tmpbuf, 0, bufferSize);
for (size_t byte = 0; byte != bufferSize; ++byte)
{
static_cast<uint8_t *>(tmpbuf)[byte] = static_cast<uint8_t *>(inBuffer)[byte];
}
rc = usbHsEpPostBuffer(&m_epSession, tmpbuf, bufferSize, &transferredSize);
if (rc == 0xcc8c)
rc = 0;
free(tmpbuf);
}
return rc;
}
Result SwitchUSBEndpoint::Read(void *outBuffer, size_t bufferSize)
{
void *tmpbuf = memalign(0x1000, bufferSize);
if (tmpbuf == nullptr)
return -1;
u32 transferredSize;
Result rc = usbHsEpPostBuffer(&m_epSession, tmpbuf, bufferSize, &transferredSize);
if (rc == 0xcc8c)
rc = 0;
if (R_SUCCEEDED(rc))
{
for (size_t byte = 0; byte != bufferSize; ++byte)
{
static_cast<uint8_t *>(outBuffer)[byte] = static_cast<uint8_t *>(tmpbuf)[byte];
}
}
free(tmpbuf);
return rc;
}
IUSBEndpoint::Direction SwitchUSBEndpoint::GetDirection()
{
return ((m_descriptor.bEndpointAddress & USB_ENDPOINT_IN) ? USB_ENDPOINT_IN : USB_ENDPOINT_OUT);
}
IUSBEndpoint::EndpointDescriptor *SwitchUSBEndpoint::GetDescriptor()
{
return reinterpret_cast<IUSBEndpoint::EndpointDescriptor *>(&m_descriptor);
}

View File

@ -0,0 +1,72 @@
#include "SwitchUSBInterface.h"
#include "SwitchUSBEndpoint.h"
SwitchUSBInterface::SwitchUSBInterface(UsbHsInterface &interface)
: m_interface(interface)
{
}
SwitchUSBInterface::~SwitchUSBInterface()
{
Close();
}
Result SwitchUSBInterface::Open()
{
UsbHsClientIfSession temp;
Result rc = usbHsAcquireUsbIf(&temp, &m_interface);
if (R_FAILED(rc))
return 11037;
m_session = temp;
if (R_SUCCEEDED(rc))
{
m_inEndpoints.clear();
m_outEndpoints.clear();
m_inEndpoints.reserve(15);
m_outEndpoints.reserve(15);
//For some reason output_endpoint_descs contains IN endpoints and input_endpoint_descs contains OUT endpoints
//we're adjusting appropriately
for (int i = 0; i != 15; ++i)
{
m_inEndpoints.push_back(std::make_unique<SwitchUSBEndpoint>(m_session, m_session.inf.inf.output_endpoint_descs[i]));
}
for (int i = 0; i != 15; ++i)
{
m_outEndpoints.push_back(std::make_unique<SwitchUSBEndpoint>(m_session, m_session.inf.inf.input_endpoint_descs[i]));
}
}
return rc;
}
void SwitchUSBInterface::Close()
{
for (auto &&endpoint : m_inEndpoints)
{
endpoint->Close();
}
for (auto &&endpoint : m_outEndpoints)
{
endpoint->Close();
}
usbHsIfClose(&m_session);
}
IUSBEndpoint *SwitchUSBInterface::GetEndpoint(IUSBEndpoint::Direction direction, uint8_t index)
{
//TODO: replace this with a control transfer to get endpoint descriptor
if (direction == IUSBEndpoint::USB_ENDPOINT_IN)
return m_inEndpoints[index].get();
else
return m_outEndpoints[index].get();
}
Result SwitchUSBInterface::Reset()
{
usbHsIfResetDevice(&m_session);
return 0;
}

View File

@ -0,0 +1,63 @@
#include "SwitchVirtualGamepadHandler.h"
SwitchVirtualGamepadHandler::SwitchVirtualGamepadHandler(std::unique_ptr<IController> &&controller)
: m_controllerHandler(std::move(controller))
{
}
SwitchVirtualGamepadHandler::~SwitchVirtualGamepadHandler()
{
}
Result SwitchVirtualGamepadHandler::Initialize()
{
return 0;
}
void SwitchVirtualGamepadHandler::Exit()
{
}
void inputThreadLoop(bool *keepThreadRunning, SwitchVirtualGamepadHandler *handler)
{
svcSleepThread(1e+7L);
while (*keepThreadRunning)
{
handler->UpdateInput();
}
}
void outputThreadLoop(bool *keepThreadRunning, SwitchVirtualGamepadHandler *handler)
{
svcSleepThread(1e+7L);
while (*keepThreadRunning)
{
handler->UpdateOutput();
}
}
void SwitchVirtualGamepadHandler::InitInputThread()
{
m_keepInputThreadRunning = true;
m_inputThread = std::thread(inputThreadLoop, &m_keepInputThreadRunning, this);
}
void SwitchVirtualGamepadHandler::ExitInputThread()
{
m_keepInputThreadRunning = false;
if (m_inputThread.joinable())
m_inputThread.join();
}
void SwitchVirtualGamepadHandler::InitOutputThread()
{
m_keepOutputThreadRunning = true;
m_outputThread = std::thread(outputThreadLoop, &m_keepOutputThreadRunning, this);
}
void SwitchVirtualGamepadHandler::ExitOutputThread()
{
m_keepOutputThreadRunning = false;
if (m_outputThread.joinable())
m_outputThread.join();
}

View File

@ -0,0 +1,51 @@
#include "libnxFix.h"
#include <cstring>
#include <stdio.h>
#include "malloc.h"
static Result _usbHsCmdNoIO(Service *s, u64 cmd_id)
{
IpcCommand c;
ipcInitialize(&c);
struct Packet
{
u64 magic;
u64 cmd_id;
} * raw;
raw = static_cast<Packet *>(serviceIpcPrepareHeader(s, &c, sizeof(*raw)));
raw->magic = SFCI_MAGIC;
raw->cmd_id = cmd_id;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc))
{
IpcParsedCommand r;
struct Reponse
{
u64 magic;
u64 result;
} * resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = static_cast<Reponse *>(r.Raw);
rc = resp->result;
}
return rc;
}
void usbHsEpCloseFixed(UsbHsClientEpSession *s)
{
if (!serviceIsActive(&s->s))
return;
_usbHsCmdNoIO(&s->s, hosversionAtLeast(2, 0, 0) ? 1 : 3); //Close
serviceClose(&s->s);
eventClose(&s->eventXfer);
memset(s, 0, sizeof(UsbHsClientEpSession));
}

172
config.json Normal file
View File

@ -0,0 +1,172 @@
{
"name": "sys-con",
"title_id": "0x690000000000000D",
"title_id_range_min": "0x690000000000000D",
"title_id_range_max": "0x690000000000000D",
"main_thread_stack_size": "0x00004000",
"main_thread_priority": 44,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 1,
"filesystem_access": {
"permissions": "0xffffffffffffffff"
},
"service_access": ["*"],
"service_host": ["*"],
"kernel_capabilities": [{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 24,
"lowest_cpu_id": 3,
"highest_cpu_id": 3
}
}, {
"type": "syscalls",
"value": {
"svcUnknown": "0x00",
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcFlushEntireDataCache": "0x2a",
"svcFlushDataCache": "0x2b",
"svcMapPhysicalMemory": "0x2c",
"svcUnmapPhysicalMemory": "0x2d",
"svcGetFutureThreadInfo": "0x2e",
"svcGetLastThreadInfo": "0x2f",
"svcGetResourceLimitLimitValue": "0x30",
"svcGetResourceLimitCurrentValue": "0x31",
"svcSetThreadActivity": "0x32",
"svcGetThreadContext3": "0x33",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcUnknown": "0x36",
"svcUnknown": "0x37",
"svcUnknown": "0x38",
"svcUnknown": "0x39",
"svcUnknown": "0x3a",
"svcUnknown": "0x3b",
"svcDumpInfo": "0x3c",
"svcDumpInfoNew": "0x3d",
"svcUnknown": "0x3e",
"svcUnknown": "0x3f",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcUnknown": "0x46",
"svcUnknown": "0x47",
"svcMapPhysicalMemoryUnsafe": "0x48",
"svcUnmapPhysicalMemoryUnsafe": "0x49",
"svcSetUnsafeLimit": "0x4a",
"svcCreateCodeMemory": "0x4b",
"svcControlCodeMemory": "0x4c",
"svcSleepSystem": "0x4d",
"svcReadWriteRegister": "0x4e",
"svcSetProcessActivity": "0x4f",
"svcCreateSharedMemory": "0x50",
"svcMapTransferMemory": "0x51",
"svcUnmapTransferMemory": "0x52",
"svcCreateInterruptEvent": "0x53",
"svcQueryPhysicalAddress": "0x54",
"svcQueryIoMapping": "0x55",
"svcCreateDeviceAddressSpace": "0x56",
"svcAttachDeviceAddressSpace": "0x57",
"svcDetachDeviceAddressSpace": "0x58",
"svcMapDeviceAddressSpaceByForce": "0x59",
"svcMapDeviceAddressSpaceAligned": "0x5a",
"svcMapDeviceAddressSpace": "0x5b",
"svcUnmapDeviceAddressSpace": "0x5c",
"svcInvalidateProcessDataCache": "0x5d",
"svcStoreProcessDataCache": "0x5e",
"svcFlushProcessDataCache": "0x5f",
"svcDebugActiveProcess": "0x60",
"svcBreakDebugProcess": "0x61",
"svcTerminateDebugProcess": "0x62",
"svcGetDebugEvent": "0x63",
"svcContinueDebugEvent": "0x64",
"svcGetProcessList": "0x65",
"svcGetThreadList": "0x66",
"svcGetDebugThreadContext": "0x67",
"svcSetDebugThreadContext": "0x68",
"svcQueryDebugProcessMemory": "0x69",
"svcReadDebugProcessMemory": "0x6a",
"svcWriteDebugProcessMemory": "0x6b",
"svcSetHardwareBreakPoint": "0x6c",
"svcGetDebugThreadParam": "0x6d",
"svcUnknown": "0x6e",
"svcGetSystemInfo": "0x6f",
"svcCreatePort": "0x70",
"svcManageNamedPort": "0x71",
"svcConnectToPort": "0x72",
"svcSetProcessMemoryPermission": "0x73",
"svcMapProcessMemory": "0x74",
"svcUnmapProcessMemory": "0x75",
"svcQueryProcessMemory": "0x76",
"svcMapProcessCodeMemory": "0x77",
"svcUnmapProcessCodeMemory": "0x78",
"svcCreateProcess": "0x79",
"svcStartProcess": "0x7a",
"svcTerminateProcess": "0x7b",
"svcGetProcessInfo": "0x7c",
"svcCreateResourceLimit": "0x7d",
"svcSetResourceLimitLimitValue": "0x7e",
"svcCallSecureMonitor": "0x7f"
}
}, {
"type": "min_kernel_version",
"value": "0x0030"
}, {
"type": "handle_table_size",
"value": 1023
}, {
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}]
}

40
source/log.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <iomanip>
#include <fstream>
#include <iostream>
template <typename... T>
void WriteToLog(T &&... text)
{
#ifdef __APPLET__
std::stringstream ss;
((ss << text), ...);
printf(ss.str().c_str());
printf("\n");
#else
using namespace std;
time_t unixTime = time(NULL);
struct tm *time = localtime((const time_t *)&unixTime);
fstream fs;
fs.open("/atmosphere/titles/690000000000000D/log.txt", fstream::app);
//Print time
fs << setfill('0');
fs << setw(4) << (time->tm_year + 1900)
<< "-" << setw(2) << time->tm_mon
<< "-" << setw(2) << time->tm_mday
<< " " << setw(2) << time->tm_hour
<< ":" << setw(2) << time->tm_min
<< ":" << setw(2) << time->tm_sec << ": ";
//Print the actual text
((fs << text), ...);
fs << "\n";
fs.close();
#endif
}

105
source/main.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "switch.h"
#include "log.h"
#include "mainLoop.h"
//ISSUES:
// when exiting the applet, only one of the controllers is reset
// in sysmodule, when you go to change the controllers, all the hdl handlers are erased but the code doesn't detect it
// Rumble is currently missing on all controllers
// Kosmos Toolbox doesn't allow this sysmodule to be turned on after turning it off, probably due to heap memory not being freed up
//TODO:
// Figure out and remove unnecessary services initialized on sysmodule init
// Figure out and shirnk unneessary heap memory/stack size used for the sysmodule
// Figure out if you can connect controllers paired through a bluetooth adapter
// Figure out if you can connect controllers through usbDs
// Comment the functions before public release
// Make a config application companion to test controller input and edit various preferences (buttons, deadzones)
extern "C"
{
// Adjust size as needed.
#define INNER_HEAP_SIZE 0x100000
#ifndef __APPLET__
u32 __nx_applet_type = AppletType_None;
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void)
{
void *addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
// Newlib
extern char *fake_heap_start;
extern char *fake_heap_end;
fake_heap_start = (char *)addr;
fake_heap_end = (char *)addr + size;
}
#endif
void __attribute__((weak)) userAppInit(void)
{
//Seems like every thread on the switch needs to sleep for a little
// or it will block the entire console
//Specifically in Kosmos Toolbox's case, you need to wait about 0.2 sec
// or it won't let you turn it on/off the sysmodule after a few tries
svcSleepThread(2e+8L);
Result rc = 0;
rc = hiddbgInitialize();
if (R_FAILED(rc))
fatalSimple(rc);
rc = hiddbgAttachHdlsWorkBuffer();
if (R_FAILED(rc))
fatalSimple(rc);
rc = usbHsInitialize();
if (R_FAILED(rc))
fatalSimple(rc);
rc = usbCommsInitialize();
if (R_FAILED(rc))
fatalSimple(rc);
}
void __attribute__((weak)) userAppExit(void)
{
usbCommsExit();
usbHsExit();
hiddbgReleaseHdlsWorkBuffer();
hiddbgExit();
}
alignas(16) u8 __nx_exception_stack[0x1000];
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
__attribute__((weak)) u32 __nx_exception_ignoredebug = 1;
void __libnx_exception_handler(ThreadExceptionDump *ctx)
{
WriteToLog("Sysmodule crashed with error ", ctx->error_desc);
}
}
int main(int argc, char *argv[])
{
Result rc;
#ifdef __APPLET__
consoleInit(nullptr);
#endif
rc = mainLoop();
#ifdef __APPLET__
consoleExit(nullptr);
#endif
return rc;
}

158
source/mainLoop.cpp Normal file
View File

@ -0,0 +1,158 @@
#include <switch.h>
#include <variant>
#include "log.h"
#include "SwitchUSBDevice.h"
#include "Controllers.h"
#include "SwitchHDLHandler.h"
#include "SwitchAbstractedPadHandler.h"
struct VendorEvent
{
uint16_t vendor;
Event event;
};
Result mainLoop()
{
Result rc;
UsbHsInterface interfaces[16];
s32 total_entries;
std::vector<uint16_t> vendors = GetVendors();
bool useAbstractedPad = hosversionBetween(5, 7);
VendorEvent events[vendors.size()];
std::vector<std::unique_ptr<SwitchVirtualGamepadHandler>> controllerInterfaces;
WriteToLog("\n\nNew sysmodule session started");
UsbHsInterfaceFilter filter;
filter.Flags = UsbHsInterfaceFilterFlags_idVendor;
{
int i = 0;
for (auto &&vendor : vendors)
{
filter.idVendor = vendor;
auto &&event = events[i++] = {vendor, Event()};
rc = usbHsCreateInterfaceAvailableEvent(&event.event, true, 0, &filter);
if (R_FAILED(rc))
WriteToLog("Failed to open event ", event.vendor);
else
WriteToLog("Successfully created event ", event.vendor);
}
}
controllerInterfaces.reserve(8);
while (appletMainLoop())
{
#ifdef __APPLET__
hidScanInput();
u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (kDown & KEY_B)
break;
#endif
//Iterate over each event and check if it went off, then iterate over each vendor product to see which one fits
for (auto &&event : events)
{
rc = eventWait(&event.event, 0);
if (R_SUCCEEDED(rc))
{
WriteToLog("Succeeded event ", event.vendor);
WriteToLog("Interfaces size: ", controllerInterfaces.size(), "; capacity: ", controllerInterfaces.capacity());
auto &&products = GetVendorProducts(event.vendor);
for (auto &&product : products)
{
if (controllerInterfaces.size() == 8)
{
WriteToLog("Reached controller limit! skipping initialization");
break;
}
UsbHsInterfaceFilter tempFilter;
tempFilter.Flags = UsbHsInterfaceFilterFlags_idProduct;
tempFilter.idProduct = product;
rc = usbHsQueryAvailableInterfaces(&tempFilter, interfaces, sizeof(interfaces), &total_entries);
if (R_FAILED(rc))
continue;
if (total_entries == 0)
continue;
std::unique_ptr<SwitchVirtualGamepadHandler> switchHandler;
if (useAbstractedPad)
switchHandler = std::make_unique<SwitchAbstractedPadHandler>(ConstructControllerFromType(GetControllerTypeFromIds(event.vendor, product), std::make_unique<SwitchUSBDevice>(interfaces, total_entries)));
else
switchHandler = std::make_unique<SwitchHDLHandler>(ConstructControllerFromType(GetControllerTypeFromIds(event.vendor, product), std::make_unique<SwitchUSBDevice>(interfaces, total_entries)));
rc = switchHandler->Initialize();
if (R_SUCCEEDED(rc))
{
controllerInterfaces.push_back(std::move(switchHandler));
WriteToLog("Interface created successfully on product ", product);
}
else
{
WriteToLog("Error creating interface for product ", product, " with error ", rc);
}
}
}
}
//On interface change event, check if any devices were removed, and erase them from memory appropriately
rc = eventWait(usbHsGetInterfaceStateChangeEvent(), 0);
if (R_SUCCEEDED(rc))
{
WriteToLog("Interface state was changed");
eventClear(usbHsGetInterfaceStateChangeEvent());
rc = usbHsQueryAcquiredInterfaces(interfaces, sizeof(interfaces), &total_entries);
if (R_SUCCEEDED(rc))
{
for (auto it = controllerInterfaces.begin(); it != controllerInterfaces.end(); ++it)
{
bool found_flag = false;
for (auto &&ptr : (*it)->GetController()->GetDevice()->GetInterfaces())
{
//We check if a device was removed by comparing the controller's interfaces and the currently acquired interfaces
//If we didn't find a single matching interface ID, we consider a controller removed
for (int i = 0; i != total_entries; ++i)
{
if (interfaces[i].inf.ID == static_cast<SwitchUSBInterface *>(ptr.get())->GetID())
{
found_flag = true;
break;
}
}
}
if (!found_flag)
{
WriteToLog("Erasing controller! ", (*it)->GetController()->GetType());
controllerInterfaces.erase(it--);
WriteToLog("Controller erased!");
}
}
}
}
#ifdef __APPLET__
consoleUpdate(nullptr);
#else
svcSleepThread(1e+7L);
#endif
}
//After we break out of the loop, close all events and exit
for (auto &&event : events)
{
WriteToLog("Destroying event " + event.vendor);
usbHsDestroyInterfaceAvailableEvent(&event.event, 0);
}
//controllerInterfaces.clear();
return rc;
}

4
source/mainLoop.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "switch/result.h"
Result mainLoop();