mirror of
https://github.com/cathery/sys-con.git
synced 2024-09-28 22:40:47 +00:00
Initial commit
This commit is contained in:
commit
7796e2e88d
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
buildApplet/*
|
||||||
|
buildSysmodule/*
|
||||||
|
*.nro
|
||||||
|
*.nacp
|
||||||
|
*.elf
|
||||||
|
*.nsp
|
||||||
|
*.npdm
|
||||||
|
*.nso
|
||||||
|
.vscode
|
||||||
|
*.code-workspace
|
||||||
|
*.flag
|
23
ControllerUSB/include/ControllerTypes.h
Normal file
23
ControllerUSB/include/ControllerTypes.h
Normal 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,
|
||||||
|
};
|
64
ControllerUSB/include/Controllers.h
Normal file
64
ControllerUSB/include/Controllers.h
Normal 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;
|
||||||
|
}
|
146
ControllerUSB/include/Controllers/Dualshock3Controller.h
Normal file
146
ControllerUSB/include/Controllers/Dualshock3Controller.h
Normal 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);
|
||||||
|
};
|
99
ControllerUSB/include/Controllers/Xbox360Controller.h
Normal file
99
ControllerUSB/include/Controllers/Xbox360Controller.h
Normal 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);
|
||||||
|
};
|
113
ControllerUSB/include/Controllers/XboxOneController.h
Normal file
113
ControllerUSB/include/Controllers/XboxOneController.h
Normal 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);
|
||||||
|
};
|
72
ControllerUSB/include/IController.h
Normal file
72
ControllerUSB/include/IController.h
Normal 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;
|
||||||
|
};
|
25
ControllerUSB/include/IUSBDevice.h
Normal file
25
ControllerUSB/include/IUSBDevice.h
Normal 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; }
|
||||||
|
};
|
40
ControllerUSB/include/IUSBEndpoint.h
Normal file
40
ControllerUSB/include/IUSBEndpoint.h
Normal 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;
|
||||||
|
};
|
31
ControllerUSB/include/IUSBInterface.h
Normal file
31
ControllerUSB/include/IUSBInterface.h
Normal 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;
|
||||||
|
};
|
10
ControllerUSB/include/Status.h
Normal file
10
ControllerUSB/include/Status.h
Normal 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)
|
240
ControllerUSB/source/Controllers/Dualshock3Controller.cpp
Normal file
240
ControllerUSB/source/Controllers/Dualshock3Controller.cpp
Normal 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));
|
||||||
|
*/
|
||||||
|
}
|
198
ControllerUSB/source/Controllers/Xbox360Controller.cpp
Normal file
198
ControllerUSB/source/Controllers/Xbox360Controller.cpp
Normal 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));
|
||||||
|
}
|
230
ControllerUSB/source/Controllers/XboxOneController.cpp
Normal file
230
ControllerUSB/source/Controllers/XboxOneController.cpp
Normal 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
209
MakefileApplet
Normal 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
222
MakefileSysmodule
Normal 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
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# sys-con
|
||||||
|
|
||||||
|
A sysmodule to provide USB support for third-party controllers
|
||||||
|
|
||||||
|
|
37
SwitchUSB/include/SwitchAbstractedPadHandler.h
Normal file
37
SwitchUSB/include/SwitchAbstractedPadHandler.h
Normal 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();
|
||||||
|
};
|
28
SwitchUSB/include/SwitchControllerHandler.h
Normal file
28
SwitchUSB/include/SwitchControllerHandler.h
Normal 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(); }
|
||||||
|
};
|
42
SwitchUSB/include/SwitchHDLHandler.h
Normal file
42
SwitchUSB/include/SwitchHDLHandler.h
Normal 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; }
|
||||||
|
};
|
26
SwitchUSB/include/SwitchUSBDevice.h
Normal file
26
SwitchUSB/include/SwitchUSBDevice.h
Normal 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);
|
||||||
|
};
|
36
SwitchUSB/include/SwitchUSBEndpoint.h
Normal file
36
SwitchUSB/include/SwitchUSBEndpoint.h
Normal 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; }
|
||||||
|
};
|
40
SwitchUSB/include/SwitchUSBInterface.h
Normal file
40
SwitchUSB/include/SwitchUSBInterface.h
Normal 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); }
|
||||||
|
};
|
52
SwitchUSB/include/SwitchVirtualGamepadHandler.h
Normal file
52
SwitchUSB/include/SwitchVirtualGamepadHandler.h
Normal 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(); }
|
||||||
|
};
|
7
SwitchUSB/include/libnxFix.h
Normal file
7
SwitchUSB/include/libnxFix.h
Normal 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);
|
141
SwitchUSB/source/SwitchAbstractedPadHandler.cpp
Normal file
141
SwitchUSB/source/SwitchAbstractedPadHandler.cpp
Normal 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);
|
||||||
|
}
|
47
SwitchUSB/source/SwitchControllerHandler.cpp
Normal file
47
SwitchUSB/source/SwitchControllerHandler.cpp
Normal 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));
|
||||||
|
}
|
149
SwitchUSB/source/SwitchHDLHandler.cpp
Normal file
149
SwitchUSB/source/SwitchHDLHandler.cpp
Normal 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);
|
||||||
|
}
|
59
SwitchUSB/source/SwitchUSBDevice.cpp
Normal file
59
SwitchUSB/source/SwitchUSBDevice.cpp
Normal 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]));
|
||||||
|
}
|
||||||
|
}
|
85
SwitchUSB/source/SwitchUSBEndpoint.cpp
Normal file
85
SwitchUSB/source/SwitchUSBEndpoint.cpp
Normal 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);
|
||||||
|
}
|
72
SwitchUSB/source/SwitchUSBInterface.cpp
Normal file
72
SwitchUSB/source/SwitchUSBInterface.cpp
Normal 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;
|
||||||
|
}
|
63
SwitchUSB/source/SwitchVirtualGamepadHandler.cpp
Normal file
63
SwitchUSB/source/SwitchVirtualGamepadHandler.cpp
Normal 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();
|
||||||
|
}
|
51
SwitchUSB/source/libnxFix.cpp
Normal file
51
SwitchUSB/source/libnxFix.cpp
Normal 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
172
config.json
Normal 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
40
source/log.h
Normal 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
105
source/main.cpp
Normal 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
158
source/mainLoop.cpp
Normal 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
4
source/mainLoop.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "switch/result.h"
|
||||||
|
|
||||||
|
Result mainLoop();
|
Loading…
Reference in New Issue
Block a user