// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse // engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c // https://github.com/CTCaer/jc_toolkit // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering #pragma once #include #include #include #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" namespace InputCommon::Joycon { constexpr u32 MaxErrorCount = 50; constexpr u32 MaxBufferSize = 368; constexpr std::array DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; using MacAddress = std::array; using SerialNumber = std::array; using TagUUID = std::array; using MifareUUID = std::array; enum class ControllerType : u8 { None = 0x00, Left = 0x01, Right = 0x02, Pro = 0x03, Dual = 0x05, // TODO: Verify this id LarkHvc1 = 0x07, LarkHvc2 = 0x08, LarkNesLeft = 0x09, LarkNesRight = 0x0A, Lucia = 0x0B, Lagon = 0x0C, Lager = 0x0D, }; enum class PadAxes { LeftStickX, LeftStickY, RightStickX, RightStickY, Undefined, }; enum class PadMotion { LeftMotion, RightMotion, Undefined, }; enum class PadButton : u32 { Down = 0x000001, Up = 0x000002, Right = 0x000004, Left = 0x000008, LeftSR = 0x000010, LeftSL = 0x000020, L = 0x000040, ZL = 0x000080, Y = 0x000100, X = 0x000200, B = 0x000400, A = 0x000800, RightSR = 0x001000, RightSL = 0x002000, R = 0x004000, ZR = 0x008000, Minus = 0x010000, Plus = 0x020000, StickR = 0x040000, StickL = 0x080000, Home = 0x100000, Capture = 0x200000, }; enum class PassivePadButton : u32 { Down_A = 0x0001, Right_X = 0x0002, Left_B = 0x0004, Up_Y = 0x0008, SL = 0x0010, SR = 0x0020, Minus = 0x0100, Plus = 0x0200, StickL = 0x0400, StickR = 0x0800, Home = 0x1000, Capture = 0x2000, L_R = 0x4000, ZL_ZR = 0x8000, }; enum class PassivePadStick : u8 { Right = 0x00, RightDown = 0x01, Down = 0x02, DownLeft = 0x03, Left = 0x04, LeftUp = 0x05, Up = 0x06, UpRight = 0x07, Neutral = 0x08, }; enum class OutputReport : u8 { RUMBLE_AND_SUBCMD = 0x01, FW_UPDATE_PKT = 0x03, RUMBLE_ONLY = 0x10, MCU_DATA = 0x11, USB_CMD = 0x80, }; enum class FeatureReport : u8 { Last_SUBCMD = 0x02, OTA_GW_UPGRADE = 0x70, SETUP_MEM_READ = 0x71, MEM_READ = 0x72, ERASE_MEM_SECTOR = 0x73, MEM_WRITE = 0x74, LAUNCH = 0x75, }; enum class SubCommand : u8 { STATE = 0x00, MANUAL_BT_PAIRING = 0x01, REQ_DEV_INFO = 0x02, SET_REPORT_MODE = 0x03, TRIGGERS_ELAPSED = 0x04, GET_PAGE_LIST_STATE = 0x05, SET_HCI_STATE = 0x06, RESET_PAIRING_INFO = 0x07, LOW_POWER_MODE = 0x08, SPI_FLASH_READ = 0x10, SPI_FLASH_WRITE = 0x11, SPI_SECTOR_ERASE = 0x12, RESET_MCU = 0x20, SET_MCU_CONFIG = 0x21, SET_MCU_STATE = 0x22, SET_PLAYER_LIGHTS = 0x30, GET_PLAYER_LIGHTS = 0x31, SET_HOME_LIGHT = 0x38, ENABLE_IMU = 0x40, SET_IMU_SENSITIVITY = 0x41, WRITE_IMU_REG = 0x42, READ_IMU_REG = 0x43, ENABLE_VIBRATION = 0x48, GET_REGULATED_VOLTAGE = 0x50, SET_EXTERNAL_CONFIG = 0x58, GET_EXTERNAL_DEVICE_INFO = 0x59, ENABLE_EXTERNAL_POLLING = 0x5A, DISABLE_EXTERNAL_POLLING = 0x5B, SET_EXTERNAL_FORMAT_CONFIG = 0x5C, }; enum class UsbSubCommand : u8 { CONN_STATUS = 0x01, HADSHAKE = 0x02, BAUDRATE_3M = 0x03, NO_TIMEOUT = 0x04, EN_TIMEOUT = 0x05, RESET = 0x06, PRE_HANDSHAKE = 0x91, SEND_UART = 0x92, }; enum class CalibrationMagic : u8 { USR_MAGIC_0 = 0xB2, USR_MAGIC_1 = 0xA1, }; enum class SpiAddress : u16 { MAGIC = 0x0000, MAC_ADDRESS = 0x0015, PAIRING_INFO = 0x2000, SHIPMENT = 0x5000, SERIAL_NUMBER = 0x6000, DEVICE_TYPE = 0x6012, FORMAT_VERSION = 0x601B, FACT_IMU_DATA = 0x6020, FACT_LEFT_DATA = 0x603d, FACT_RIGHT_DATA = 0x6046, COLOR_DATA = 0x6050, DESIGN_VARIATION = 0x605C, SENSOR_DATA = 0x6080, USER_LEFT_MAGIC = 0x8010, USER_LEFT_DATA = 0x8012, USER_RIGHT_MAGIC = 0x801B, USER_RIGHT_DATA = 0x801D, USER_IMU_MAGIC = 0x8026, USER_IMU_DATA = 0x8028, }; enum class ReportMode : u8 { ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00, ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01, ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02, ACTIVE_POLLING_IR_CAMERA_DATA = 0x03, SUBCMD_REPLY = 0x21, MCU_UPDATE_STATE = 0x23, STANDARD_FULL_60HZ = 0x30, NFC_IR_MODE_60HZ = 0x31, SIMPLE_HID_MODE = 0x3F, INPUT_USB_RESPONSE = 0x81, }; enum class GyroSensitivity : u8 { DPS250, DPS500, DPS1000, DPS2000, // Default }; enum class AccelerometerSensitivity : u8 { G8, // Default G4, G2, G16, }; enum class GyroPerformance : u8 { HZ833, HZ208, // Default }; enum class AccelerometerPerformance : u8 { HZ200, HZ100, // Default }; enum class MCUCommand : u8 { ConfigureMCU = 0x21, ConfigureIR = 0x23, }; enum class MCUSubCommand : u8 { SetMCUMode = 0x0, SetDeviceMode = 0x1, ReadDeviceMode = 0x02, WriteDeviceRegisters = 0x4, }; enum class MCUMode : u8 { Suspend = 0, Standby = 1, Ringcon = 3, NFC = 4, IR = 5, MaybeFWUpdate = 6, }; enum class MCURequest : u8 { GetMCUStatus = 1, GetNFCData = 2, GetIRData = 3, }; enum class MCUReport : u8 { Empty = 0x00, StateReport = 0x01, IRData = 0x03, BusyInitializing = 0x0b, IRStatus = 0x13, IRRegisters = 0x1b, NFCState = 0x2a, NFCReadData = 0x3a, EmptyAwaitingCmd = 0xff, }; enum class MCUPacketFlag : u8 { MorePacketsRemaining = 0x00, LastCommandPacket = 0x08, }; enum class NFCCommand : u8 { CancelAll = 0x00, StartPolling = 0x01, StopPolling = 0x02, StartWaitingRecieve = 0x04, ReadNtag = 0x06, WriteNtag = 0x08, Mifare = 0x0F, }; enum class NFCTagType : u8 { AllTags = 0x00, Ntag215 = 0x01, }; enum class NFCPages { Block0 = 0, Block3 = 3, Block45 = 45, Block135 = 135, Block231 = 231, }; enum class NFCStatus : u8 { Ready = 0x00, Polling = 0x01, LastPackage = 0x04, WriteDone = 0x05, TagLost = 0x07, WriteReady = 0x09, MifareDone = 0x10, }; enum class MifareCmd : u8 { None = 0x00, Read = 0x30, AuthA = 0x60, AuthB = 0x61, Write = 0xA0, Transfer = 0xB0, Decrement = 0xC0, Increment = 0xC1, Store = 0xC2 }; enum class IrsMode : u8 { None = 0x02, Moment = 0x03, Dpd = 0x04, Clustering = 0x06, ImageTransfer = 0x07, Silhouette = 0x08, TeraImage = 0x09, SilhouetteTeraImage = 0x0A, }; enum class IrsResolution { Size320x240, Size160x120, Size80x60, Size40x30, Size20x15, None, }; enum class IrsResolutionCode : u8 { Size320x240 = 0x00, // Full pixel array Size160x120 = 0x50, // Sensor Binning [2 X 2] Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2] Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4] Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4] }; // Size of image divided by 300 enum class IrsFragments : u8 { Size20x15 = 0x00, Size40x30 = 0x03, Size80x60 = 0x0f, Size160x120 = 0x3f, Size320x240 = 0xFF, }; enum class IrLeds : u8 { BrightAndDim = 0x00, Bright = 0x20, Dim = 0x10, None = 0x30, }; enum class IrExLedFilter : u8 { Disabled = 0x00, Enabled = 0x03, }; enum class IrImageFlip : u8 { Normal = 0x00, Inverted = 0x02, }; enum class IrRegistersAddress : u16 { UpdateTime = 0x0400, FinalizeConfig = 0x0700, LedFilter = 0x0e00, Leds = 0x1000, LedIntensitiyMSB = 0x1100, LedIntensitiyLSB = 0x1200, ImageFlip = 0x2d00, Resolution = 0x2e00, DigitalGainLSB = 0x2e01, DigitalGainMSB = 0x2f01, ExposureLSB = 0x3001, ExposureMSB = 0x3101, ExposureTime = 0x3201, WhitePixelThreshold = 0x4301, DenoiseSmoothing = 0x6701, DenoiseEdge = 0x6801, DenoiseColor = 0x6901, }; enum class ExternalDeviceId : u16 { RingController = 0x2000, Starlink = 0x2800, }; enum class DriverResult { Success, WrongReply, Timeout, InvalidParameters, UnsupportedControllerType, HandleInUse, ErrorReadingData, ErrorWritingData, NoDeviceDetected, InvalidHandle, NotSupported, Disabled, Delayed, Unknown, }; struct MotionSensorCalibration { s16 offset; s16 scale; }; struct MotionCalibration { std::array accelerometer; std::array gyro; }; // Basic motion data containing data from the sensors and a timestamp in microseconds struct MotionData { float gyro_x{}; float gyro_y{}; float gyro_z{}; float accel_x{}; float accel_y{}; float accel_z{}; u64 delta_timestamp{}; }; // Output from SPI read command containing user calibration magic struct MagicSpiCalibration { CalibrationMagic first; CalibrationMagic second; }; static_assert(sizeof(MagicSpiCalibration) == 0x2, "MagicSpiCalibration is an invalid size"); // Output from SPI read command containing left joystick calibration struct JoystickLeftSpiCalibration { std::array max; std::array center; std::array min; }; static_assert(sizeof(JoystickLeftSpiCalibration) == 0x9, "JoystickLeftSpiCalibration is an invalid size"); // Output from SPI read command containing right joystick calibration struct JoystickRightSpiCalibration { std::array center; std::array min; std::array max; }; static_assert(sizeof(JoystickRightSpiCalibration) == 0x9, "JoystickRightSpiCalibration is an invalid size"); struct JoyStickAxisCalibration { u16 max; u16 min; u16 center; }; struct JoyStickCalibration { JoyStickAxisCalibration x; JoyStickAxisCalibration y; }; struct ImuSpiCalibration { std::array accelerometer_offset; std::array accelerometer_scale; std::array gyroscope_offset; std::array gyroscope_scale; }; static_assert(sizeof(ImuSpiCalibration) == 0x18, "ImuSpiCalibration is an invalid size"); struct RingCalibration { s16 default_value; s16 max_value; s16 min_value; }; struct Color { u32 body; u32 buttons; u32 left_grip; u32 right_grip; }; struct Battery { union { u8 raw{}; BitField<0, 4, u8> unknown; BitField<4, 1, u8> charging; BitField<5, 3, u8> status; }; }; struct VibrationValue { f32 low_amplitude; f32 low_frequency; f32 high_amplitude; f32 high_frequency; }; struct JoyconHandle { SDL_hid_device* handle = nullptr; u8 packet_counter{}; }; struct MCUConfig { MCUCommand command; MCUSubCommand sub_command; MCUMode mode; INSERT_PADDING_BYTES(0x22); u8 crc; }; static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size"); #pragma pack(push, 1) struct InputReportPassive { ReportMode report_mode; u16 button_input; u8 stick_state; std::array unknown_data; }; static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size"); struct InputReportActive { ReportMode report_mode; u8 packet_id; Battery battery_status; std::array button_input; std::array left_stick_state; std::array right_stick_state; u8 vibration_code; std::array motion_input; INSERT_PADDING_BYTES(0x2); s16 ring_input; }; static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size"); struct InputReportNfcIr { ReportMode report_mode; u8 packet_id; Battery battery_status; std::array button_input; std::array left_stick_state; std::array right_stick_state; u8 vibration_code; std::array motion_input; INSERT_PADDING_BYTES(0x4); }; static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size"); #pragma pack(pop) struct NFCReadBlock { u8 start; u8 end; }; static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size"); struct NFCReadBlockCommand { u8 block_count{}; std::array blocks{}; }; static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size"); struct NFCReadCommandData { u8 unknown; u8 uuid_length; TagUUID uid; NFCTagType tag_type; NFCReadBlockCommand read_block; }; static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); #pragma pack(push, 1) struct NFCWriteCommandData { u8 unknown; u8 uuid_length; TagUUID uid; NFCTagType tag_type; u8 unknown2; u8 unknown3; u8 unknown4; u8 unknown5; u8 unknown6; u8 unknown7; u8 unknown8; u8 magic; u16_be write_count; u8 amiibo_version; }; static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size"); #pragma pack(pop) struct MifareCommandData { u8 unknown1; u8 unknown2; u8 number_of_short_bytes; MifareUUID uid; }; static_assert(sizeof(MifareCommandData) == 0x7, "MifareCommandData is an invalid size"); struct NFCPollingCommandData { u8 enable_mifare; u8 unknown_1; u8 unknown_2; u8 unknown_3; u8 unknown_4; }; static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); struct NFCRequestState { NFCCommand command_argument; u8 block_id; u8 packet_id; MCUPacketFlag packet_flag; u8 data_length; union { std::array raw_data; NFCReadCommandData nfc_read; NFCPollingCommandData nfc_polling; }; u8 crc; INSERT_PADDING_BYTES(0x1); }; static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); struct NFCDataChunk { u8 nfc_page; u8 data_size; std::array data; }; struct NFCWritePackage { NFCWriteCommandData command_data; u8 number_of_chunks; std::array data_chunks; }; struct MifareReadChunk { MifareCmd command; std::array sector_key; u8 sector; }; struct MifareWriteChunk { MifareCmd command; std::array sector_key; u8 sector; std::array data; }; struct MifareReadData { u8 sector; std::array data; }; struct MifareReadPackage { MifareCommandData command_data; std::array data_chunks; }; struct MifareWritePackage { MifareCommandData command_data; std::array data_chunks; }; struct TagInfo { u8 uuid_length; u8 protocol; u8 tag_type; std::array uuid; }; struct IrsConfigure { MCUCommand command; MCUSubCommand sub_command; IrsMode irs_mode; IrsFragments number_of_fragments; u16 mcu_major_version; u16 mcu_minor_version; INSERT_PADDING_BYTES(0x1D); u8 crc; }; static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size"); #pragma pack(push, 1) struct IrsRegister { IrRegistersAddress address; u8 value; }; static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size"); struct IrsWriteRegisters { MCUCommand command; MCUSubCommand sub_command; u8 number_of_registers; std::array registers; INSERT_PADDING_BYTES(0x7); u8 crc; }; static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size"); #pragma pack(pop) struct FirmwareVersion { u8 major; u8 minor; }; static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); struct DeviceInfo { FirmwareVersion firmware; std::array unknown_1; MacAddress mac_address; std::array unknown_2; }; static_assert(sizeof(DeviceInfo) == 0xC, "DeviceInfo is an invalid size"); struct MotionStatus { bool is_enabled; u64 delta_time; GyroSensitivity gyro_sensitivity; AccelerometerSensitivity accelerometer_sensitivity; }; struct RingStatus { bool is_enabled; s16 default_value; s16 max_value; s16 min_value; }; struct VibrationPacket { OutputReport output_report; u8 packet_counter; std::array vibration_data; }; static_assert(sizeof(VibrationPacket) == 0xA, "VibrationPacket is an invalid size"); struct SubCommandPacket { OutputReport output_report; u8 packet_counter; INSERT_PADDING_BYTES(0x8); // This contains vibration data union { SubCommand sub_command; MCUSubCommand mcu_sub_command; }; std::array command_data; }; static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size"); #pragma pack(push, 1) struct ReadSpiPacket { SpiAddress spi_address; INSERT_PADDING_BYTES(0x2); u8 size; }; static_assert(sizeof(ReadSpiPacket) == 0x5, "ReadSpiPacket is an invalid size"); struct SubCommandResponse { InputReportPassive input_report; SubCommand sub_command; union { std::array command_data; SpiAddress spi_address; // Reply from SPI_FLASH_READ subcommand ExternalDeviceId external_device_id; // Reply from GET_EXTERNAL_DEVICE_INFO subcommand DeviceInfo device_info; // Reply from REQ_DEV_INFO subcommand }; u8 crc; // This is never used }; static_assert(sizeof(SubCommandResponse) == 0x40, "SubCommandResponse is an invalid size"); #pragma pack(pop) struct MCUCommandResponse { InputReportNfcIr input_report; INSERT_PADDING_BYTES(0x8); MCUReport mcu_report; std::array mcu_data; u8 crc; }; static_assert(sizeof(MCUCommandResponse) == 0x170, "MCUCommandResponse is an invalid size"); struct JoyconCallbacks { std::function on_battery_data; std::function on_color_data; std::function on_button_data; std::function on_stick_data; std::function on_motion_data; std::function on_ring_data; std::function on_amiibo_data; std::function&, IrsResolution)> on_camera_data; }; } // namespace InputCommon::Joycon