diff options
Diffstat (limited to 'src/hid_core/hidbus/ringcon.cpp')
| -rw-r--r-- | src/hid_core/hidbus/ringcon.cpp | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/src/hid_core/hidbus/ringcon.cpp b/src/hid_core/hidbus/ringcon.cpp new file mode 100644 index 000000000..cedf25c16 --- /dev/null +++ b/src/hid_core/hidbus/ringcon.cpp | |||
| @@ -0,0 +1,292 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/core.h" | ||
| 5 | #include "core/hle/kernel/k_event.h" | ||
| 6 | #include "core/hle/kernel/k_readable_event.h" | ||
| 7 | #include "core/memory.h" | ||
| 8 | #include "hid_core/frontend/emulated_controller.h" | ||
| 9 | #include "hid_core/hid_core.h" | ||
| 10 | #include "hid_core/hidbus/ringcon.h" | ||
| 11 | |||
| 12 | namespace Service::HID { | ||
| 13 | |||
| 14 | RingController::RingController(Core::System& system_, | ||
| 15 | KernelHelpers::ServiceContext& service_context_) | ||
| 16 | : HidbusBase(system_, service_context_) { | ||
| 17 | input = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 18 | } | ||
| 19 | |||
| 20 | RingController::~RingController() = default; | ||
| 21 | |||
| 22 | void RingController::OnInit() { | ||
| 23 | input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||
| 24 | Common::Input::PollingMode::Ring); | ||
| 25 | return; | ||
| 26 | } | ||
| 27 | |||
| 28 | void RingController::OnRelease() { | ||
| 29 | input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||
| 30 | Common::Input::PollingMode::Active); | ||
| 31 | return; | ||
| 32 | }; | ||
| 33 | |||
| 34 | void RingController::OnUpdate() { | ||
| 35 | if (!is_activated) { | ||
| 36 | return; | ||
| 37 | } | ||
| 38 | |||
| 39 | if (!device_enabled) { | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | if (!polling_mode_enabled || transfer_memory == 0) { | ||
| 44 | return; | ||
| 45 | } | ||
| 46 | |||
| 47 | // TODO: Increment multitasking counters from motion and sensor data | ||
| 48 | |||
| 49 | switch (polling_mode) { | ||
| 50 | case JoyPollingMode::SixAxisSensorEnable: { | ||
| 51 | enable_sixaxis_data.header.total_entries = 10; | ||
| 52 | enable_sixaxis_data.header.result = ResultSuccess; | ||
| 53 | const auto& last_entry = | ||
| 54 | enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; | ||
| 55 | |||
| 56 | enable_sixaxis_data.header.latest_entry = | ||
| 57 | (enable_sixaxis_data.header.latest_entry + 1) % 10; | ||
| 58 | auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; | ||
| 59 | |||
| 60 | curr_entry.sampling_number = last_entry.sampling_number + 1; | ||
| 61 | curr_entry.polling_data.sampling_number = curr_entry.sampling_number; | ||
| 62 | |||
| 63 | const RingConData ringcon_value = GetSensorValue(); | ||
| 64 | curr_entry.polling_data.out_size = sizeof(ringcon_value); | ||
| 65 | std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value)); | ||
| 66 | |||
| 67 | system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data, | ||
| 68 | sizeof(enable_sixaxis_data)); | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | default: | ||
| 72 | LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | RingController::RingConData RingController::GetSensorValue() const { | ||
| 78 | RingConData ringcon_sensor_value{ | ||
| 79 | .status = DataValid::Valid, | ||
| 80 | .data = 0, | ||
| 81 | }; | ||
| 82 | |||
| 83 | const f32 force_value = input->GetRingSensorForce().force * range; | ||
| 84 | ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value; | ||
| 85 | |||
| 86 | return ringcon_sensor_value; | ||
| 87 | } | ||
| 88 | |||
| 89 | u8 RingController::GetDeviceId() const { | ||
| 90 | return device_id; | ||
| 91 | } | ||
| 92 | |||
| 93 | std::vector<u8> RingController::GetReply() const { | ||
| 94 | const RingConCommands current_command = command; | ||
| 95 | |||
| 96 | switch (current_command) { | ||
| 97 | case RingConCommands::GetFirmwareVersion: | ||
| 98 | return GetFirmwareVersionReply(); | ||
| 99 | case RingConCommands::ReadId: | ||
| 100 | return GetReadIdReply(); | ||
| 101 | case RingConCommands::c20105: | ||
| 102 | return GetC020105Reply(); | ||
| 103 | case RingConCommands::ReadUnkCal: | ||
| 104 | return GetReadUnkCalReply(); | ||
| 105 | case RingConCommands::ReadFactoryCal: | ||
| 106 | return GetReadFactoryCalReply(); | ||
| 107 | case RingConCommands::ReadUserCal: | ||
| 108 | return GetReadUserCalReply(); | ||
| 109 | case RingConCommands::ReadRepCount: | ||
| 110 | return GetReadRepCountReply(); | ||
| 111 | case RingConCommands::ReadTotalPushCount: | ||
| 112 | return GetReadTotalPushCountReply(); | ||
| 113 | case RingConCommands::ResetRepCount: | ||
| 114 | return GetResetRepCountReply(); | ||
| 115 | case RingConCommands::SaveCalData: | ||
| 116 | return GetSaveDataReply(); | ||
| 117 | default: | ||
| 118 | return GetErrorReply(); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | bool RingController::SetCommand(std::span<const u8> data) { | ||
| 123 | if (data.size() < 4) { | ||
| 124 | LOG_ERROR(Service_HID, "Command size not supported {}", data.size()); | ||
| 125 | command = RingConCommands::Error; | ||
| 126 | return false; | ||
| 127 | } | ||
| 128 | |||
| 129 | std::memcpy(&command, data.data(), sizeof(RingConCommands)); | ||
| 130 | |||
| 131 | switch (command) { | ||
| 132 | case RingConCommands::GetFirmwareVersion: | ||
| 133 | case RingConCommands::ReadId: | ||
| 134 | case RingConCommands::c20105: | ||
| 135 | case RingConCommands::ReadUnkCal: | ||
| 136 | case RingConCommands::ReadFactoryCal: | ||
| 137 | case RingConCommands::ReadUserCal: | ||
| 138 | case RingConCommands::ReadRepCount: | ||
| 139 | case RingConCommands::ReadTotalPushCount: | ||
| 140 | ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); | ||
| 141 | send_command_async_event->Signal(); | ||
| 142 | return true; | ||
| 143 | case RingConCommands::ResetRepCount: | ||
| 144 | ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); | ||
| 145 | total_rep_count = 0; | ||
| 146 | send_command_async_event->Signal(); | ||
| 147 | return true; | ||
| 148 | case RingConCommands::SaveCalData: { | ||
| 149 | ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); | ||
| 150 | |||
| 151 | SaveCalData save_info{}; | ||
| 152 | std::memcpy(&save_info, data.data(), sizeof(SaveCalData)); | ||
| 153 | user_calibration = save_info.calibration; | ||
| 154 | send_command_async_event->Signal(); | ||
| 155 | return true; | ||
| 156 | } | ||
| 157 | default: | ||
| 158 | LOG_ERROR(Service_HID, "Command not implemented {}", command); | ||
| 159 | command = RingConCommands::Error; | ||
| 160 | // Signal a reply to avoid softlocking the game | ||
| 161 | send_command_async_event->Signal(); | ||
| 162 | return false; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | std::vector<u8> RingController::GetFirmwareVersionReply() const { | ||
| 167 | const FirmwareVersionReply reply{ | ||
| 168 | .status = DataValid::Valid, | ||
| 169 | .firmware = version, | ||
| 170 | }; | ||
| 171 | |||
| 172 | return GetDataVector(reply); | ||
| 173 | } | ||
| 174 | |||
| 175 | std::vector<u8> RingController::GetReadIdReply() const { | ||
| 176 | // The values are hardcoded from a real joycon | ||
| 177 | const ReadIdReply reply{ | ||
| 178 | .status = DataValid::Valid, | ||
| 179 | .id_l_x0 = 8, | ||
| 180 | .id_l_x0_2 = 41, | ||
| 181 | .id_l_x4 = 22294, | ||
| 182 | .id_h_x0 = 19777, | ||
| 183 | .id_h_x0_2 = 13621, | ||
| 184 | .id_h_x4 = 8245, | ||
| 185 | }; | ||
| 186 | |||
| 187 | return GetDataVector(reply); | ||
| 188 | } | ||
| 189 | |||
| 190 | std::vector<u8> RingController::GetC020105Reply() const { | ||
| 191 | const Cmd020105Reply reply{ | ||
| 192 | .status = DataValid::Valid, | ||
| 193 | .data = 1, | ||
| 194 | }; | ||
| 195 | |||
| 196 | return GetDataVector(reply); | ||
| 197 | } | ||
| 198 | |||
| 199 | std::vector<u8> RingController::GetReadUnkCalReply() const { | ||
| 200 | const ReadUnkCalReply reply{ | ||
| 201 | .status = DataValid::Valid, | ||
| 202 | .data = 0, | ||
| 203 | }; | ||
| 204 | |||
| 205 | return GetDataVector(reply); | ||
| 206 | } | ||
| 207 | |||
| 208 | std::vector<u8> RingController::GetReadFactoryCalReply() const { | ||
| 209 | const ReadFactoryCalReply reply{ | ||
| 210 | .status = DataValid::Valid, | ||
| 211 | .calibration = factory_calibration, | ||
| 212 | }; | ||
| 213 | |||
| 214 | return GetDataVector(reply); | ||
| 215 | } | ||
| 216 | |||
| 217 | std::vector<u8> RingController::GetReadUserCalReply() const { | ||
| 218 | const ReadUserCalReply reply{ | ||
| 219 | .status = DataValid::Valid, | ||
| 220 | .calibration = user_calibration, | ||
| 221 | }; | ||
| 222 | |||
| 223 | return GetDataVector(reply); | ||
| 224 | } | ||
| 225 | |||
| 226 | std::vector<u8> RingController::GetReadRepCountReply() const { | ||
| 227 | const GetThreeByteReply reply{ | ||
| 228 | .status = DataValid::Valid, | ||
| 229 | .data = {total_rep_count, 0, 0}, | ||
| 230 | .crc = GetCrcValue({total_rep_count, 0, 0, 0}), | ||
| 231 | }; | ||
| 232 | |||
| 233 | return GetDataVector(reply); | ||
| 234 | } | ||
| 235 | |||
| 236 | std::vector<u8> RingController::GetReadTotalPushCountReply() const { | ||
| 237 | const GetThreeByteReply reply{ | ||
| 238 | .status = DataValid::Valid, | ||
| 239 | .data = {total_push_count, 0, 0}, | ||
| 240 | .crc = GetCrcValue({total_push_count, 0, 0, 0}), | ||
| 241 | }; | ||
| 242 | |||
| 243 | return GetDataVector(reply); | ||
| 244 | } | ||
| 245 | |||
| 246 | std::vector<u8> RingController::GetResetRepCountReply() const { | ||
| 247 | return GetReadRepCountReply(); | ||
| 248 | } | ||
| 249 | |||
| 250 | std::vector<u8> RingController::GetSaveDataReply() const { | ||
| 251 | const StatusReply reply{ | ||
| 252 | .status = DataValid::Valid, | ||
| 253 | }; | ||
| 254 | |||
| 255 | return GetDataVector(reply); | ||
| 256 | } | ||
| 257 | |||
| 258 | std::vector<u8> RingController::GetErrorReply() const { | ||
| 259 | const ErrorReply reply{ | ||
| 260 | .status = DataValid::BadCRC, | ||
| 261 | }; | ||
| 262 | |||
| 263 | return GetDataVector(reply); | ||
| 264 | } | ||
| 265 | |||
| 266 | u8 RingController::GetCrcValue(const std::vector<u8>& data) const { | ||
| 267 | u8 crc = 0; | ||
| 268 | for (std::size_t index = 0; index < data.size(); index++) { | ||
| 269 | for (u8 i = 0x80; i > 0; i >>= 1) { | ||
| 270 | bool bit = (crc & 0x80) != 0; | ||
| 271 | if ((data[index] & i) != 0) { | ||
| 272 | bit = !bit; | ||
| 273 | } | ||
| 274 | crc <<= 1; | ||
| 275 | if (bit) { | ||
| 276 | crc ^= 0x8d; | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | return crc; | ||
| 281 | } | ||
| 282 | |||
| 283 | template <typename T> | ||
| 284 | std::vector<u8> RingController::GetDataVector(const T& reply) const { | ||
| 285 | static_assert(std::is_trivially_copyable_v<T>); | ||
| 286 | std::vector<u8> data; | ||
| 287 | data.resize(sizeof(reply)); | ||
| 288 | std::memcpy(data.data(), &reply, sizeof(reply)); | ||
| 289 | return data; | ||
| 290 | } | ||
| 291 | |||
| 292 | } // namespace Service::HID | ||