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