From b50ac5b322e2d99dd4d60f3996cd3a205613b0f6 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Tue, 24 Mar 2020 22:18:56 -0400 Subject: [PATCH] Added ringcon. (#388) * Added ringcon. --- nx/include/switch.h | 1 + nx/include/switch/runtime/ringcon.h | 257 ++++++++++++ nx/source/runtime/ringcon.c | 623 ++++++++++++++++++++++++++++ 3 files changed, 881 insertions(+) create mode 100644 nx/include/switch/runtime/ringcon.h create mode 100644 nx/source/runtime/ringcon.c diff --git a/nx/include/switch.h b/nx/include/switch.h index 5d263d8b..1a43469c 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -139,6 +139,7 @@ extern "C" { #include "switch/runtime/hosversion.h" #include "switch/runtime/nxlink.h" #include "switch/runtime/resolver.h" +#include "switch/runtime/ringcon.h" #include "switch/runtime/util/utf.h" diff --git a/nx/include/switch/runtime/ringcon.h b/nx/include/switch/runtime/ringcon.h new file mode 100644 index 00000000..8c2858b5 --- /dev/null +++ b/nx/include/switch/runtime/ringcon.h @@ -0,0 +1,257 @@ +/** + * @file ringcon.h + * @brief Wrapper for using the Ring-Con attached to a Joy-Con, with hidbus. See also: https://switchbrew.org/wiki/Ring-Con + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../services/hidbus.h" + +#define RINGCON_CAL_MAGIC -0x3502 // 0xCAFE + +/// Whether the output data is valid. +typedef enum { + RingConDataValid_Ok = 0, ///< Valid. + RingConDataValid_CRC = 1, ///< Bad CRC. + RingConDataValid_Cal = 2, ///< Only used with \ref ringconReadUserCal. Calibration is needed via \ref ringconUpdateUserCal. +} RingConDataValid; + +typedef enum { + RingConErrorFlag_BadUserCalUpdate = 0, ///< The output from \ref ringconReadUserCal doesn't match the input used with \ref ringconWriteUserCal, or the \ref RingConDataValid is not ::RingConDataValid_Ok. + RingConErrorFlag_BadFlag = 4, ///< The output flag from \ref ringconCmdx00020105 when successful is invalid. + RingConErrorFlag_BadUserCal = 5, ///< BadUserCal + RingConErrorFlag_BadManuCal = 6, ///< BadManuCal +} RingConErrorFlag; + +/// Ring-Con firmware version. +typedef struct { + u8 fw_main_ver; ///< Main firmware version. + u8 fw_sub_ver; ///< Sub firmware version. +} RingConFwVersion; + +/// Ring-Con manufacturer calibration. +typedef struct { + s16 os_max; ///< (manu_)os_max + s16 hk_max; ///< (manu_)hk_max + s16 zero_min; ///< (manu_)zero_min + s16 zero_max; ///< (manu_)zero_max +} RingConManuCal; + +/// Ring-Con user calibration. +typedef struct { + s16 os_max; ///< (user_)os_max + s16 hk_max; ///< (user_)hk_max + s16 zero; ///< (user_)zero + RingConDataValid data_valid; ///< \ref RingConDataValid +} RingConUserCal; + +/// Polling data extracted from \ref HidbusJoyPollingReceivedData. +typedef struct { + s16 data; ///< Sensor state data. + u64 timestamp; ///< Sample timestamp. +} RingConPollingData; + +/// Ring-Con state object. +typedef struct { + bool bus_initialized; + HidbusBusHandle handle; + void* workbuf; + size_t workbuf_size; + u64 polling_last_timestamp; + u32 error_flags; + + u64 id_l, id_h; + RingConFwVersion fw_ver; + u32 flag; + s16 unk_cal; + s32 total_push_count; + + RingConManuCal manu_cal; + RingConUserCal user_cal; +} RingCon; + +/** + * @brief Creates a \ref RingCon object, and handles the various initialization for it. + * @param c \ref RingCon + * @param[in] id \ref HidControllerID. A Ring-Con must be attached to this controller. + */ +Result ringconCreate(RingCon *c, HidControllerID id); + +/** + * @brief Close a \ref RingCon. + * @param c \ref RingCon + */ +void ringconClose(RingCon *c); + +/** + * @brief Gets the error flags field. + * @param c \ref RingCon + */ +NX_CONSTEXPR u32 ringconGetErrorFlags(RingCon *c) { + return c->error_flags; +} + +/** + * @brief Gets the value of an error flag, set by \ref ringconSetErrorFlag. + * @param c \ref RingCon + * @param[in] flag \ref RingConErrorFlag + */ +NX_CONSTEXPR bool ringconGetErrorFlag(RingCon *c, RingConErrorFlag flag) { + return (c->error_flags & BIT(flag)) != 0; +} + +/** + * @brief Gets the \ref RingConFwVersion previously loaded by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] out \ref RingConFwVersion + */ +NX_CONSTEXPR RingConFwVersion ringconGetFwVersion(RingCon *c) { + return c->fw_ver; +} + +/** + * @brief Gets the Id previously loaded by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] id_l Id low. + * @param[out] id_h Id high. + */ +NX_CONSTEXPR void ringconGetId(RingCon *c, u64 *id_l, u64 *id_h) { + *id_l = c->id_l; + *id_h = c->id_h; +} + +/** + * @brief Gets the unk_cal previously loaded by \ref ringconCreate with \ref ringconReadUnkCal. Only valid when the output flag from \ref ringconCmdx00020105 is valid. + * @param c \ref RingCon + */ +NX_CONSTEXPR s16 ringconGetUnkCal(RingCon *c) { + return c->unk_cal; +} + +/** + * @brief Gets the total-push-count previously loaded by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] out total_push_count + */ +NX_CONSTEXPR s32 ringconGetTotalPushCount(RingCon *c) { + return c->total_push_count; +} + +/** + * @brief Gets the \ref RingConManuCal previously loaded by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] out \ref RingConManuCal + */ +NX_CONSTEXPR void ringconGetManuCal(RingCon *c, RingConManuCal *out) { + *out = c->manu_cal; +} + +/** + * @brief Gets the \ref RingConUserCal previously loaded by \ref ringconCreate. + * @note The Ring-Con UserCal doesn't seem to be calibrated normally? + * @param c \ref RingCon + * @param[out] out \ref RingConUserCal + */ +NX_CONSTEXPR void ringconGetUserCal(RingCon *c, RingConUserCal *out) { + *out = c->user_cal; +} + +/** + * @brief Updates the \ref RingConUserCal. + * @note The input \ref RingConUserCal is used with \ref ringconWriteUserCal, and the output from \ref ringconReadUserCal is verified with the input \ref RingConUserCal. This does not update the \ref RingConUserCal returned by \ref ringconGetUserCal. + * @note The Ring-Con UserCal doesn't seem to be calibrated normally? + * @param c \ref RingCon + * @param[in] cal \ref RingConUserCal + */ +Result ringconUpdateUserCal(RingCon *c, RingConUserCal cal); + +/** + * @brief Reads the \ref RingConFwVersion. + * @note This is used internally by \ref ringconCreate. Normally you should use \ref ringconGetFwVersion instead. + * @param c \ref RingCon + * @param[out] out \ref RingConFwVersion + */ +Result ringconReadFwVersion(RingCon *c, RingConFwVersion *out); + +/** + * @brief Reads the Id. + * @note This is used internally by \ref ringconCreate. Normally you should use \ref ringconGetId instead. + * @param c \ref RingCon + * @param[out] id_l Id low. + * @param[out] id_h Id high. + */ +Result ringconReadId(RingCon *c, u64 *id_l, u64 *id_h); + +/** + * @brief Gets the \ref RingConPollingData. Only returns entries which are new since the last time this was called (or if not previously called, all available entries up to count). + * @param c \ref RingCon + * @param[out] out Output array of \ref RingConPollingData. Entry order is newest -> oldest. + * @param[in] count Total size of the out array in entries, max value is 0x9. + * @param[out] total_out Total output entries. + */ +Result ringconGetPollingData(RingCon *c, RingConPollingData *out, s32 count, s32 *total_out); + +/** + * @brief Uses cmd 0x00020105. + * @note Used internally by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] out Output value. + */ +Result ringconCmdx00020105(RingCon *c, u32 *out); + +/** + * @brief Reads the \ref RingConManuCal. + * @note Used internally by \ref ringconCreate and \ref ringconReadUnkCal. + * @param c \ref RingCon + * @param[out] out \ref RingConManuCal + */ +Result ringconReadManuCal(RingCon *c, RingConManuCal *out); + +/** + * @brief Gets the unknown value derived from the output of cmd 0x00020504 and \ref ringconReadManuCal. + * @note Used internally by \ref ringconCreate. + * @param c \ref RingCon + * @param[out] out Output value. + */ +Result ringconReadUnkCal(RingCon *c, s16 *out); + +/** + * @brief Reads the \ref RingConUserCal. + * @note Used internally by \ref ringconCreate and \ref ringconUpdateUserCal. + * @param c \ref RingCon + * @param[out] out \ref RingConUserCal + */ +Result ringconReadUserCal(RingCon *c, RingConUserCal *out); + +/** + * @brief Reads the rep-count for Multitask Mode. + * @param c \ref RingCon + * @param[out] out Output value. Official sw using this clamps the output to range 0-500. + * @param[out] data_valid \ref RingConDataValid + */ +Result ringconReadRepCount(RingCon *c, s32 *out, RingConDataValid *data_valid); + +/** + * @brief Reads the total-push-count, for Multitask Mode. + * @note Used internally by \ref ringconCreate. Normally \ref ringconGetTotalPushCount should be used instead. + * @param c \ref RingCon + * @param[out] out Output value. + * @param[out] data_valid \ref RingConDataValid + */ +Result ringconReadTotalPushCount(RingCon *c, s32 *out, RingConDataValid *data_valid); + +/** + * @brief This resets the value returned by \ref ringconReadRepCount to 0. + * @param c \ref RingCon + */ +Result ringconResetRepCount(RingCon *c); + +/** + * @brief Writes the \ref RingConUserCal. + * @note Used internally by \ref ringconUpdateUserCal. + * @param c \ref RingCon + * @param[in] cal \ref RingConUserCal + */ +Result ringconWriteUserCal(RingCon *c, RingConUserCal cal); + diff --git a/nx/source/runtime/ringcon.c b/nx/source/runtime/ringcon.c new file mode 100644 index 00000000..6cf85849 --- /dev/null +++ b/nx/source/runtime/ringcon.c @@ -0,0 +1,623 @@ +#include +#include +#include "runtime/ringcon.h" +#include "arm/counter.h" + +static Result _ringconSetup(RingCon *c); + +/** + * \file + * Functions and types for CRC checks. + * + * Generated on Fri Mar 13 12:10:23 2020 + * by pycrc v0.9.2, https://pycrc.org + * using the configuration: + * - Width = 8 + * - Poly = 0x8d + * - XorIn = 0x00 + * - ReflectIn = False + * - XorOut = 0x00 + * - ReflectOut = False + * - Algorithm = bit-by-bit-fast + */ +// Generated output from pycrc was slightly adjusted. + +static u8 crc_update(u8 crc, const void *data, size_t data_len) { + const u8 *d = (const u8*)data; + u32 i; + bool bit; + u8 c; + + while (data_len--) { + c = *d++; + for (i = 0x80; i > 0; i >>= 1) { + bit = crc & 0x80; + if (c & i) { + bit = !bit; + } + crc <<= 1; + if (bit) { + crc ^= 0x8d; + } + } + crc &= 0xff; + } + return crc & 0xff; +} + +static void _ringconSetErrorFlag(RingCon *c, RingConErrorFlag flag, bool value) { + if (value) + c->error_flags |= BIT(flag); + else + c->error_flags &= ~BIT(flag); +} + +Result ringconCreate(RingCon *c, HidControllerID id) { + Result rc=0; + bool handleflag=0; + HidbusBusType bus_type; + u32 type = hidGetControllerType(id); + u32 cmd = 0x00020101; + + memset(c, 0, sizeof(*c)); + + c->workbuf_size = 0x1000; + c->workbuf = memalign(0x1000, c->workbuf_size); + if (c->workbuf == NULL) + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + else + memset(c->workbuf, 0, c->workbuf_size); + + if (R_SUCCEEDED(rc)) { + if (type & TYPE_JOYCON_LEFT) + bus_type = HidbusBusType_JoyLeftRail; + else if (type & (TYPE_JOYCON_RIGHT | TYPE_JOYCON_PAIR)) + bus_type = HidbusBusType_JoyRightRail; + else + rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + if (R_SUCCEEDED(rc)) rc = hidbusGetBusHandle(&c->handle, &handleflag, id, bus_type); + if (R_SUCCEEDED(rc) && !handleflag) rc = MAKERESULT(Module_Libnx, LibnxError_NotFound); + + if (R_SUCCEEDED(rc)) rc = hidbusInitialize(c->handle); + if (R_SUCCEEDED(rc)) c->bus_initialized = true; + + if (R_SUCCEEDED(rc)) rc = hidbusEnableExternalDevice(c->handle, true, 0x20); + if (R_SUCCEEDED(rc)) rc = hidbusEnableJoyPollingReceiveMode(c->handle, &cmd, sizeof(cmd), c->workbuf, c->workbuf_size, HidbusJoyPollingMode_JoyEnableSixAxisPollingData); + + if (R_SUCCEEDED(rc)) rc = _ringconSetup(c); + + if (R_FAILED(rc)) ringconClose(c); + + return rc; +} + +void ringconClose(RingCon *c) { + if (c->bus_initialized) { + // Official sw uses hidbusDisableJoyPollingReceiveMode here, but that's redundant since hidbusEnableExternalDevice with flag=false uses that automatically. + hidbusEnableExternalDevice(c->handle, false, 0x20); + hidbusFinalize(c->handle); + } + + free(c->workbuf); + c->workbuf = 0; +} + +static Result _ringconSetup(RingCon *c) { + Result rc=0, rc2=0; + u64 prev_tick=0; + u64 tick_freq = armGetSystemTickFreq(); // Used for 1-second timeout. + + while (R_FAILED(rc2 = ringconCmdx00020105(c, &c->flag))) { + if (R_VALUE(rc2) != MAKERESULT(218, 7)) return rc; + prev_tick = armGetSystemTick(); + while (R_FAILED(rc = ringconReadFwVersion(c, &c->fw_ver))) { + if (armGetSystemTick() - prev_tick >= tick_freq) return rc; + } + if (c->fw_ver.fw_main_ver > 0x1f) continue; + c->flag = 1; + break; + } + if (R_SUCCEEDED(rc2)) { + if (c->flag == 1) { + prev_tick = armGetSystemTick(); + do { + rc2 = ringconReadFwVersion(c, &c->fw_ver); + } while (R_FAILED(rc2) && armGetSystemTick() - prev_tick < tick_freq); + + prev_tick = armGetSystemTick(); + while (R_FAILED(rc = ringconReadUnkCal(c, &c->unk_cal))) { + if (armGetSystemTick() - prev_tick >= tick_freq) { + return R_SUCCEEDED(rc2) ? rc : rc2; + } + } + if (R_FAILED(rc2)) return rc2; + } + } + + s32 total_push_count = 0; + RingConDataValid data_valid = RingConDataValid_Ok; + rc = ringconReadTotalPushCount(c, &total_push_count, &data_valid); + if (R_SUCCEEDED(rc) && data_valid == RingConDataValid_Ok) c->total_push_count = total_push_count; + + // Official sw has a success_flag, where the app just write rc=0 to state returns here, when it's not set. But it's not actually possible to reach this point with that flag being clear, so we won't impl that. + + if (c->flag != 1) { + _ringconSetErrorFlag(c, RingConErrorFlag_BadFlag, true); + return MAKERESULT(Module_Libnx, LibnxError_IoError); + } + + rc = ringconReadId(c, &c->id_l, &c->id_h); + if (R_FAILED(rc)) return rc; + + for (u32 i=0; i<2; i++) { + rc = ringconReadManuCal(c, &c->manu_cal); + if (R_SUCCEEDED(rc)) + break; + else if (i==1) + return rc; + } + + for (u32 i=0; i<2; i++) { + rc = ringconReadUserCal(c, &c->user_cal); + if (R_SUCCEEDED(rc)) + break; + else if (i==1) + return rc; + } + + s16 manu_os_max = c->manu_cal.os_max; + s16 manu_hk_max = c->manu_cal.hk_max; + s16 manu_zero_min = c->manu_cal.zero_min; + if (manu_os_max <= manu_hk_max || manu_zero_min <= manu_hk_max || c->manu_cal.zero_max <= manu_zero_min || manu_os_max <= manu_zero_min) { + _ringconSetErrorFlag(c, RingConErrorFlag_BadManuCal, true); + return MAKERESULT(218, 7); + } + + s16 user_os_max = c->user_cal.os_max; + if (c->user_cal.data_valid == RingConDataValid_CRC || (user_os_max != RINGCON_CAL_MAGIC && user_os_max <= c->user_cal.hk_max)) { + _ringconSetErrorFlag(c, RingConErrorFlag_BadUserCal, true); + return MAKERESULT(Module_Libnx, LibnxError_IoError); + } + + return 0; +} + +Result ringconUpdateUserCal(RingCon *c, RingConUserCal cal) { + Result rc=0; + RingConUserCal tmp_cal={0}; + + _ringconSetErrorFlag(c, RingConErrorFlag_BadUserCalUpdate, false); + + for (u32 i=0; i<2; i++) { + rc = ringconWriteUserCal(c, cal); + if (R_SUCCEEDED(rc)) { + rc = ringconReadUserCal(c, &tmp_cal); + if (R_SUCCEEDED(rc) && tmp_cal.data_valid == RingConDataValid_Ok && tmp_cal.os_max == cal.os_max && tmp_cal.hk_max == cal.hk_max && tmp_cal.zero == cal.zero) return 0; + } + } + if (R_FAILED(rc)) return rc; + + _ringconSetErrorFlag(c, RingConErrorFlag_BadUserCalUpdate, true); + return MAKERESULT(Module_Libnx, LibnxError_IoError); +} + +Result ringconReadFwVersion(RingCon *c, RingConFwVersion *out) { + Result rc=0; + u32 cmd = 0x00020000; + u64 out_size=0; + + struct { + u8 status; + u8 pad[0x3]; + u8 fw_sub_ver; + u8 fw_main_ver; + u8 pad2[0x2]; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + out->fw_main_ver = reply.fw_main_ver; + out->fw_sub_ver = reply.fw_sub_ver; + } + + return rc; +} + +Result ringconReadId(RingCon *c, u64 *id_l, u64 *id_h) { + Result rc=0; + u32 cmd = 0x00020100; + u64 out_size=0; + + struct { + u8 status; + u8 pad[0x3]; + + struct { + struct { + u32 data_x0; + u16 data_x4; + } PACKED id_l; + + struct { + u32 data_x0; + u16 data_x4; + } PACKED id_h; + } id; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + *id_l = reply.id.id_l.data_x0 | ((u64)reply.id.id_l.data_x4<<32); + *id_h = reply.id.id_h.data_x0 | ((u64)reply.id.id_h.data_x4<<32); + } + + return rc; +} + +Result ringconGetPollingData(RingCon *c, RingConPollingData *out, s32 count, s32 *total_out) { + Result rc=0; + HidbusJoyPollingReceivedData recv_data[0x9]; + + rc = hidbusGetJoyPollingReceivedData(c->handle, recv_data, 0x9); + if (R_SUCCEEDED(rc)) { + u64 tmp = recv_data[0].timestamp - c->polling_last_timestamp; + if (tmp > 0x9) tmp = 0x9; + if (count > tmp) count = tmp; + c->polling_last_timestamp = recv_data[0].timestamp; + + for (s32 i=0; istatus != 0) return MAKERESULT(218, 7); + + out[i].data = reply->data; + out[i].timestamp = recv_data[i].timestamp; + } + } + + if (R_SUCCEEDED(rc) && count) *total_out = count; + + return rc; +} + +Result ringconCmdx00020105(RingCon *c, u32 *out) { + Result rc=0; + u32 cmd = 0x00020105; + u64 out_size=0; + + struct { + u8 status; + u8 pad[0x3]; + u8 data; + u8 pad2[0x3]; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) *out = reply.data; + + return rc; +} + +Result ringconReadManuCal(RingCon *c, RingConManuCal *out) { + Result rc=0; + u32 cmd=0; + u64 out_size=0; + RingConFwVersion ver={0}; + + rc = ringconReadFwVersion(c, &ver); + if (R_FAILED(rc)) return rc; + + if (ver.fw_main_ver >= 0x20) { + struct { + u8 status; + u8 pad[0x3]; + s16 os_max; + u16 pad1; + s16 hk_max; + u16 pad2; + s16 zero_min; + u16 pad3; + s16 zero_max; + u16 pad4; + } reply; + + cmd = 0x00020A04; + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + out->os_max = reply.os_max; + out->hk_max = reply.hk_max; + out->zero_min = reply.zero_min; + out->zero_max = reply.zero_max; + } + } + else { + struct { + u8 status; + u8 pad[0x3]; + s16 data; + u8 pad2[0x2]; + } reply; + + if (R_SUCCEEDED(rc)) { + cmd = 0x00020104; + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) out->os_max = reply.data; + } + + if (R_SUCCEEDED(rc)) { + cmd = 0x00020204; + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) out->hk_max = reply.data; + } + + if (R_SUCCEEDED(rc)) { + cmd = 0x00020404; + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) out->zero_max = reply.data; + } + + if (R_SUCCEEDED(rc)) { + cmd = 0x00020304; + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) out->zero_min = reply.data; + } + } + + return rc; +} + +Result ringconReadUnkCal(RingCon *c, s16 *out) { + Result rc=0; + u32 cmd = 0x00020504; + u64 out_size=0; + RingConManuCal cal={0}; + + struct { + u8 status; + u8 pad[0x3]; + s16 data; + u8 pad2[0x2]; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_FAILED(rc)) return rc; + + rc = ringconReadManuCal(c, &cal); + if (R_SUCCEEDED(rc)) { + s16 tmp = cal.hk_max - cal.os_max; + if (tmp < 0) tmp++; + *out = reply.data + (((u16)tmp)>>1); + } + + return 0; // Official sw ignores error from the above ringconReadManuCal call, besides the above block. +} + +static Result _ringconReadUserCalOld(RingCon *c, u32 cmd, s16 *out, RingConDataValid *data_valid) { + Result rc=0; + u64 out_size=0; + + struct { + u8 status; + u8 pad[0x3]; + s16 data; + u8 crc; + u8 pad2; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + if (crc_update(0, &reply.data, sizeof(reply.data)) != reply.crc) *data_valid = RingConDataValid_CRC; + else { + *data_valid = RingConDataValid_Ok; + *out = reply.data; + } + } + + return rc; +} + +Result ringconReadUserCal(RingCon *c, RingConUserCal *out) { + Result rc=0; + u32 cmd = 0x00021A04; + u64 out_size=0; + RingConFwVersion ver={0}; + + out->data_valid = RingConDataValid_Ok; + + rc = ringconReadFwVersion(c, &ver); + if (R_FAILED(rc)) return rc; + + if (ver.fw_main_ver >= 0x20) { + struct { + u8 status; + u8 pad[0x3]; + s16 os_max; + u8 os_max_crc; + u8 pad1; + s16 hk_max; + u8 hk_max_crc; + u8 pad2; + s16 zero; + u8 zero_crc; + u8 pad4; + u8 unused[0x4]; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + if ((crc_update(0, &reply.os_max, sizeof(reply.os_max)) != reply.os_max_crc) || (crc_update(0, &reply.hk_max, sizeof(reply.hk_max)) != reply.hk_max_crc) || (crc_update(0, &reply.zero, sizeof(reply.zero)) != reply.zero_crc)) + out->data_valid = RingConDataValid_CRC; + else { + out->data_valid = RingConDataValid_Ok; + out->os_max = reply.os_max; + out->hk_max = reply.hk_max; + out->zero = reply.zero; + } + } + } + else { + // Official sw doesn't check the data_valid output from these. + rc = _ringconReadUserCalOld(c, 0x00021104 , &out->os_max, &out->data_valid); + if (R_SUCCEEDED(rc)) rc = _ringconReadUserCalOld(c, 0x00021204, &out->hk_max, &out->data_valid); + if (R_SUCCEEDED(rc)) rc = _ringconReadUserCalOld(c, 0x00021304, &out->zero, &out->data_valid); + } + + if (R_SUCCEEDED(rc)) { + if (out->os_max == RINGCON_CAL_MAGIC || out->hk_max == RINGCON_CAL_MAGIC || out->zero == RINGCON_CAL_MAGIC) + out->data_valid = RingConDataValid_Cal; + } + + return rc; +} + +static Result _ringconGet3ByteOut(RingCon *c, u32 cmd, s32 *out, RingConDataValid *data_valid) { + Result rc=0; + u64 out_size=0; + u8 data[0x4]={0}; + + struct { + u8 status; + u8 pad[0x3]; + u8 data[0x3]; + u8 crc; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7); + if (R_SUCCEEDED(rc)) { + memcpy(data, reply.data, sizeof(reply.data)); + if (crc_update(0, data, sizeof(data)) != reply.crc) *data_valid = RingConDataValid_CRC; // Official sw has this field value inverted with this func, but whatever. + else { + *data_valid = RingConDataValid_Ok; + *out = data[0x0] | (data[0x1]<<8) | (data[0x2]<<16); + } + } + + return rc; +} + +Result ringconReadRepCount(RingCon *c, s32 *out, RingConDataValid *data_valid) { + return _ringconGet3ByteOut(c, 0x00023104, out, data_valid); +} + +Result ringconReadTotalPushCount(RingCon *c, s32 *out, RingConDataValid *data_valid) { + return _ringconGet3ByteOut(c, 0x00023204, out, data_valid); +} + +Result ringconResetRepCount(RingCon *c) { + Result rc=0; + u64 cmd = 0x04013104; + u64 out_size=0; + + struct { + u8 status; + u8 pad[0x3]; + u8 unused[0x4]; + } reply; + + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && (out_size != 0x4 || reply.status != 0)) rc = MAKERESULT(218, 7); // Official sw uses value 0x4 for the out_size check, instead of the usual sizeof(reply) value. + + return rc; +} + +Result ringconWriteUserCal(RingCon *c, RingConUserCal cal) { + Result rc=0; + u64 out_size=0; + RingConFwVersion ver={0}; + + struct { + u32 cmd; + + struct { + s16 data; + u8 crc; + u8 pad; + } os_max; + + struct { + s16 data; + u8 crc; + u8 pad; + } hk_max; + + struct { + s16 data; + u8 crc; + u8 pad; + } zero; + + u8 unused[0x4]; + } cmd = {.cmd = 0x10011A04}; + + struct { + u8 status; + u8 pad[0x3]; + } reply; + + cmd.os_max.data = cal.os_max; + cmd.os_max.crc = crc_update(0, &cmd.os_max.data, sizeof(cmd.os_max.data)); + + cmd.hk_max.data = cal.hk_max; + cmd.hk_max.crc = crc_update(0, &cmd.hk_max.data, sizeof(cmd.hk_max.data)); + + cmd.zero.data = cal.zero; + cmd.zero.crc = crc_update(0, &cmd.zero.data, sizeof(cmd.zero.data)); + + rc = ringconReadFwVersion(c, &ver); + if (R_FAILED(rc)) return rc; + + // Official sw doesn't check the output_size for these. + if (ver.fw_main_ver >= 0x20) { + rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7); + } + else { + struct { + u32 cmd; + u8 data[0x4]; + } old_cmd; + + if (R_SUCCEEDED(rc)) { + old_cmd.cmd = 0x04011104; + memcpy(old_cmd.data, &cmd.os_max, sizeof(cmd.os_max)); + rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7); + } + + if (R_SUCCEEDED(rc)) { + old_cmd.cmd = 0x04011204; + memcpy(old_cmd.data, &cmd.hk_max, sizeof(cmd.hk_max)); + rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7); + } + + if (R_SUCCEEDED(rc)) { + old_cmd.cmd = 0x04011304; + memcpy(old_cmd.data, &cmd.zero, sizeof(cmd.zero)); + rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size); + if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7); + } + } + + return rc; +} +