Mario Kart Live: Home Circuit: Difference between revisions
→Hardware: I think I identified the IMU |
→Type 0x01: Battery Status: First byte actually indicates cable status |
||
(8 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
This page documents the Mario Kart Live: Home Circuit game. | This page documents the Mario Kart Live: Home Circuit game. | ||
Communication with the kart is done directly over local-WLAN via an access point that the game sets up using [[ | Communication with the kart is done directly over local-WLAN via an access point that the game sets up using [[LDN services|lp2p:app]], making it the first title on retail to use lp2p. The underlying device management uses [[RCD]]. The RCD implementation is in the main-codebin itself, without symbols - however there are strings for this. | ||
This is also the first known title on retail which uses stack cookies. This is used by main-codebin, the ssp functionality in sdknso is still not used other than being called from an initialization func. This is implemented in the main-codebin as follows: | This is also the first known title on retail which uses stack cookies. This is used by main-codebin, the ssp functionality in sdknso is still not used other than being called from an initialization func. This is implemented in the main-codebin as follows: | ||
* The global u64 __stack_chk_guard is loaded then saved immediately before {first saved register} on stack, during func entry. During func exit, the global u64 is compared with the cookie on stack, it will call __stack_chk_fail on mismatch. __stack_chk_fail just executes an undefined instruction to trigger a crash. | * The global u64 __stack_chk_guard is loaded then saved immediately before {first saved register} on stack, during func entry. During func exit, the global u64 is compared with the cookie on stack, it will call __stack_chk_fail on mismatch. __stack_chk_fail just executes an undefined instruction to trigger a crash. | ||
* There is no initialization func for __stack_chk_guard, it's just a hard-coded constant: 0xDEADBEEFDEADBEEF. Since it's constant, this renders the stack cookie useless. | * There is no initialization func for __stack_chk_guard, it's just a hard-coded constant: 0xDEADBEEFDEADBEEF. Since it's constant, this renders the stack cookie useless. | ||
RomFs contains only two files: | RomFs contains only two files: | ||
* "data.zip" | * "data.zip" | ||
* "update.pua": This is the firmware update data for the Kart. This is a tar archive. The extracted archive contains "update.pui" and "pui.hash". The latter is a binary 0x100-byte file. The former is another tar archive, the content of that archive is the following: | * "update.pua": This is the firmware update data for the Kart. This is a tar archive. The extracted archive contains "update.pui" and "pui.hash". The latter is a binary 0x100-byte file. The former is another tar archive, the content of that archive is the following: | ||
Line 47: | Line 49: | ||
The only changes in the OSS for 1.0.0_1 -> 1.1.0_3 are the following (note that there are more versions between these): | The only changes in the OSS for 1.0.0_1 -> 1.1.0_3 are the following (note that there are more versions between these): | ||
* The following archives were updated: linux-kernel, PsdDriver, rtl8188eu, uboot. | * The following archives were updated: linux-kernel, PsdDriver, rtl8188eu, uboot. | ||
* In the PsdDriver source, the line-ending at the start of various source files was updated. | * In the PsdDriver source, the line-ending at the start of various source files was updated. | ||
Line 58: | Line 61: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Component | |||
!Manufacturer | |||
!Part | |||
!Notes | |||
|- | |- | ||
|SoC | |||
|Realtek | |||
|RTD1195 (PB) | |||
|Contains hardware H.264 encoder | |||
| SoC | |||
| Realtek | |||
| RTD1195 (PB) | |||
| Contains hardware H.264 encoder | |||
|- | |- | ||
| DDR3 RAM | |DDR3 RAM | ||
| Winbond | |Winbond | ||
| W631GG6MB-15 | |W631GG6MB-15 | ||
| 128 MiB | |128 MiB | ||
|- | |- | ||
| NAND Flash | |NAND Flash | ||
| Winbond | |Winbond | ||
| W29N01HVSINA | |W29N01HVSINA | ||
| 128 MiB | |128 MiB | ||
|- | |- | ||
| WNIC | |WNIC | ||
| Realtek | |Realtek | ||
| RTL8188E | |RTL8188E | ||
| Connected to SoC via USB | |Connected to SoC via USB | ||
|- | |- | ||
| MCU | |MCU | ||
| STMicroelectronics | |STMicroelectronics | ||
| STM32F | |STM32F | ||
| Responsible for real-time steering and throttle control loops, six-axis IMU acquisition, GPIO, battery status | |Responsible for real-time steering and throttle control loops, six-axis IMU acquisition, GPIO, battery status | ||
|- | |- | ||
| IMU | |IMU | ||
| STMicroelectronics | |STMicroelectronics | ||
| LSM6DSL | |LSM6DSL | ||
| Unconfirmed. Mounted "upside-down" on the PCB: +X=right +Y=back +Z=down. Sensitivities: 4.375 m°/s, 0.244 mg. | |Unconfirmed. Mounted "upside-down" on the PCB: +X=right +Y=back +Z=down. Sensitivities: 4.375 m°/s, 0.244 mg. | ||
|- | |- | ||
| Li-ion pouch cell | |Li-ion pouch cell | ||
| Nintendo branded; OEM unknown | |Nintendo branded; OEM unknown | ||
| HAC-038 | |HAC-038 | ||
| 3.7V nominal; 1750 mAh capacity | |3.7V nominal; 1750 mAh capacity | ||
|- | |- | ||
| Camera | |Camera | ||
| Unknown | |Unknown | ||
| Unknown | |Unknown | ||
| Possibly connected via MIPI CSI | |Possibly connected via MIPI CSI | ||
|} | |} | ||
Line 115: | Line 117: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x0 | |||
|0x10 | |||
|Pairing seed. LP2P PSK is SHA256(seed) | |||
|- | |- | ||
|0x10 | |||
|0x20 | |||
|Pairing SSID. Remaining space is filled with zeros. | |||
|- | |- | ||
| | |0x30 | ||
|0x2 | |||
|Pairing channel. Encoded little-endian. Usually 0. | |||
|- | |- | ||
| | |0x32 | ||
| | |0xC | ||
|Padding bytes; zero. | |||
|} | |} | ||
Line 133: | Line 142: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
|+ Summary of ports | |+Summary of ports | ||
!Protocol | |||
!Endpoint | |||
!Port | |||
!Description | |||
![[RCD]] service ID | |||
|- | |- | ||
! | ! colspan="5" |Management | ||
|- | |- | ||
| rowspan="5" |TCP | |||
| rowspan="2" |Switch | |||
|5201 | |||
|[[RCD#Handshake protocol|RCD handshake]] service (pairing only) | |||
|0x0001 | |||
|- | |- | ||
| | |5202 | ||
|RCD handshake service (non-pairing only) | |||
|0x0001 | |||
| 0x0001 | |||
|- | |- | ||
| | | rowspan="3" |Kart | ||
| RCD | |5103 | ||
| | |"Fuji Control" RCD service | ||
|0x0100 | |||
|- | |- | ||
| | |5106 | ||
|"Fuji Pairing" RCD service (pairing only) | |||
| "Fuji | |0x0102 | ||
| | |||
|- | |- | ||
| | |5107 | ||
| " | |Unknown ("Event"?) RCD service | ||
| | |0x0103 | ||
|- | |- | ||
| | |UDP | ||
| | |Kart | ||
| | |5004 | ||
|Time synchronization | |||
|N/A | |||
|- | |- | ||
| | ! colspan="5" |Video | ||
|- | |- | ||
|UDP | |||
| rowspan="2" |Switch | |||
|5016+kartid | |||
|"LSP" video streaming | |||
|N/A | |||
|- | |- | ||
| | |TCP | ||
| | |5032+kartid | ||
|"LSP" control channel | |||
| "LSP" | |N/A (non-RCD) | ||
| N/A | |||
|- | |- | ||
! colspan="5" |Control | |||
| | |||
|- | |- | ||
| rowspan="2" |UDP | |||
|Kart | |||
|5102 | |||
|Teleoperation (throttle, steering, tail light control) | |||
|N/A | |||
|- | |- | ||
|Switch | |||
|5116+kartid | |||
|Telemetry | |||
|N/A | |||
| Switch | |||
| 5116+kartid | |||
| Telemetry | |||
| N/A | |||
|} | |} | ||
Line 215: | Line 223: | ||
Accepts a 0x40-byte payload: | Accepts a 0x40-byte payload: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x00 | |||
|0x20 | |||
|SSID, zero-terminated. ''Home Circuit'' copies this straight from GroupInfo, so uninitialized bytes may follow. | |||
|- | |- | ||
|0x20 | |||
|0x20 | |||
|LP2P PSK | |||
| 0x20 | |||
| 0x20 | |||
| LP2P PSK | |||
|} | |} | ||
Line 240: | Line 247: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x0 | |||
|0x1 | |||
|boot_major_version | |||
|- | |- | ||
| | |0x1 | ||
| 0x1 | |0x1 | ||
| | |boot_minor_version | ||
|- | |- | ||
| | |0x2 | ||
| 0x1 | |0x1 | ||
| | |system_major_version | ||
|- | |- | ||
| | |0x3 | ||
| 0x1 | |0x1 | ||
| | |system_minor_version | ||
|- | |- | ||
|0x4 | |||
|0x29 | |||
|Zero-terminated ASCII string which is a 160-bit value in hexadecimal. Appears to be some kind of SHA1. | |||
| 0x4 | |||
| 0x29 | |||
| Zero-terminated ASCII string which is a 160-bit value in hexadecimal. Appears to be some kind of SHA1. | |||
|} | |} | ||
Line 271: | Line 277: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x0 | |||
|0x80 | |||
|Parameter name, zero-padded. | |||
|- | |- | ||
| | |0x80 | ||
| | |0x2 | ||
| | |Value length | ||
|- | |- | ||
| | |0x82 | ||
| | |0xE | ||
| | |Zero padding to align to 0x10-byte boundary | ||
|- | |- | ||
|0x90 | |||
|Varies | |||
|Value to set | |||
| 0x90 | |||
| Varies | |||
| Value to set | |||
|} | |} | ||
Line 296: | Line 301: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x0 | |||
|0x2 | |||
|Fixed 0x0001; unknown purpose. | |||
|- | |- | ||
| | |0x2 | ||
| 0x2 | |0x2 | ||
| | |Telemetry (UDP) port on Switch | ||
|- | |- | ||
| | |0x4 | ||
| 0x2 | |0x2 | ||
| | |"LSP" control (TCP) port on Switch | ||
|- | |- | ||
| | |0x6 | ||
| 0x2 | |0x2 | ||
| "LSP" | |"LSP" video (UDP) port on Switch | ||
|- | |- | ||
|0x8 | |||
|0x8 | |||
|Current network time, per nn::time::StandardNetworkSystemClock::GetCurrentTime. (A Unix timestamp with one-second precision.) | |||
| 0x8 | |||
| 0x8 | |||
| Current network time, per nn::time::StandardNetworkSystemClock::GetCurrentTime. (A Unix timestamp with one-second precision.) | |||
|} | |} | ||
Line 327: | Line 331: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
! Offset | |0x00 | ||
! Size | |0x80 | ||
! Description | |Parameter name, zero-padded. | ||
|} | |||
One of the known parameters is "product_code" which includes the kart's character as 1 byte, and includes a string with the value of the kart's serial number on the bottom of the kart (white-label barcode). | |||
{| class="wikitable" border="1" | |||
!Offset | |||
!Size | |||
!Description | |||
|- | |||
|0x00 | |||
|0x80 | |||
|Fixed 0x010000; unknown purpose | |||
|- | |||
|0x3 | |||
|0x1 | |||
|Fixed 0x03; unknown purpose | |||
|- | |||
|0x7 | |||
|0x1 | |||
|Fixed 0x24; unknown purpose | |||
|- | |- | ||
| | |0xC | ||
| | |0x1 | ||
| | |Fixed 0x01; unknown purpose | ||
|- | |||
|0x11 | |||
|0x1 | |||
|Fixed 0x14; unknown purpose | |||
|- | |||
|0x20 | |||
|0x2 | |||
|Fixed 0x0100; padding | |||
|- | |||
|0x22 | |||
|0x1 | |||
|Character ID; 0x01 = Mario, 0x02 = Luigi, unknown if there are other "hidden" characters | |||
|- | |||
|0x23 | |||
|0x2 | |||
|Fixed 0x0001; padding | |||
|- | |||
|0x24 | |||
|0xF | |||
|Kart Serial Number; | |||
'''X''': Nintendo Switch Platform | |||
'''Q''': Hardware Identifier for the Mario Kart Live: Home Circuit kart. | |||
'''J'''/'''W''': Factory Location; Either '''W''': US, or '''J''': Japan | |||
'''10'''/'''14'''/'''40'''/'''70:''' Factory Number | |||
'''Last 9 Numbers''': Unit Number | |||
|} | |} | ||
==== Command 0x04: SetState ==== | ==== Command 0x04: SetState ==== | ||
Line 344: | Line 398: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
!Offset | |||
!Size | |||
!Description | |||
|- | |- | ||
|0x0 | |||
|0x1 | |||
|Drive mode. 0x01 enables driving controls (and video), 0x00 puts the kart to "sleep." | |||
|- | |- | ||
|0x1 | |||
|0xF | |||
|Zero padding. | |||
| 0x1 | |||
| 0xF | |||
| Zero padding. | |||
|} | |} | ||
Line 371: | Line 424: | ||
Expects 0x20-byte zero-padded application ID (''Home Circuit'' uses "YVCOQ00000000XFB") and retrieves data stored in the kart unique to the application. | Expects 0x20-byte zero-padded application ID (''Home Circuit'' uses "YVCOQ00000000XFB") and retrieves data stored in the kart unique to the application. | ||
=== Teleoperation === | |||
The kart is operated over UDP port 5102. This port is only open when the kart is in [[#Command 0x04: SetState|drive state]]. It expects packets of the form: | |||
{| class="wikitable" border="1" | |||
!Offset | |||
!Size | |||
!Description | |||
|- | |||
|0x0 | |||
|0x1 | |||
|Throttle; a signed integer. -128 is full-speed reverse, +127 is full-speed forward. | |||
|- | |||
|0x1 | |||
|0x1 | |||
|Steering; a signed integer. -128 is full left, +127 is full right. | |||
|- | |||
|0x2 | |||
|0x1 | |||
|Brake light control. 0x00 turns the brake light off, 0x01 turns it on. This is independent of throttle. | |||
|- | |||
|0x3 | |||
|0x1 | |||
|Pad byte, apparently always zero. | |||
|- | |||
|0x4 | |||
|0x4? | |||
|Little-endian packet counter. Incremented by 1 for each sent packet. Unknown total size. | |||
|- | |||
|0x8? | |||
|0x18? | |||
|Zero padding to bring the total packet size to 0x20. | |||
|} | |||
''Home Circuit'' sends these packets at 30 Hz. Any rate that is at least 1 Hz will do, however. The kart will automatically zero the throttle (and only the throttle) if a valid packet is not received in a one-second period, as protection from loss of control. | |||
The counter appears to be intended as a mechanism for detecting duplicated/reordered packets, as such packets will have a counter that is not greater than the previously-accepted packet and can be quickly discarded. However, it appears to be ignored entirely by the kart. Nevertheless, it is a good idea to include it anyway. | |||
'''NOTICE:''' The throttle/steering values take effect immediately. ''Home Circuit'' applies smoothing to these values, possibly to simulate the physics of a much more massive kart. However, this smoothing may also be necessary to keep the kart from suffering undue mechanical stress. | |||
'''NOTICE:''' ''Home Circuit'' contains logic to detect when the kart is being held, and it locks out the controls in such cases. This logic is not implemented in the kart, which will eagerly follow any valid throttle/steering command given. The kart is not likely to cause injury if operated while held, but nevertheless, if you do so, it is at your own risk. | |||
=== Telemetry === | |||
The kart will send UDP packets to the Switch over a port it specifies in "connection_info" (but typically port 5116) indicating its current status. The packet type is indicated by the first byte. (The tables below ignore this first byte.) | |||
==== Type 0x01: Battery Status ==== | |||
{| class="wikitable" border="1" | |||
!Offset | |||
!Size | |||
!Description | |||
|- | |||
|0x0 | |||
|0x1 | |||
|Cable status bitmask: 0x1 = Cable connected; 0x2 = Unknown; 0x4 = Unknown | |||
|- | |||
|0x1 | |||
|0x2 | |||
|Zero padding | |||
|- | |||
|0x3 | |||
|0x1 | |||
|How many bars to show in the HUD for battery status (0x00-0x04) | |||
|- | |||
|0x4 | |||
|0x1C | |||
|Zero padding, to bring total packet length to 0x20. | |||
|} | |||
One would think this would be a good location for wireless metrics (e.g. RSSI), but this is presumably measured only on the Switch side of the link. | |||
==== Type 0x02: IMU ==== | |||
The kart contains a 6DoF IMU for measuring movement. This packet type communicates the motion status. | |||
All integers are little-endian: | |||
{| class="wikitable" border="1" | |||
!Offset | |||
!Size | |||
! colspan="2" |Description | |||
|- | |||
|0x0 | |||
|0x2 | |||
| colspan="2" |Total IMU samples, as an unsigned integer. This number includes the samples in this packet. It overflows. | |||
|- | |||
|0x2 | |||
|0x1 | |||
| colspan="2" |Flags bitfield: IGA??NNN. I/G/A=High-precision units used, ?=Unknown, possibly clipping indicators. N=Number of IMU samples. | |||
|- | |||
|0x3 | |||
|0x1 | |||
| colspan="2" |Pad byte; zero. | |||
|- | |||
|0x4 | |||
|0x4 | |||
| colspan="2" |Microsecond-precision timer, as an unsigned integer. | |||
|- | |||
|0x8 | |||
|0x10 | |||
| colspan="2" |Orientation: s32 qi, qj, qk, qr; gives the coefficients of a quaternion. Units appear to be increments of 2^-30, but quaternions should always be normalized before use. | |||
|- | |||
|0x18 | |||
|0xC | |||
|Integrator A position | |||
| rowspan="2" |s32 x, y, z; gives an estimate of total displacement since this integrator was reset. Every 6ms step: pos += vel>>12; Units: (4096*0.006)² * 2^-30 m (I=1), 78.4348 * (4096*0.006)² * 2^-30 m (I=0) | |||
|- | |||
|0x24 | |||
|0xC | |||
|Integrator B position | |||
|- | |||
|0x30 | |||
|0xC | |||
|Integrator A velocity | |||
| rowspan="2" |s32 x, y, z; gives an estimate of total change in velocity since this integrator was reset. Every 6ms step: vel += acc>>12; (where 'acc' is acceleration in 2^-30 m/s²). Units: (4096*0.006) * 2^-30 m/s (I=1), 78.4348 * (4096*0.006) * 2^-30 m/s (I=0) | |||
|- | |||
|0x3C | |||
|0xC | |||
|Integrator B velocity | |||
|- | |||
| rowspan="2" |0x48 | |||
| rowspan="2" |0xC*(NNN+1) | |||
| rowspan="2" |1-8 raw IMU samples | |||
|s16 accel_x, accel_y, accel_z; // Units: 9.81/4096 m/s² (A=1), 15.99*9.81/4096 m/s² (A=0) (9.81m/s² added to accel_z to compensate for gravity) | |||
|- | |||
|s16 ang_vel_x, ang_vel_y, ang_vel_z; // Units: 0.004375 °/s (G=1), 0.1 °/s (G=0) | |||
|} | |||
The exact rate at which these packets are sent appears to be somewhat variable. The raw IMU samples seem to be collected with a period of 6ms, and when not in drive mode, this packet always contains 8 IMU samples, for an overall period 48ms. However, when in drive mode, not all of these packets contain exactly 8 IMU samples, and the packets will arrive at an uneven rate. This is probably due to some logic in the kart that flushes the buffer early based on certain conditions. | |||
The IMU itself is mounted on the PCB "upside down" which gives a reference frame different from what one would expect: While +X does correspond to the driver's right-hand side, the +Y vector extends beyond the rear of the vehicle, and +Z extends out the bottom. All measurements are taken with respect to this reference frame. | |||
The orientation quaternion is reasonably stable, but should still only be trusted for relative measurements over short periods of time, as the quaternion can drift due to error (noise, clipping, quantization) in the IMU samples. The quaternion is initialized to 0i+0j+0k+1 once the IMU is enabled (i.e. when "connection_info" is set). | |||
The integrators are useful in an environment where packet loss is expected to be reasonably high, and so not all raw IMU samples can be reliably collected. The integrators are periodically reset (every 0x2000 samples), as they will accumulate error over time. | |||
Both integrators are reset at the same time when the IMU is enabled, but integrator A is reset at sample 0x1000 so that the resets are staggered. | |||
==== Type 0x03: Motion feedback ==== | |||
TBD | |||
= Versions = | = Versions = | ||
Line 379: | Line 575: | ||
ExeFs: | ExeFs: | ||
* Nothing changed besides the usual NPDM update. | * Nothing changed besides the usual NPDM update. | ||
RomFs: | RomFs: | ||
* Only update.pua was updated, the following was changed in the extracted update.pui (pui.hash in update.pua was also updated): | * Only update.pua was updated, the following was changed in the extracted update.pui (pui.hash in update.pua was also updated): | ||
** "config.txt": system_minor_version was changed from "3" to "4". | ** "config.txt": system_minor_version was changed from "3" to "4". |