commit 7796e2e88df0c786cfe6f92a90c70b59c59a5bde Author: cathery Date: Thu Oct 31 21:00:42 2019 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d5f570 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +buildApplet/* +buildSysmodule/* +*.nro +*.nacp +*.elf +*.nsp +*.npdm +*.nso +.vscode +*.code-workspace +*.flag \ No newline at end of file diff --git a/ControllerUSB/include/ControllerTypes.h b/ControllerUSB/include/ControllerTypes.h new file mode 100644 index 0000000..212e85f --- /dev/null +++ b/ControllerUSB/include/ControllerTypes.h @@ -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, +}; \ No newline at end of file diff --git a/ControllerUSB/include/Controllers.h b/ControllerUSB/include/Controllers.h new file mode 100644 index 0000000..46121b7 --- /dev/null +++ b/ControllerUSB/include/Controllers.h @@ -0,0 +1,64 @@ +#pragma once +#include +//Catch-all header to include all the controllers +#include "Controllers/Xbox360Controller.h" +#include "Controllers/XboxOneController.h" + +std::vector GetVendors() +{ + return {VENDOR_MICROSOFT}; +} + +std::vector 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 ConstructControllerFromType(ControllerType type, std::unique_ptr &&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(std::move(device)); + case CONTROLLER_XBOXONE: + return std::make_unique(std::move(device)); + default: + break; + } + return std::unique_ptr{}; +} + +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; +} \ No newline at end of file diff --git a/ControllerUSB/include/Controllers/Dualshock3Controller.h b/ControllerUSB/include/Controllers/Dualshock3Controller.h new file mode 100644 index 0000000..c657538 --- /dev/null +++ b/ControllerUSB/include/Controllers/Dualshock3Controller.h @@ -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 &&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); +}; \ No newline at end of file diff --git a/ControllerUSB/include/Controllers/Xbox360Controller.h b/ControllerUSB/include/Controllers/Xbox360Controller.h new file mode 100644 index 0000000..497dcad --- /dev/null +++ b/ControllerUSB/include/Controllers/Xbox360Controller.h @@ -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 &&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); +}; \ No newline at end of file diff --git a/ControllerUSB/include/Controllers/XboxOneController.h b/ControllerUSB/include/Controllers/XboxOneController.h new file mode 100644 index 0000000..1037a08 --- /dev/null +++ b/ControllerUSB/include/Controllers/XboxOneController.h @@ -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 &&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); +}; \ No newline at end of file diff --git a/ControllerUSB/include/IController.h b/ControllerUSB/include/IController.h new file mode 100644 index 0000000..708e505 --- /dev/null +++ b/ControllerUSB/include/IController.h @@ -0,0 +1,72 @@ +#pragma once +#include "IUSBDevice.h" +#include "ControllerTypes.h" +#include +#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 m_device; + +public: + IController(std::unique_ptr &&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; +}; \ No newline at end of file diff --git a/ControllerUSB/include/IUSBDevice.h b/ControllerUSB/include/IUSBDevice.h new file mode 100644 index 0000000..5c0cd80 --- /dev/null +++ b/ControllerUSB/include/IUSBDevice.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include "Status.h" +#include "IUSBInterface.h" +#include +#include + +class IUSBDevice +{ +protected: + std::vector> 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> &GetInterfaces() { return m_interfaces; } +}; \ No newline at end of file diff --git a/ControllerUSB/include/IUSBEndpoint.h b/ControllerUSB/include/IUSBEndpoint.h new file mode 100644 index 0000000..a5a38f0 --- /dev/null +++ b/ControllerUSB/include/IUSBEndpoint.h @@ -0,0 +1,40 @@ +#pragma once +#include "Status.h" +#include + +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; +}; \ No newline at end of file diff --git a/ControllerUSB/include/IUSBInterface.h b/ControllerUSB/include/IUSBInterface.h new file mode 100644 index 0000000..119391d --- /dev/null +++ b/ControllerUSB/include/IUSBInterface.h @@ -0,0 +1,31 @@ +#pragma once +#include "Status.h" +#include "IUSBEndpoint.h" +#include + +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; +}; \ No newline at end of file diff --git a/ControllerUSB/include/Status.h b/ControllerUSB/include/Status.h new file mode 100644 index 0000000..e3fbe0a --- /dev/null +++ b/ControllerUSB/include/Status.h @@ -0,0 +1,10 @@ +#pragma once +#include + +//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) \ No newline at end of file diff --git a/ControllerUSB/source/Controllers/Dualshock3Controller.cpp b/ControllerUSB/source/Controllers/Dualshock3Controller.cpp new file mode 100644 index 0000000..e5d8ccd --- /dev/null +++ b/ControllerUSB/source/Controllers/Dualshock3Controller.cpp @@ -0,0 +1,240 @@ +#include "Controllers/Dualshock3Controller.h" +#include + +Dualshock3Controller::Dualshock3Controller(std::unique_ptr &&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> &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(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(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)); + */ +} \ No newline at end of file diff --git a/ControllerUSB/source/Controllers/Xbox360Controller.cpp b/ControllerUSB/source/Controllers/Xbox360Controller.cpp new file mode 100644 index 0000000..28ca5b1 --- /dev/null +++ b/ControllerUSB/source/Controllers/Xbox360Controller.cpp @@ -0,0 +1,198 @@ +#include "Controllers/Xbox360Controller.h" +#include + +Xbox360Controller::Xbox360Controller(std::unique_ptr &&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> &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(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(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)); +} \ No newline at end of file diff --git a/ControllerUSB/source/Controllers/XboxOneController.cpp b/ControllerUSB/source/Controllers/XboxOneController.cpp new file mode 100644 index 0000000..fd4ad03 --- /dev/null +++ b/ControllerUSB/source/Controllers/XboxOneController.cpp @@ -0,0 +1,230 @@ +#include "Controllers/XboxOneController.h" +#include + +XboxOneController::XboxOneController(std::unique_ptr &&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> &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(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(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)); +} \ No newline at end of file diff --git a/MakefileApplet b/MakefileApplet new file mode 100644 index 0000000..7e74c20 --- /dev/null +++ b/MakefileApplet @@ -0,0 +1,209 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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): +# - .jpg +# - icon.jpg +# - /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): +# - .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 +#--------------------------------------------------------------------------------------- diff --git a/MakefileSysmodule b/MakefileSysmodule new file mode 100644 index 0000000..f9b2dc8 --- /dev/null +++ b/MakefileSysmodule @@ -0,0 +1,222 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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): +# - .jpg +# - icon.jpg +# - /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): +# - .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 +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..2baeb6e --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# sys-con + +A sysmodule to provide USB support for third-party controllers + + diff --git a/SwitchUSB/include/SwitchAbstractedPadHandler.h b/SwitchUSB/include/SwitchAbstractedPadHandler.h new file mode 100644 index 0000000..97f1fcd --- /dev/null +++ b/SwitchUSB/include/SwitchAbstractedPadHandler.h @@ -0,0 +1,37 @@ +#pragma once + +#include "switch.h" +#include "IController.h" +#include "SwitchControllerHandler.h" +#include "SwitchVirtualGamepadHandler.h" +#include + +//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 &&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(); +}; diff --git a/SwitchUSB/include/SwitchControllerHandler.h b/SwitchUSB/include/SwitchControllerHandler.h new file mode 100644 index 0000000..4bbe724 --- /dev/null +++ b/SwitchUSB/include/SwitchControllerHandler.h @@ -0,0 +1,28 @@ +#pragma once + +#include "switch.h" +#include "IController.h" +#include + +class SwitchControllerHandler +{ +private: + std::unique_ptr m_controller; + +public: + //Initialize the class with specified controller + SwitchControllerHandler(std::unique_ptr &&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(); } +}; \ No newline at end of file diff --git a/SwitchUSB/include/SwitchHDLHandler.h b/SwitchUSB/include/SwitchHDLHandler.h new file mode 100644 index 0000000..769344c --- /dev/null +++ b/SwitchUSB/include/SwitchHDLHandler.h @@ -0,0 +1,42 @@ +#pragma once + +#include "switch.h" +#include "IController.h" +#include "SwitchControllerHandler.h" +#include "SwitchVirtualGamepadHandler.h" +#include + +//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 &&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; } +}; \ No newline at end of file diff --git a/SwitchUSB/include/SwitchUSBDevice.h b/SwitchUSB/include/SwitchUSBDevice.h new file mode 100644 index 0000000..b11fe78 --- /dev/null +++ b/SwitchUSB/include/SwitchUSBDevice.h @@ -0,0 +1,26 @@ +#pragma once +#include "IUSBDevice.h" +#include "switch.h" +#include "SwitchUSBInterface.h" +#include + +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); +}; \ No newline at end of file diff --git a/SwitchUSB/include/SwitchUSBEndpoint.h b/SwitchUSB/include/SwitchUSBEndpoint.h new file mode 100644 index 0000000..1a6dfd3 --- /dev/null +++ b/SwitchUSB/include/SwitchUSBEndpoint.h @@ -0,0 +1,36 @@ +#pragma once +#include "IUSBEndpoint.h" +#include "switch.h" +#include + +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; } +}; \ No newline at end of file diff --git a/SwitchUSB/include/SwitchUSBInterface.h b/SwitchUSB/include/SwitchUSBInterface.h new file mode 100644 index 0000000..dd32e88 --- /dev/null +++ b/SwitchUSB/include/SwitchUSBInterface.h @@ -0,0 +1,40 @@ +#pragma once +#include "IUSBInterface.h" +#include "SwitchUSBEndpoint.h" +#include "switch.h" +#include +#include + +class SwitchUSBInterface : public IUSBInterface +{ +private: + UsbHsClientIfSession m_session{}; + UsbHsInterface m_interface{}; + + std::vector> m_inEndpoints; + std::vector> 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(&m_interface.inf.interface_desc); } +}; \ No newline at end of file diff --git a/SwitchUSB/include/SwitchVirtualGamepadHandler.h b/SwitchUSB/include/SwitchVirtualGamepadHandler.h new file mode 100644 index 0000000..620720f --- /dev/null +++ b/SwitchUSB/include/SwitchVirtualGamepadHandler.h @@ -0,0 +1,52 @@ +#pragma once +#include "switch.h" +#include "IController.h" +#include "SwitchControllerHandler.h" +#include + +//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 &&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(); } +}; \ No newline at end of file diff --git a/SwitchUSB/include/libnxFix.h b/SwitchUSB/include/libnxFix.h new file mode 100644 index 0000000..29c4bdf --- /dev/null +++ b/SwitchUSB/include/libnxFix.h @@ -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); diff --git a/SwitchUSB/source/SwitchAbstractedPadHandler.cpp b/SwitchUSB/source/SwitchAbstractedPadHandler.cpp new file mode 100644 index 0000000..1bc48ab --- /dev/null +++ b/SwitchUSB/source/SwitchAbstractedPadHandler.cpp @@ -0,0 +1,141 @@ +#include "SwitchAbstractedPadHandler.h" +#include + +SwitchAbstractedPadHandler::SwitchAbstractedPadHandler(std::unique_ptr &&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); +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchControllerHandler.cpp b/SwitchUSB/source/SwitchControllerHandler.cpp new file mode 100644 index 0000000..9247e3a --- /dev/null +++ b/SwitchUSB/source/SwitchControllerHandler.cpp @@ -0,0 +1,47 @@ +#include "SwitchControllerHandler.h" +#include + +SwitchControllerHandler::SwitchControllerHandler(std::unique_ptr &&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(0.0f, std::min(strong_mag, 1.0f)); + weak_mag = std::max(0.0f, std::min(weak_mag, 1.0f)); + + return m_controller->SetRumble(static_cast(strong_mag * 255.0f), static_cast(weak_mag * 255.0f)); +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchHDLHandler.cpp b/SwitchUSB/source/SwitchHDLHandler.cpp new file mode 100644 index 0000000..0f4600b --- /dev/null +++ b/SwitchUSB/source/SwitchHDLHandler.cpp @@ -0,0 +1,149 @@ +#include "SwitchHDLHandler.h" +#include + +SwitchHDLHandler::SwitchHDLHandler(std::unique_ptr &&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(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(value.amp_high * 255.0f), static_cast(value.amp_low * 255.0f)); + + svcSleepThread(1e+7L); +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchUSBDevice.cpp b/SwitchUSB/source/SwitchUSBDevice.cpp new file mode 100644 index 0000000..c36115e --- /dev/null +++ b/SwitchUSB/source/SwitchUSBDevice.cpp @@ -0,0 +1,59 @@ +#include "SwitchUSBDevice.h" +#include "libnxFix.h" +#include //for memset +#include "malloc.h" //for memalign + +SwitchUSBDevice::SwitchUSBDevice(UsbHsInterface *interfaces, int length) +//: m_interfaces(std::vector>()) +{ + 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(interfaces[i])); + } +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchUSBEndpoint.cpp b/SwitchUSB/source/SwitchUSBEndpoint.cpp new file mode 100644 index 0000000..b4de61a --- /dev/null +++ b/SwitchUSB/source/SwitchUSBEndpoint.cpp @@ -0,0 +1,85 @@ +#include "SwitchUSBEndpoint.h" +#include +#include +#include + +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(tmpbuf)[byte] = static_cast(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(outBuffer)[byte] = static_cast(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(&m_descriptor); +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchUSBInterface.cpp b/SwitchUSB/source/SwitchUSBInterface.cpp new file mode 100644 index 0000000..a003dd8 --- /dev/null +++ b/SwitchUSB/source/SwitchUSBInterface.cpp @@ -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(m_session, m_session.inf.inf.output_endpoint_descs[i])); + } + + for (int i = 0; i != 15; ++i) + { + m_outEndpoints.push_back(std::make_unique(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; +} \ No newline at end of file diff --git a/SwitchUSB/source/SwitchVirtualGamepadHandler.cpp b/SwitchUSB/source/SwitchVirtualGamepadHandler.cpp new file mode 100644 index 0000000..da86125 --- /dev/null +++ b/SwitchUSB/source/SwitchVirtualGamepadHandler.cpp @@ -0,0 +1,63 @@ +#include "SwitchVirtualGamepadHandler.h" + +SwitchVirtualGamepadHandler::SwitchVirtualGamepadHandler(std::unique_ptr &&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(); +} \ No newline at end of file diff --git a/SwitchUSB/source/libnxFix.cpp b/SwitchUSB/source/libnxFix.cpp new file mode 100644 index 0000000..a4c2ea0 --- /dev/null +++ b/SwitchUSB/source/libnxFix.cpp @@ -0,0 +1,51 @@ +#include "libnxFix.h" +#include +#include +#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(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(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)); +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..32b55fa --- /dev/null +++ b/config.json @@ -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 + } + }] +} diff --git a/source/log.h b/source/log.h new file mode 100644 index 0000000..77409b2 --- /dev/null +++ b/source/log.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include + +template +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 +} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..f919c5f --- /dev/null +++ b/source/main.cpp @@ -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; +} \ No newline at end of file diff --git a/source/mainLoop.cpp b/source/mainLoop.cpp new file mode 100644 index 0000000..b70bcbc --- /dev/null +++ b/source/mainLoop.cpp @@ -0,0 +1,158 @@ +#include +#include +#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 vendors = GetVendors(); + bool useAbstractedPad = hosversionBetween(5, 7); + VendorEvent events[vendors.size()]; + std::vector> 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 switchHandler; + if (useAbstractedPad) + switchHandler = std::make_unique(ConstructControllerFromType(GetControllerTypeFromIds(event.vendor, product), std::make_unique(interfaces, total_entries))); + else + switchHandler = std::make_unique(ConstructControllerFromType(GetControllerTypeFromIds(event.vendor, product), std::make_unique(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(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; +} \ No newline at end of file diff --git a/source/mainLoop.h b/source/mainLoop.h new file mode 100644 index 0000000..0f265a1 --- /dev/null +++ b/source/mainLoop.h @@ -0,0 +1,4 @@ +#pragma once +#include "switch/result.h" + +Result mainLoop(); \ No newline at end of file