Difference between revisions of "Mario Kart Live: Home Circuit"
Cmdblkster (talk | contribs) m (Added new information about the "product_code" parameter) |
(→Type 0x01: Battery Status: First byte actually indicates cable status) |
||
(3 intermediate revisions by 2 users not shown) | |||
Line 171: | Line 171: | ||
|- | |- | ||
|5107 | |5107 | ||
− | |" | + | |Unknown ("Event"?) RCD service |
|0x0103 | |0x0103 | ||
|- | |- | ||
Line 427: | Line 427: | ||
=== Teleoperation === | === Teleoperation === | ||
− | The kart is operated over UDP port 5102. This port is only open when the kart is in [[ | + | 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" | {| class="wikitable" border="1" | ||
Line 479: | Line 479: | ||
|- | |- | ||
|0x0 | |0x0 | ||
− | | | + | |0x1 |
+ | |Cable status bitmask: 0x1 = Cable connected; 0x2 = Unknown; 0x4 = Unknown | ||
+ | |- | ||
+ | |0x1 | ||
+ | |0x2 | ||
|Zero padding | |Zero padding | ||
|- | |- | ||
Line 510: | Line 514: | ||
|0x2 | |0x2 | ||
|0x1 | |0x1 | ||
− | | colspan="2" |Flags bitfield: | + | | colspan="2" |Flags bitfield: IGA??NNN. I/G/A=High-precision units used, ?=Unknown, possibly clipping indicators. N=Number of IMU samples. |
|- | |- | ||
|0x3 | |0x3 | ||
Line 527: | Line 531: | ||
|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: | + | | 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 | ||
Line 536: | Line 540: | ||
|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: | + | | 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 | ||
Line 545: | Line 549: | ||
| 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; // | + | |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; // | + | |s16 ang_vel_x, ang_vel_y, ang_vel_z; // Units: 0.004375 °/s (G=1), 0.1 °/s (G=0) |
|} | |} | ||
Line 554: | 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 | + | 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. | ||
− | |||
− | |||
==== Type 0x03: Motion feedback ==== | ==== Type 0x03: Motion feedback ==== |
Latest revision as of 18:10, 26 February 2021
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 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:
- 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.
RomFs contains only two files:
- "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:
- "config.txt": Contains config which includes fields for efuse_key, efuse_fw, secure_boot, etc. Also references the data under generic/. Seems to be configuration for firmware installation, not uboot.
- "audiofw_sha": 0x20-byte binary SHA256 hash for the "bluecore.audio.aes" file.
- "dtb_sha": 0x20-byte binary SHA256 hash for the .dtb file.
- "rootfs_sha": 0x20-byte binary SHA256 hash for the "root.nand.cpio.gz_pad.img.aes" file.
- "tee_sha": 0x20-byte binary SHA256 hash for the tee file.
- "uImage_sha": 0x20-byte binary SHA256 hash for the "nand.uImage.aes" file.
- "generic/": This contains:
- "android.nand.dtb": Plaintext "kernelDT".
- "bluecore.audio.aes": Encrypted "audioKernel".
- "nand.uImage.aes": Encrypted "linuxKernel".
- "root.nand.cpio.gz_pad.img.aes": Encrypted "InitrdRootFS".
- "tee.bin.aes": Encrypted "tee".
Note that the only firmware archive files accessed by the game are "update.pui", "pui.hash", and "config.txt". The content of "config.txt" is only used with sscanf() to extract the version fields. "update.pui"/"pui.hash" are probably sent over the network connection to the kart - it's unknown whether the game does anything with the content of "pui.hash" other than this.
Kart
The kart is likely codenamed "Fuji" as this is both what the game calls it internally, and how it identifies itself during RCD handshake. Various strings in the kart OSS refer to it as "DHC".
OSS is available for the kart itself.
This uses Linux. The 1.1.0_3 archive contains the following:
busybox-1.22.1.tar.bz2 eudev-1.5.3.tar.gz kmod-17.tar.xz libnl-3.2.24.tar.gz linux-kernel_5c3cb2e0be2243f6d4553ccad2047c9d72e25ea2.tar.gz lrzsz-0.12.20.tar.gz PsdDriver_5a8d821.zip rtl8188eu_074cc66fece232b0d5f1e1f7de57e72022ec12b1.tar.gz uboot_53a0fa98b176329e340b0a2fca6edb7117209751.tar.gz util-linux-2.24.2.tar.xz
PsdDriver is Nintendo's custom kernel module, the GPL license header used in the source starts with the following:
* Sensors and Motors driver * Copyright (C) 2020 Nintendo Co, Ltd
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.
- In the PsdDriver source, the line-ending at the start of various source files was updated.
- In sources/psd_util.c,
initialize_table();
is now called by a dedicatedpsd_util_init_crc8
function instead ofpsd_util_get_crc8
, which is now called bydevice_init
in sources/psd.c.
- In sources/psd_util.c,
The above git-commit-hashes (?) from the filenames doesn't seem to match commits in the upstream repos.
On October 28, 2020, the existing OSS archives were updated without adding a new version. With 1.1.0_3, the uboot archive (which has the same filename) had the "/examples" directory removed.
Hardware
Component | Manufacturer | Part | Notes |
---|---|---|---|
SoC | Realtek | RTD1195 (PB) | Contains hardware H.264 encoder |
DDR3 RAM | Winbond | W631GG6MB-15 | 128 MiB |
NAND Flash | Winbond | W29N01HVSINA | 128 MiB |
WNIC | Realtek | RTL8188E | Connected to SoC via USB |
MCU | STMicroelectronics | STM32F | Responsible for real-time steering and throttle control loops, six-axis IMU acquisition, GPIO, battery status |
IMU | STMicroelectronics | LSM6DSL | 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 | Nintendo branded; OEM unknown | HAC-038 | 3.7V nominal; 1750 mAh capacity |
Camera | Unknown | Unknown | Possibly connected via MIPI CSI |
Pairing process
The kart communicates with the Switch via standard 802.11 frames on channel 1/6/11, with standard CCMP encryption, but the authentication and key exchange protocol is Nintendo-proprietary. See LDN services for more information.
On first startup, Home Circuit generates a random SSID (beginning with 'G') and 0x20-byte PSK (for use in the aforementioned key exchange; it is not standard WPA). These are saved and reused on every subsequent startup, so that any kart(s) with this information stored can reconnect without needing to be paired again.
When pairing, Home Circuit creates a temporary network (random SSID beginning with 'P', randomized PSK, neither stored) and shows the details for this "pairing network" in a QR code for the kart to scan. Once the kart connects, it fetches the main "game" network SSID+PSK and stores them, and both it and Home Circuit exit pairing mode. The pairing network is only active while the QR code is displayed, otherwise the main "game" network runs.
The QR code is a "version 4" (33x33) code with level-M error correction. It uses byte encoding, and stores 0x3E bytes:
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. |
Network protocols
Once the kart connects to the "game" network hosted by Home Circuit, it requests an IP address via DHCP, then connects to the Switch via standard TCP/IP to announce its presence. A series of TCP and UDP connections are established to exchange information, stream video, transmit control signals, monitor kart telemetry, etc.
Protocol | Endpoint | Port | Description | RCD service ID |
---|---|---|---|---|
Management | ||||
TCP | Switch | 5201 | RCD handshake service (pairing only) | 0x0001 |
5202 | RCD handshake service (non-pairing only) | 0x0001 | ||
Kart | 5103 | "Fuji Control" RCD service | 0x0100 | |
5106 | "Fuji Pairing" RCD service (pairing only) | 0x0102 | ||
5107 | Unknown ("Event"?) RCD service | 0x0103 | ||
UDP | Kart | 5004 | Time synchronization | N/A |
Video | ||||
UDP | Switch | 5016+kartid | "LSP" video streaming | N/A |
TCP | 5032+kartid | "LSP" control channel | N/A (non-RCD) | |
Control | ||||
UDP | Kart | 5102 | Teleoperation (throttle, steering, tail light control) | N/A |
Switch | 5116+kartid | Telemetry | N/A |
Note: The ports determined by the kart ID are actually assigned by the Switch and configured via the "Fuji Control" service. The kart itself will actually use any port it is told to use; shown above are how Home Circuit calculates the port numbers.
Handshaking
After the kart connects to the LP2P network and requests an IP address via DHCP, it connects to the RCD handshake service. The port it uses is determined by whether it is in pairing mode (i.e. it learned the network details from a QR code) or not. When pairing, it connects to port 5201, otherwise it uses port 5202. The RCD handshake is the same in either case.
Upon a successful RCD handshake, the handshake channel is left open. The kart will then open ports 5103 and 5107 (or, when pairing, only port 5106) and expect a single connection on each: The listening socket is closed once connection(s) are established to prevent multiple connections, and if connection(s) aren't made within 5 seconds of a completed handshake, the kart will reset its network connection (it closes all TCP connections, releases its IP back to DHCP, disassociates the wireless link, and starts the connection process anew). It will also reset the network connection in this manner if any RCD connection is lost/closed.
"Fuji Pairing" RCD service
The pairing service implements a single command, and is only available immediately after a handshake and when pairing. A network reset will occur if the connection is idle for 1 second.
Command 0x01: SetGroupInfo
Accepts a 0x40-byte payload:
Offset | Size | Description |
---|---|---|
0x00 | 0x20 | SSID, zero-terminated. Home Circuit copies this straight from GroupInfo, so uninitialized bytes may follow. |
0x20 | 0x20 | LP2P PSK |
On success, responds with an empty payload. The kart commits these network settings to non-volatile memory immediately, replacing the old settings (if present). A network reset is initiated, and the kart exits pairing mode, using these newly-provided network settings after the reset. The Switch also exits pairing mode to accept the newly-paired kart.
"Fuji Control" RCD service
This service is used to set up and manage the kart. It is only available when not in pairing mode.
Command 0x01: GetSystemInfo
Takes no payload as input, returns:
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | boot_major_version |
0x1 | 0x1 | boot_minor_version |
0x2 | 0x1 | system_major_version |
0x3 | 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. |
Command 0x02: SetParam
Returns no output (on success) or error code 0x1060e8 if the parameter isn't recognized. Input:
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 |
Setting the parameter "connection_info" is required to prepare the kart to drive. It can only be set once (trying to set it again gives error 0x1040e8). It expects (all values little-endian):
Offset | Size | Description |
---|---|---|
0x0 | 0x2 | Fixed 0x0001; unknown purpose. |
0x2 | 0x2 | Telemetry (UDP) port on Switch |
0x4 | 0x2 | "LSP" control (TCP) port on Switch |
0x6 | 0x2 | "LSP" video (UDP) port on Switch |
0x8 | 0x8 | Current network time, per nn::time::StandardNetworkSystemClock::GetCurrentTime. (A Unix timestamp with one-second precision.) |
Command 0x03: GetParam
Returns the value of the parameter (on success) or error code 0x1060e8. Expected input:
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).
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
This sets the kart between drive mode and "parked" mode. "connection_info" must be set first; it will return error 0x1040e8 if not. Returns an empty payload on success. It expects:
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | Drive mode. 0x01 enables driving controls (and video), 0x00 puts the kart to "sleep." |
0x1 | 0xF | Zero padding. |
Command 0x09: Shutdown
Takes no input and gives no output.
Disables the "network reset" behavior so that the kart will power off when the RCD channel(s) are closed.
Command 0x11: StartFluorescentLightDetection
TBD
Command 0x12: ReadApplicationData
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 drive state. It expects packets of the form:
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
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:
Offset | Size | Description | |
---|---|---|---|
0x0 | 0x2 | Total IMU samples, as an unsigned integer. This number includes the samples in this packet. It overflows. | |
0x2 | 0x1 | Flags bitfield: IGA??NNN. I/G/A=High-precision units used, ?=Unknown, possibly clipping indicators. N=Number of IMU samples. | |
0x3 | 0x1 | Pad byte; zero. | |
0x4 | 0x4 | Microsecond-precision timer, as an unsigned integer. | |
0x8 | 0x10 | 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 | 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 | 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 | |
0x48 | 0xC*(NNN+1) | 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
This section documents the changes for game-updates.
v1.0.1
This only updated the Kart firmware.
ExeFs:
- Nothing changed besides the usual NPDM update.
RomFs:
- 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".
- "audiofw_sha"
- "rootfs_sha"
- "uImage_sha"
- "generic/bluecore.audio.aes": Starts differing at offset 0x1D0.
- "generic/nand.uImage.aes": Starts differing at offset 0x0.
- "generic/root.nand.cpio.gz_pad.img.aes": Starts differing at offset 0x0.