Mario Kart Live: Home Circuit: Difference between revisions

CFSworks (talk | contribs)
CFSworks (talk | contribs)
Type 0x01: Battery Status: First byte actually indicates cable status
 
(4 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 [[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.
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
|-
|-
! Component
|SoC
! Manufacturer
|Realtek
! Part
|RTD1195 (PB)
! Notes
|Contains hardware H.264 encoder
|-
|-
| SoC
|DDR3 RAM
| Realtek
|Winbond
| RTD1195 (PB)
|W631GG6MB-15
| Contains hardware H.264 encoder
|128 MiB
|-
|-
| DDR3 RAM
|NAND Flash
| Winbond
|Winbond
| W631GG6MB-15
|W29N01HVSINA
| 128 MiB
|128 MiB
|-
|-
| NAND Flash
|WNIC
| Winbond
|Realtek
| W29N01HVSINA
|RTL8188E
| 128 MiB
|Connected to SoC via USB
|-
|-
| WNIC
|MCU
| Realtek
|STMicroelectronics
| RTL8188E
|STM32F
| Connected to SoC via USB
|Responsible for real-time steering and throttle control loops, six-axis IMU acquisition, GPIO, battery status
|-
|-
| MCU
|IMU
| STMicroelectronics
|STMicroelectronics
| STM32F
|LSM6DSL
| Responsible for real-time steering and throttle control loops, six-axis IMU acquisition, GPIO, battery status
|Unconfirmed. Mounted "upside-down" on the PCB: +X=right +Y=back +Z=down. Sensitivities: 4.375 m°/s, 0.244 mg.
|-
|-
| IMU
|Li-ion pouch cell
| STMicroelectronics
|Nintendo branded; OEM unknown
| LSM6DSL
|HAC-038
| Unconfirmed. Mounted "upside-down" on the PCB: +X=right +Y=back +Z=down. Sensitivities: 4.375 m°/s, 0.244 mg.
|3.7V nominal; 1750 mAh capacity
|-
|-
| Li-ion pouch cell
|Camera
| Nintendo branded; OEM unknown
|Unknown
| HAC-038
|Unknown
| 3.7V nominal; 1750 mAh capacity
|Possibly connected via MIPI CSI
|-
| Camera
| Unknown
| Unknown
| Possibly connected via MIPI CSI
|}
|}


Line 115: Line 117:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x10
! Description
|Pairing seed. LP2P PSK is SHA256(seed)
|-
|-
| 0x0 || 0x10 || Pairing seed. LP2P PSK is SHA256(seed)
|0x10
|0x20
|Pairing SSID. Remaining space is filled with zeros.
|-
|-
| 0x10 || 0x20 || Pairing SSID. Remaining space is filled with zeros.
|0x30
|0x2
|Pairing channel. Encoded little-endian. Usually 0.
|-
|-
| 0x30 || 0x2 || Pairing channel. Encoded little-endian. Usually 0.
|0x32
|-
|0xC
| 0x32 || 0xC || Padding bytes; zero.
|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
|-
|-
! Protocol
! colspan="5" |Management
! 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
|-
|-
|rowspan="5"| TCP
|5202
|rowspan="2"| Switch
|RCD handshake service (non-pairing only)
| 5201
|0x0001
| [[RCD#Handshake protocol|RCD handshake]] service (pairing only)
| 0x0001
|-
|-
| 5202
| rowspan="3" |Kart
| RCD handshake service (non-pairing only)
|5103
| 0x0001
|"Fuji Control" RCD service
|0x0100
|-
|-
|rowspan="3"| Kart
|5106
| 5103
|"Fuji Pairing" RCD service (pairing only)
| "Fuji Control" RCD service
|0x0102
| 0x0100
|-
|-
| 5106
|5107
| "Fuji Pairing" RCD service (pairing only)
|Unknown ("Event"?) RCD service
| 0x0102
|0x0103
|-
|-
| 5107
|UDP
| "Fuji Update" RCD service
|Kart
| 0x0103
|5004
|Time synchronization
|N/A
|-
|-
| UDP
! colspan="5" |Video
| Kart
| 5004
| Time synchronization
| N/A
|-
|-
!colspan="5"| Video
|UDP
| rowspan="2" |Switch
|5016+kartid
|"LSP" video streaming
|N/A
|-
|-
| UDP
|TCP
|rowspan="2"| Switch
|5032+kartid
| 5016+kartid
|"LSP" control channel
| "LSP" video streaming
|N/A (non-RCD)
| N/A
|-
|-
| TCP
! colspan="5" |Control
| 5032+kartid
| "LSP" control channel
| N/A (non-RCD)
|-
|-
!colspan="5"| Control
| rowspan="2" |UDP
|Kart
|5102
|Teleoperation (throttle, steering, tail light control)
|N/A
|-
|-
|rowspan="2"| UDP
|Switch
| Kart
|5116+kartid
| 5102
|Telemetry
| Teleoperation (throttle, steering, tail light control)
|N/A
| 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
|-
|-
! Offset
|0x00
! Size
|0x20
! Description
|SSID, zero-terminated. ''Home Circuit'' copies this straight from GroupInfo, so uninitialized bytes may follow.
|-
| 0x00
| 0x20
| SSID, zero-terminated. ''Home Circuit'' copies this straight from GroupInfo, so uninitialized bytes may follow.
|-
|-
| 0x20
|0x20
| 0x20
|0x20
| LP2P PSK
|LP2P PSK
|}
|}


Line 240: Line 247:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x1
! Description
|boot_major_version
|-
| 0x0
| 0x1
| boot_major_version
|-
|-
| 0x1
|0x1
| 0x1
|0x1
| boot_minor_version
|boot_minor_version
|-
|-
| 0x2
|0x2
| 0x1
|0x1
| system_major_version
|system_major_version
|-
|-
| 0x3
|0x3
| 0x1
|0x1
| system_minor_version
|system_minor_version
|-
|-
| 0x4
|0x4
| 0x29
|0x29
| Zero-terminated ASCII string which is a 160-bit value in hexadecimal. Appears to be some kind of SHA1.
|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
|-
|-
! Offset
|0x0
! Size
|0x80
! Description
|Parameter name, zero-padded.
|-
|-
| 0x0
|0x80
| 0x80
|0x2
| Parameter name, zero-padded.
|Value length
|-
|-
| 0x80
|0x82
| 0x2
|0xE
| Value length
|Zero padding to align to 0x10-byte boundary
|-
|-
| 0x82
|0x90
| 0xE
|Varies
| Zero padding to align to 0x10-byte boundary
|Value to set
|-
| 0x90
| Varies
| Value to set
|}
|}


Line 296: Line 301:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x2
! Description
|Fixed 0x0001; unknown purpose.
|-
|-
| 0x0
|0x2
| 0x2
|0x2
| Fixed 0x0001; unknown purpose.
|Telemetry (UDP) port on Switch
|-
|-
| 0x2
|0x4
| 0x2
|0x2
| Telemetry (UDP) port on Switch
|"LSP" control (TCP) port on Switch
|-
|-
| 0x4
|0x6
| 0x2
|0x2
| "LSP" control (TCP) port on Switch
|"LSP" video (UDP) port on Switch
|-
|-
| 0x6
|0x8
| 0x2
|0x8
| "LSP" video (UDP) port on Switch
|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
|-
|0x00
|0x80
|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
|-
|-
! Offset
|0x3
! Size
|0x1
! Description
|Fixed 0x03; unknown purpose
|-
|-
| 0x00
|0x7
| 0x80
|0x1
| Parameter name, zero-padded.
|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
|}
|}
One known parameter is "product_code" which appears to include a string with the value of the white-label barcode on the bottom of the kart. Perhaps this is how ''Home Circuit'' distinguishes between Mario and Luigi.


==== Command 0x04: SetState ====
==== Command 0x04: SetState ====
Line 344: Line 398:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x1
! Description
|Drive mode. 0x01 enables driving controls (and video), 0x00 puts the kart to "sleep."
|-
| 0x0
| 0x1
| Drive mode. 0x01 enables driving controls (and video), 0x00 puts the kart to "sleep."
|-
|-
| 0x1
|0x1
| 0xF
|0xF
| Zero padding.
|Zero padding.
|}
|}


Line 377: Line 430:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x1
! Description
|Throttle; a signed integer. -128 is full-speed reverse, +127 is full-speed forward.
|-
| 0x0
| 0x1
| Throttle; a signed integer. -128 is full-speed reverse, +127 is full-speed forward.
|-
|-
| 0x1
|0x1
| 0x1
|0x1
| Steering; a signed integer. -128 is full left, +127 is full right.
|Steering; a signed integer. -128 is full left, +127 is full right.
|-
|-
| 0x2
|0x2
| 0x1
|0x1
| Brake light control. 0x00 turns the brake light off, 0x01 turns it on. This is independent of throttle.
|Brake light control. 0x00 turns the brake light off, 0x01 turns it on. This is independent of throttle.
|-
|-
| 0x3
|0x3
| 0x1
|0x1
| Pad byte, apparently always zero.
|Pad byte, apparently always zero.
|-
|-
| 0x4
|0x4
| 0x4?
|0x4?
| Little-endian packet counter. Incremented by 1 for each sent packet. Unknown total size.
|Little-endian packet counter. Incremented by 1 for each sent packet. Unknown total size.
|-
|-
| 0x8?
|0x8?
| 0x18?
|0x18?
| Zero padding to bring the total packet size to 0x20.
|Zero padding to bring the total packet size to 0x20.
|}
|}


Line 422: Line 474:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
!Description
|-
|-
! Offset
|0x0
! Size
|0x1
! Description
|Cable status bitmask: 0x1 = Cable connected; 0x2 = Unknown; 0x4 = Unknown
|-
|-
| 0x0
|0x1
| 0x3
|0x2
| Zero padding
|Zero padding
|-
|-
| 0x3
|0x3
| 0x1
|0x1
| How many bars to show in the HUD for battery status (0x00-0x04)
|How many bars to show in the HUD for battery status (0x00-0x04)
|-
|-
| 0x4
|0x4
| 0x1C
|0x1C
| Zero padding, to bring total packet length to 0x20.
|Zero padding, to bring total packet length to 0x20.
|}
|}


Line 449: Line 504:


{| class="wikitable" border="1"
{| class="wikitable" border="1"
!Offset
!Size
! colspan="2" |Description
|-
|-
! Offset
|0x0
! Size
|0x2
!colspan="2"| Description
| colspan="2" |Total IMU samples, as an unsigned integer. This number includes the samples in this packet. It overflows.
|-
| 0x0
| 0x2
|colspan="2"| Total IMU samples, as an unsigned integer. This number includes the samples in this packet. It overflows.
|-
|-
| 0x2
|0x2
| 0x1
|0x1
|colspan="2"| Flags bitfield: PCCCCNNN. P=High-precision, C=Unknown, possibly clipping indicators. N=Number of IMU samples.
| colspan="2" |Flags bitfield: IGA??NNN. I/G/A=High-precision units used, ?=Unknown, possibly clipping indicators. N=Number of IMU samples.
|-
|-
| 0x3
|0x3
| 0x1
|0x1
|colspan="2"| Pad byte; zero.
| colspan="2" |Pad byte; zero.
|-
|-
| 0x4
|0x4
| 0x4
|0x4
|colspan="2"| Microsecond-precision timer, as an unsigned integer.
| colspan="2" |Microsecond-precision timer, as an unsigned integer.
|-
|-
| 0x8
|0x8
| 0x10
|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.
| 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
|0x18
| 0xC
|0xC
| Integrator A position
|Integrator A position
|rowspan="2"| s32 x, y, z; gives an estimate of total displacement since this integrator was reset. Units: 24.59 * 2^-19 m(???)
| 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
|0x24
| 0xC
|0xC
| Integrator B position
|Integrator B position
|-
|-
| 0x30
|0x30
| 0xC
|0xC
| Integrator A velocity
|Integrator A velocity
|rowspan="2"| s32 x, y, z; gives an estimate of total change in velocity since this integrator was reset. Units: ~2^-19 m/s
| 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
|0x3C
| 0xC
|0xC
| Integrator B velocity
|Integrator B velocity
|-
|-
|rowspan="2"| 0x48
| rowspan="2" |0x48
|rowspan="2"| 0xC*(NNN+1)
| rowspan="2" |0xC*(NNN+1)
|rowspan="2"| 1-8 raw IMU samples
| rowspan="2" |1-8 raw IMU samples
| s16 accel_x, accel_y, accel_z; // in units of 0.000244 Gs. 1G is added to accel_z to compensate for gravity.
|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; // in units of 0.004375 degrees per second.
|s16 ang_vel_x, ang_vel_y, ang_vel_z; // Units: 0.004375 °/s (G=1), 0.1 °/s (G=0)
|}
|}


Line 504: Line 558:
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 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 1+0i+0j+0k once the IMU is enabled (i.e. when "connection_info" is set).
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.
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.
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.
If the 'P' bit is set, the integrator values are multiplied by 78.5(???) prior to being sent, for added precision. The kart will always set the 'P' bit as long as all integrator values are small enough to permit it.


==== Type 0x03: Motion feedback ====
==== Type 0x03: Motion feedback ====
Line 523: 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".