Ring-Con
This page documents how the Ring Fit Adventure game uses hidbus for using the Ring-Con ExternalDevice, which attaches to Joy-Cons.
After EnableExternalDevice is used successfully during initialization, EnableJoyPollingReceiveMode(flag=true) is used with command #0x00020101. For cleanup, DisableJoyPollingReceiveMode is used then on success EnableExternalDevice(flag=false) is used.
Commands (input data for EnableJoyPollingReceiveMode / SendAndReceive) start with the u32 cmd. Data in the replies are at least 0x4-bytes, with fields being 4-byte aligned. Reply+0 is the u8 status. When the output size doesn't match the expected size, or status is non-zero, error 0xEDA is returned. When status is non-zero, a func is called with the status value which updates global state.
The status field is not listed below since all replies have it.
Note that the app saves various PlayReports, this includes various Ring-Con data (which includes field-name strings).
CRC
Some commands/replies use CRC(s) for validating the previous N-bytes prior to the CRC. When the CRC is invalid, the app sets an output flag indicating invalid CRC and skips writing output-data to the output-param.
This CRC doesn't use Xor(In/Out)/Reflect(In/Out). The polynomial is 0x8D. This is CRC8.
Commands
Input cmd u32 | Input cmd size | Reply size |
---|---|---|
#0x00020000 | 0x4 | 0x8 |
#0x00020100 | 0x4 | 0x10 |
#0x00020101 | 0x4 | 0x8 |
#0x00020104 | 0x4 | 0x8 |
#0x00020105 | 0x4 | 0x8 |
#0x00020204 | 0x4 | 0x8 |
#0x00020304 | 0x4 | 0x8 |
#0x00020404 | 0x4 | 0x8 |
#0x00020504 | 0x4 | 0x8 |
#0x00020A04 | 0x4 | 0x14 |
#0x00021104 | 0x4 | 0x8 |
#0x00021204 | 0x4 | 0x8 |
#0x00021304 | 0x4 | 0x8 |
#0x00021A04 | 0x4 | 0x14 |
#0x00023104 | 0x4 | 0x8 |
#0x00023204 | 0x4 | 0x8 |
#0x04013104 | 0x8 | 0x8 |
#0x04011104 | 0x8 | 0x4 |
#0x04011204 | 0x8 | 0x4 |
#0x04011304 | 0x8 | 0x4 |
#0x10011A04 | 0x14 | 0x4 |
0x00020000
The two u8s at reply+0x4 are copied to an output struct, with the order swapped in the output struct. This is also used directly by various funcs, without the struct.
Reply u8 +0x4 is "fw_sub_ver", +0x5 is "fw_main_ver".
This gets the firmware version. Funcs using this do: if (fw_main_ver >= 0x20) {} else {}
0x00020100
This gets the ID. The first 0x6-bytes from the below output is "id_l", the remaining 0x6-bytes are "id_h". During PlayReport saving the cached ID is copied to u64s, which are then used with the PlayReport.
Reply:
Offset | Size | Description |
---|---|---|
0x4 | 0xC | This is copied to an output buffer. |
0x00020101
This is used with EnableJoyPollingReceiveMode/GetJoyPollingReceivedData. Reply+0x4 is an u16 which is copied to an output struct, the timestamp is also copied. This seems to be the current sensor state.
0x00020104
Reply+0x4 is the output 16bit value.
See #0x00020A04.
0x00020105
Reply+0x4 is the output u8, which copied to an output u32.
0x00020204
Reply+0x4 is the output 16bit value.
See #0x00020A04.
0x00020304
Reply+0x4 is the output 16bit value.
See #0x00020A04.
0x00020404
Reply+0x4 is the output 16bit value.
See #0x00020A04.
0x00020504
Reply+0x4 is a s16. After successfully using this cmd, this calls the func for #0x00020A04. When that's successful, this does the following with the output from that func and the earlier reply data: s16 tmp = s16 manu_hk_max - s16 manu_os_max; if (tmp < 0) tmp++; *16bit_outparam = replydata + (tmp>>1);
Then this returns 0, regardless of whether #0x00020A04 was successful.
0x00020A04
The app uses cmd #0x00020000 first. When fw_main_ver from that cmd is >=0x20, it proceeds to use cmd 0x00020A04 then returns. Otherwise, the following cmds are used with the output being copied to the output struct, then returns: #0x00020104 (output_struct+0x0), #0x00020204 (output_struct+0x2), #0x00020404 (output_struct+0x6), #0x00020304 (output_struct+0x4).
Reply:
Offset | Size | Description |
---|---|---|
0x4 | 0x2 | "manu_os_max". s16 value copied to output_struct+0x0. |
0x8 | 0x2 | "manu_hk_max". s16 value copied to output_struct+0x2. |
0xC | 0x2 | "manu_zero_min". s16 value copied to output_struct+0x4. |
0x10 | 0x2 | "manu_zero_max". s16 value copied to output_struct+0x6. |
0x00021104
This is implemented in the app with the cmd to use being specified via an input param.
See #0x00021A04.
Reply:
Offset | Size | Description |
---|---|---|
0x4 | 0x2 | Data, copied to an output struct as an 16bit value. |
0x6 | 0x1 | #CRC over the previous 0x2-bytes. |
0x00021204
See #0x00021104.
0x00021304
See #0x00021104.
0x00021A04
The app uses cmd #0x00020000 first. When fw_main_ver from that cmd is >=0x20, it proceeds to use cmd 0x00021A04. Otherwise, the following cmds are used, with the output being written to the output_struct: #0x00021104 (output_struct+0x0), #0x00021204 (output_struct+0x2), #0x00021304 (output_struct+0x4).
Then, if any of the 16bit output fields in output_struct are set to 0xCAFE, a flag is set to 0x2 (same field used for invalid-CRC, which uses value 0x1 for that). Then this returns 0. This field is "user_state".
This reads the user calibration. This is normally (?) not calibrated (fields are set to 0xCAFE).
Reply:
Offset | Size | Description |
---|---|---|
0x4 | 0x2 | "user_os_max"/"os_max". s16 value copied to output_struct+0x0. |
0x6 | 0x1 | #CRC over the previous 0x2-bytes. |
0x7 | 0x1 | Padding. |
0x8 | 0x2 | "user_hk_max"/"hk_max". s16 value copied to output_struct+0x2. |
0xA | 0x1 | #CRC over the previous 0x2-bytes. |
0xB | 0x1 | Padding. |
0xC | 0x2 | "user_zero"/"zero". s16 value copied to output_struct+0x4. |
0xE | 0x1 | #CRC over the previous 0x2-bytes. |
0xF | 0x1 | Padding. |
0x00023104
The code which calls the func implementing this clamps the output value to range 0-500, then copies it elsewhere.
This gets the rep-count for Multitask-Mode.
Reply:
Offset | Size | Description |
---|---|---|
0x4 | 0x3 | Data, copied to an output struct as a s32. |
0x7 | 0x1 | #CRC over the previous 0x3-bytes, followed a value-0 byte. |
0x00023204
The func implementing this in the app is identical to #0x00023104 except for the cmd u32. The output field is "total_push_count". This is only updated in Multitask Mode.
The code calling this func copies the output into a global field, when it's valid.
0x04013104
The data at cmd+0x4 is all-zero. The app doesn't use reply data here besides the status.
Unlike the other cmds, this checks for output_size==0x4, instead of {output buffer size} (it's unknown whether this is intended).
This resets the value returned by #0x00023104 to 0.
0x04011104
Cmd+0x4 is the 4-byte input. The output_size is not checked.
See #0x10011A04.
0x04011204
Cmd+0x4 is the 4-byte input. The output_size is not checked.
See #0x10011A04.
0x04011304
Cmd+0x4 is the 4-byte input. The output_size is not checked.
See #0x10011A04.
0x10011A04
The app uses cmd #0x00020000 first. When fw_main_ver from that cmd is >=0x20, it proceeds to use cmd 0x10011A04 then returns. Otherwise, the following cmds are used, with the same 4-byte input listed in the below table: #0x04011104 (below cmd+0x4), #0x04011204 (below cmd+0x8), #0x04011304 (below cmd+0xC).
The app doesn't use reply data here besides the status, and the output_size is not checked.
The code calling this func saves PlayReports with EventId "write_user_cal". This is the Write version of #0x00021A04, the s16s here are the same as #0x00021A04.
The code calling this func will first call this func, then on success a PlayReport is saved. Then #0x00021A04 is used, on success if the output matches the data used for 0x10011A04 and the is_valid flag is 0, this returns 0. Otherwise: the func for 0x10011A04 is used again, handling error on failure. Then a PlayReport is saved again. #0x00021A04 is used, with error handling on failure. Then the same validation checks run again. If it didn't return at this point, error handling runs.
Cmd:
Offset | Size | Description |
---|---|---|
0x4 | 0x2 | Func input param & 0xFFFF |
0x6 | 0x1 | #CRC over the previous 0x2-bytes. |
0x7 | 0x1 | Padding. |
0x8 | 0x2 | (Func input param >> 16) & 0xFFFF |
0xA | 0x1 | #CRC over the previous 0x2-bytes. |
0xB | 0x1 | Padding. |
0xC | 0x2 | (Func input param >> 32) & 0xFFFF |
0xE | 0x1 | #CRC over the previous 0x2-bytes. |
0xF | 0x1 | Padding. |
0x10 | 0x4 | Zeros |