mirror of
https://github.com/cathery/sys-con.git
synced 2025-01-27 12:35:44 +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…
x
Reference in New Issue
Block a user