From 459fb2b21337bae60194a2a99ce68c87aaed522d Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Wed, 28 Dec 2022 15:21:12 -0600 Subject: [PATCH] input_common: Implement joycon ir camera --- src/core/hid/emulated_controller.cpp | 21 +- src/core/hid/emulated_controller.h | 5 +- src/core/hle/service/hid/irs.cpp | 11 + src/input_common/CMakeLists.txt | 2 + src/input_common/drivers/joycon.cpp | 29 +- src/input_common/drivers/joycon.h | 5 +- src/input_common/helpers/joycon_driver.cpp | 55 +++- src/input_common/helpers/joycon_driver.h | 4 + .../joycon_protocol/common_protocol.cpp | 13 + .../helpers/joycon_protocol/common_protocol.h | 7 + .../helpers/joycon_protocol/irs.cpp | 300 ++++++++++++++++++ .../helpers/joycon_protocol/irs.h | 63 ++++ .../helpers/joycon_protocol/joycon_types.h | 107 ++++++- .../helpers/joycon_protocol/poller.cpp | 6 +- .../helpers/joycon_protocol/poller.h | 3 +- 15 files changed, 608 insertions(+), 23 deletions(-) create mode 100644 src/input_common/helpers/joycon_protocol/irs.cpp create mode 100644 src/input_common/helpers/joycon_protocol/irs.h diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 915ffa490..faf9e7c4e 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -145,7 +145,9 @@ void EmulatedController::LoadDevices() { battery_params[LeftIndex].Set("battery", true); battery_params[RightIndex].Set("battery", true); - camera_params = Common::ParamPackage{"engine:camera,camera:1"}; + camera_params[0] = right_joycon; + camera_params[0].Set("camera", true); + camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; nfc_params[1] = right_joycon; @@ -153,7 +155,7 @@ void EmulatedController::LoadDevices() { output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; - output_params[2] = camera_params; + output_params[2] = camera_params[1]; output_params[3] = nfc_params[0]; output_params[LeftIndex].Set("output", true); output_params[RightIndex].Set("output", true); @@ -171,7 +173,7 @@ void EmulatedController::LoadDevices() { std::ranges::transform(battery_params, battery_devices.begin(), Common::Input::CreateInputDevice); std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice); - camera_devices = Common::Input::CreateInputDevice(camera_params); + std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice); std::ranges::transform(ring_params, ring_analog_devices.begin(), Common::Input::CreateInputDevice); std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice); @@ -362,12 +364,15 @@ void EmulatedController::ReloadInput() { motion_devices[index]->ForceUpdate(); } - if (camera_devices) { - camera_devices->SetCallback({ + for (std::size_t index = 0; index < camera_devices.size(); ++index) { + if (!camera_devices[index]) { + continue; + } + camera_devices[index]->SetCallback({ .on_change = [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, }); - camera_devices->ForceUpdate(); + camera_devices[index]->ForceUpdate(); } for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) { @@ -477,7 +482,9 @@ void EmulatedController::UnloadInput() { for (auto& stick : virtual_stick_devices) { stick.reset(); } - camera_devices.reset(); + for (auto& camera : camera_devices) { + camera.reset(); + } for (auto& ring : ring_analog_devices) { ring.reset(); } diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index fb931fc0a..edebfc15c 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -39,7 +39,8 @@ using ColorDevices = std::array, max_emulated_controllers>; using BatteryDevices = std::array, max_emulated_controllers>; -using CameraDevices = std::unique_ptr; +using CameraDevices = + std::array, max_emulated_controllers>; using RingAnalogDevices = std::array, max_emulated_controllers>; using NfcDevices = @@ -52,7 +53,7 @@ using ControllerMotionParams = std::array; using ColorParams = std::array; using BatteryParams = std::array; -using CameraParams = Common::ParamPackage; +using CameraParams = std::array; using RingAnalogParams = std::array; using NfcParams = std::array; using OutputParams = std::array; diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 6a3453457..3c1fa2274 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -74,6 +74,8 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); + npad_device->SetPollingMode(Common::Input::PollingMode::Active); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -108,6 +110,7 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { auto result = IsIrCameraHandleValid(parameters.camera_handle); if (result.IsSuccess()) { // TODO: Stop Image processor + npad_device->SetPollingMode(Common::Input::PollingMode::Active); result = ResultSuccess; } @@ -139,6 +142,7 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor(parameters.camera_handle, device); auto& image_transfer_processor = GetProcessor(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -170,6 +174,7 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { auto& image_transfer_processor = GetProcessor(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -219,6 +224,7 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { GetProcessor(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -294,6 +300,7 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { auto& image_transfer_processor = GetProcessor(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -343,6 +350,7 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor(camera_handle, device); auto& image_transfer_processor = GetProcessor(camera_handle); image_transfer_processor.SetConfig(processor_config); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -453,6 +461,7 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { GetProcessor(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -479,6 +488,7 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor(camera_handle, device); auto& image_transfer_processor = GetProcessor(camera_handle); image_transfer_processor.SetConfig(processor_config); + npad_device->SetPollingMode(Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -504,6 +514,7 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { auto result = IsIrCameraHandleValid(parameters.camera_handle); if (result.IsSuccess()) { // TODO: Stop image processor async + npad_device->SetPollingMode(Common::Input::PollingMode::IR); result = ResultSuccess; } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 9c901af2a..e3b627e4f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -64,6 +64,8 @@ if (ENABLE_SDL2) helpers/joycon_protocol/generic_functions.cpp helpers/joycon_protocol/generic_functions.h helpers/joycon_protocol/joycon_types.h + helpers/joycon_protocol/irs.cpp + helpers/joycon_protocol/irs.h helpers/joycon_protocol/nfc.cpp helpers/joycon_protocol/nfc.h helpers/joycon_protocol/poller.cpp diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp index cf54f1b53..6c03e0953 100644 --- a/src/input_common/drivers/joycon.cpp +++ b/src/input_common/drivers/joycon.cpp @@ -191,6 +191,10 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { .on_amiibo_data = {[this, port](const std::vector& amiibo_data) { OnAmiiboUpdate(port, amiibo_data); }}, + .on_camera_data = {[this, port](const std::vector& camera_data, + Joycon::IrsResolution format) { + OnCameraUpdate(port, camera_data, format); + }}, }; handle->InitializeDevice(); @@ -265,9 +269,14 @@ Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, handle->SetLedConfig(static_cast(led_config))); } -Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier_, +Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, Common::Input::CameraFormat camera_format) { - return Common::Input::DriverResult::NotSupported; + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + return static_cast(handle->SetIrsConfig( + Joycon::IrsMode::ImageTransfer, static_cast(camera_format))); }; Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { @@ -288,18 +297,16 @@ Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identif } switch (polling_mode) { - case Common::Input::PollingMode::NFC: - return static_cast(handle->SetNfcMode()); - break; case Common::Input::PollingMode::Active: return static_cast(handle->SetActiveMode()); - break; case Common::Input::PollingMode::Pasive: return static_cast(handle->SetPasiveMode()); - break; + case Common::Input::PollingMode::IR: + return static_cast(handle->SetIrMode()); + case Common::Input::PollingMode::NFC: + return static_cast(handle->SetNfcMode()); case Common::Input::PollingMode::Ring: return static_cast(handle->SetRingConMode()); - break; default: return Common::Input::DriverResult::NotSupported; } @@ -390,6 +397,12 @@ void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector& amiibo_dat SetNfc(identifier, {nfc_state, amiibo_data}); } +void Joycons::OnCameraUpdate(std::size_t port, const std::vector& camera_data, + Joycon::IrsResolution format) { + const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); + SetCamera(identifier, {static_cast(format), camera_data}); +} + std::shared_ptr Joycons::GetHandle(PadIdentifier identifier) const { auto is_handle_active = [&](std::shared_ptr device) { if (!device) { diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h index 1a04c19fd..f180b7478 100644 --- a/src/input_common/drivers/joycon.h +++ b/src/input_common/drivers/joycon.h @@ -17,6 +17,7 @@ struct Color; struct MotionData; enum class ControllerType; enum class DriverResult; +enum class IrsResolution; class JoyconDriver; } // namespace InputCommon::Joycon @@ -35,7 +36,7 @@ public: Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) override; - Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, Common::Input::CameraFormat camera_format) override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; @@ -81,6 +82,8 @@ private: const Joycon::MotionData& value); void OnRingConUpdate(f32 ring_data); void OnAmiiboUpdate(std::size_t port, const std::vector& amiibo_data); + void OnCameraUpdate(std::size_t port, const std::vector& camera_data, + Joycon::IrsResolution format); /// Returns a JoyconHandle corresponding to a PadIdentifier std::shared_ptr GetHandle(PadIdentifier identifier) const; diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index 8217ba7f6..040832a4b 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp @@ -7,6 +7,7 @@ #include "input_common/helpers/joycon_driver.h" #include "input_common/helpers/joycon_protocol/calibration.h" #include "input_common/helpers/joycon_protocol/generic_functions.h" +#include "input_common/helpers/joycon_protocol/irs.h" #include "input_common/helpers/joycon_protocol/nfc.h" #include "input_common/helpers/joycon_protocol/poller.h" #include "input_common/helpers/joycon_protocol/ringcon.h" @@ -78,6 +79,7 @@ DriverResult JoyconDriver::InitializeDevice() { // Initialize HW Protocols calibration_protocol = std::make_unique(hidapi_handle); generic_protocol = std::make_unique(hidapi_handle); + irs_protocol = std::make_unique(hidapi_handle); nfc_protocol = std::make_unique(hidapi_handle); ring_protocol = std::make_unique(hidapi_handle); rumble_protocol = std::make_unique(hidapi_handle); @@ -200,10 +202,15 @@ void JoyconDriver::OnNewData(std::span buffer) { .min_value = ring_calibration.min_value, }; + if (irs_protocol->IsEnabled()) { + irs_protocol->RequestImage(buffer); + joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); + } + if (nfc_protocol->IsEnabled()) { if (amiibo_detected) { if (!nfc_protocol->HasAmiibo()) { - joycon_poller->updateAmiibo({}); + joycon_poller->UpdateAmiibo({}); amiibo_detected = false; return; } @@ -213,7 +220,7 @@ void JoyconDriver::OnNewData(std::span buffer) { std::vector data(0x21C); const auto result = nfc_protocol->ScanAmiibo(data); if (result == DriverResult::Success) { - joycon_poller->updateAmiibo(data); + joycon_poller->UpdateAmiibo(data); amiibo_detected = true; } } @@ -251,6 +258,20 @@ DriverResult JoyconDriver::SetPollingMode() { generic_protocol->EnableImu(false); } + if (irs_protocol->IsEnabled()) { + irs_protocol->DisableIrs(); + } + + if (irs_enabled && supported_features.irs) { + auto result = irs_protocol->EnableIrs(); + if (result == DriverResult::Success) { + disable_input_thread = false; + return result; + } + irs_protocol->DisableIrs(); + LOG_ERROR(Input, "Error enabling IRS"); + } + if (nfc_protocol->IsEnabled()) { amiibo_detected = false; nfc_protocol->DisableNfc(); @@ -375,12 +396,24 @@ DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { return generic_protocol->SetLedPattern(led_pattern); } +DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { + std::scoped_lock lock{mutex}; + if (disable_input_thread) { + return DriverResult::HandleInUse; + } + disable_input_thread = true; + const auto result = irs_protocol->SetIrsConfig(mode_, format_); + disable_input_thread = false; + return result; +} + DriverResult JoyconDriver::SetPasiveMode() { std::scoped_lock lock{mutex}; motion_enabled = false; hidbus_enabled = false; nfc_enabled = false; passive_enabled = true; + irs_enabled = false; return SetPollingMode(); } @@ -390,6 +423,22 @@ DriverResult JoyconDriver::SetActiveMode() { hidbus_enabled = false; nfc_enabled = false; passive_enabled = false; + irs_enabled = false; + return SetPollingMode(); +} + +DriverResult JoyconDriver::SetIrMode() { + std::scoped_lock lock{mutex}; + + if (!supported_features.irs) { + return DriverResult::NotSupported; + } + + motion_enabled = false; + hidbus_enabled = false; + nfc_enabled = false; + passive_enabled = false; + irs_enabled = true; return SetPollingMode(); } @@ -404,6 +453,7 @@ DriverResult JoyconDriver::SetNfcMode() { hidbus_enabled = false; nfc_enabled = true; passive_enabled = false; + irs_enabled = false; return SetPollingMode(); } @@ -418,6 +468,7 @@ DriverResult JoyconDriver::SetRingConMode() { hidbus_enabled = true; nfc_enabled = false; passive_enabled = false; + irs_enabled = false; const auto result = SetPollingMode(); diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index 5ff15c784..61ecf4a6c 100644 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h @@ -13,6 +13,7 @@ namespace InputCommon::Joycon { class CalibrationProtocol; class GenericProtocol; +class IrsProtocol; class NfcProtocol; class JoyconPoller; class RingConProtocol; @@ -41,8 +42,10 @@ public: DriverResult SetVibration(const VibrationValue& vibration); DriverResult SetLedConfig(u8 led_pattern); + DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_); DriverResult SetPasiveMode(); DriverResult SetActiveMode(); + DriverResult SetIrMode(); DriverResult SetNfcMode(); DriverResult SetRingConMode(); @@ -87,6 +90,7 @@ private: // Protocol Features std::unique_ptr calibration_protocol; std::unique_ptr generic_protocol; + std::unique_ptr irs_protocol; std::unique_ptr nfc_protocol; std::unique_ptr joycon_poller; std::unique_ptr ring_protocol; diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp index a4d08fdaf..a329db107 100644 --- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp @@ -120,6 +120,19 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span buffer) { + std::vector local_buffer(MaxResponseSize); + + local_buffer[0] = static_cast(OutputReport::MCU_DATA); + local_buffer[1] = GetCounter(); + local_buffer[10] = static_cast(sc); + for (std::size_t i = 0; i < buffer.size(); ++i) { + local_buffer[11 + i] = buffer[i]; + } + + return SendData(local_buffer); +} + DriverResult JoyconCommonProtocol::SendVibrationReport(std::span buffer) { std::vector local_buffer(MaxResponseSize); diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h index a65e4aa76..2a3feaf59 100644 --- a/src/input_common/helpers/joycon_protocol/common_protocol.h +++ b/src/input_common/helpers/joycon_protocol/common_protocol.h @@ -74,6 +74,13 @@ public: */ DriverResult SendSubCommand(SubCommand sc, std::span buffer, std::vector& output); + /** + * Sends a mcu command to the device + * @param sc sub command to be send + * @param buffer data to be send + */ + DriverResult SendMcuCommand(SubCommand sc, std::span buffer); + /** * Sends vibration data to the joycon * @param buffer data to be send diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp new file mode 100644 index 000000000..9dfa503c2 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.cpp @@ -0,0 +1,300 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/irs.h" + +namespace InputCommon::Joycon { + +IrsProtocol::IrsProtocol(std::shared_ptr handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult IrsProtocol::EnableIrs() { + LOG_INFO(Input, "Enable IRS"); + DriverResult result{DriverResult::Success}; + SetBlocking(); + + if (result == DriverResult::Success) { + result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); + } + if (result == DriverResult::Success) { + result = EnableMCU(true); + } + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); + } + if (result == DriverResult::Success) { + const MCUConfig config{ + .command = MCUCommand::ConfigureMCU, + .sub_command = MCUSubCommand::SetMCUMode, + .mode = MCUMode::IR, + .crc = {}, + }; + + result = ConfigureMCU(config); + } + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR); + } + if (result == DriverResult::Success) { + result = ConfigureIrs(); + } + if (result == DriverResult::Success) { + result = WriteRegistersStep1(); + } + if (result == DriverResult::Success) { + result = WriteRegistersStep2(); + } + + is_enabled = true; + + SetNonBlocking(); + return result; +} + +DriverResult IrsProtocol::DisableIrs() { + LOG_DEBUG(Input, "Disable IRS"); + DriverResult result{DriverResult::Success}; + SetBlocking(); + + if (result == DriverResult::Success) { + result = EnableMCU(false); + } + + is_enabled = false; + + SetNonBlocking(); + return result; +} + +DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) { + irs_mode = mode; + switch (format) { + case IrsResolution::Size320x240: + resolution_code = IrsResolutionCode::Size320x240; + fragments = IrsFragments::Size320x240; + resolution = IrsResolution::Size320x240; + break; + case IrsResolution::Size160x120: + resolution_code = IrsResolutionCode::Size160x120; + fragments = IrsFragments::Size160x120; + resolution = IrsResolution::Size160x120; + break; + case IrsResolution::Size80x60: + resolution_code = IrsResolutionCode::Size80x60; + fragments = IrsFragments::Size80x60; + resolution = IrsResolution::Size80x60; + break; + case IrsResolution::Size20x15: + resolution_code = IrsResolutionCode::Size20x15; + fragments = IrsFragments::Size20x15; + resolution = IrsResolution::Size20x15; + break; + case IrsResolution::Size40x30: + default: + resolution_code = IrsResolutionCode::Size40x30; + fragments = IrsFragments::Size40x30; + resolution = IrsResolution::Size40x30; + break; + } + + // Restart feature + if (is_enabled) { + DisableIrs(); + return EnableIrs(); + } + + return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestImage(std::span buffer) { + const u8 next_packet_fragment = + static_cast((packet_fragment + 1) % (static_cast(fragments) + 1)); + + if (buffer[0] == 0x31 && buffer[49] == 0x03) { + u8 new_packet_fragment = buffer[52]; + if (new_packet_fragment == next_packet_fragment) { + packet_fragment = next_packet_fragment; + memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300); + + return RequestFrame(packet_fragment); + } + + if (new_packet_fragment == packet_fragment) { + return RequestFrame(packet_fragment); + } + + return ResendFrame(next_packet_fragment); + } + + return RequestFrame(packet_fragment); +} + +DriverResult IrsProtocol::ConfigureIrs() { + LOG_DEBUG(Input, "Configure IRS"); + constexpr std::size_t max_tries = 28; + std::vector output; + std::size_t tries = 0; + + const IrsConfigure irs_configuration{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::SetDeviceMode, + .irs_mode = IrsMode::ImageTransfer, + .number_of_fragments = fragments, + .mcu_major_version = 0x0500, + .mcu_minor_version = 0x1800, + .crc = {}, + }; + buf_image.resize((static_cast(fragments) + 1) * 300); + + std::vector request_data(sizeof(IrsConfigure)); + memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + do { + const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (output[15] != 0x0b); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep1() { + LOG_DEBUG(Input, "WriteRegistersStep1"); + DriverResult result{DriverResult::Success}; + constexpr std::size_t max_tries = 28; + std::vector output; + std::size_t tries = 0; + + const IrsWriteRegisters irs_registers{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::WriteDeviceRegisters, + .number_of_registers = 0x9, + .registers = + { + IrsRegister{IrRegistersAddress::Resolution, static_cast(resolution_code)}, + {IrRegistersAddress::ExposureLSB, static_cast(exposure & 0xff)}, + {IrRegistersAddress::ExposureMSB, static_cast(exposure >> 8)}, + {IrRegistersAddress::ExposureTime, 0x00}, + {IrRegistersAddress::Leds, static_cast(leds)}, + {IrRegistersAddress::DigitalGainLSB, static_cast((digital_gain & 0x0f) << 4)}, + {IrRegistersAddress::DigitalGainMSB, static_cast((digital_gain & 0xf0) >> 4)}, + {IrRegistersAddress::LedFilter, static_cast(led_filter)}, + {IrRegistersAddress::WhitePixelThreshold, 0xc8}, + }, + .crc = {}, + }; + + std::vector request_data(sizeof(IrsWriteRegisters)); + memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + + std::array mcu_request{0x02}; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + + if (result != DriverResult::Success) { + return result; + } + + do { + result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + // First time we need to set the report mode + if (result == DriverResult::Success && tries == 0) { + result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request); + } + if (result == DriverResult::Success && tries == 0) { + GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output); + } + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep2() { + LOG_DEBUG(Input, "WriteRegistersStep2"); + constexpr std::size_t max_tries = 28; + std::vector output; + std::size_t tries = 0; + + const IrsWriteRegisters irs_registers{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::WriteDeviceRegisters, + .number_of_registers = 0x8, + .registers = + { + IrsRegister{IrRegistersAddress::LedIntensitiyMSB, + static_cast(led_intensity >> 8)}, + {IrRegistersAddress::LedIntensitiyLSB, static_cast(led_intensity & 0xff)}, + {IrRegistersAddress::ImageFlip, static_cast(image_flip)}, + {IrRegistersAddress::DenoiseSmoothing, static_cast((denoise >> 16) & 0xff)}, + {IrRegistersAddress::DenoiseEdge, static_cast((denoise >> 8) & 0xff)}, + {IrRegistersAddress::DenoiseColor, static_cast(denoise & 0xff)}, + {IrRegistersAddress::UpdateTime, 0x2d}, + {IrRegistersAddress::FinalizeConfig, 0x01}, + }, + .crc = {}, + }; + + std::vector request_data(sizeof(IrsWriteRegisters)); + memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + do { + const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (output[15] != 0x13 && output[15] != 0x23); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestFrame(u8 frame) { + std::array mcu_request{}; + mcu_request[3] = frame; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +DriverResult IrsProtocol::ResendFrame(u8 frame) { + std::array mcu_request{}; + mcu_request[1] = 0x1; + mcu_request[2] = frame; + mcu_request[3] = 0x0; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +std::vector IrsProtocol::GetImage() const { + return buf_image; +} + +IrsResolution IrsProtocol::GetIrsFormat() const { + return resolution; +} + +bool IrsProtocol::IsEnabled() const { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h new file mode 100644 index 000000000..76dfa02ea --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.h @@ -0,0 +1,63 @@ +// 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 "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class IrsProtocol final : private JoyconCommonProtocol { +public: + explicit IrsProtocol(std::shared_ptr handle); + + DriverResult EnableIrs(); + + DriverResult DisableIrs(); + + DriverResult SetIrsConfig(IrsMode mode, IrsResolution format); + + DriverResult RequestImage(std::span buffer); + + std::vector GetImage() const; + + IrsResolution GetIrsFormat() const; + + bool IsEnabled() const; + +private: + DriverResult ConfigureIrs(); + + DriverResult WriteRegistersStep1(); + DriverResult WriteRegistersStep2(); + + DriverResult RequestFrame(u8 frame); + DriverResult ResendFrame(u8 frame); + + IrsMode irs_mode{IrsMode::ImageTransfer}; + IrsResolution resolution{IrsResolution::Size40x30}; + IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30}; + IrsFragments fragments{IrsFragments::Size40x30}; + IrLeds leds{IrLeds::BrightAndDim}; + IrExLedFilter led_filter{IrExLedFilter::Enabled}; + IrImageFlip image_flip{IrImageFlip::Normal}; + u8 digital_gain{0x01}; + u16 exposure{0x2490}; + u16 led_intensity{0x0f10}; + u32 denoise{0x012344}; + + u8 packet_fragment{}; + std::vector buf_image; // 8bpp greyscale image. + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index 36c00a8d7..273c8d07d 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -18,7 +18,7 @@ namespace InputCommon::Joycon { constexpr u32 MaxErrorCount = 50; -constexpr u32 MaxBufferSize = 60; +constexpr u32 MaxBufferSize = 368; constexpr u32 MaxResponseSize = 49; constexpr u32 MaxSubCommandResponseSize = 64; constexpr std::array DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; @@ -273,6 +273,80 @@ enum class NFCTagType : u8 { Ntag215 = 0x01, }; +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 DriverResult { Success, WrongReply, @@ -456,6 +530,36 @@ struct NFCRequestState { }; static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); +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; @@ -490,6 +594,7 @@ struct JoyconCallbacks { std::function on_motion_data; std::function on_ring_data; std::function&)> on_amiibo_data; + std::function&, IrsResolution)> on_camera_data; }; } // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp index fd05d98f3..940b20b7f 100644 --- a/src/input_common/helpers/joycon_protocol/poller.cpp +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -74,10 +74,14 @@ void JoyconPoller::UpdateColor(const Color& color) { callbacks.on_color_data(color); } -void JoyconPoller::updateAmiibo(const std::vector& amiibo_data) { +void JoyconPoller::UpdateAmiibo(const std::vector& amiibo_data) { callbacks.on_amiibo_data(amiibo_data); } +void JoyconPoller::UpdateCamera(const std::vector& camera_data, IrsResolution format) { + callbacks.on_camera_data(camera_data, format); +} + void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { float normalized_value = static_cast(value - ring_status.default_value); if (normalized_value > 0) { diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h index c40fc7bca..354d41dad 100644 --- a/src/input_common/helpers/joycon_protocol/poller.h +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -36,7 +36,8 @@ public: void UpdateColor(const Color& color); void UpdateRing(s16 value, const RingStatus& ring_status); - void updateAmiibo(const std::vector& amiibo_data); + void UpdateAmiibo(const std::vector& amiibo_data); + void UpdateCamera(const std::vector& amiibo_data, IrsResolution format); private: void UpdateActiveLeftPadInput(const InputReportActive& input,