Mario Kart Live: Home Circuit

From Nintendo Switch Brew
Revision as of 00:30, 13 February 2021 by CFSworks (talk | contribs) (→‎Network protocols: Teleoperation)
Jump to navigation Jump to search

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 dedicated psd_util_init_crc8 function instead of psd_util_get_crc8, which is now called by device_init in sources/psd.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.

Summary of ports
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 "Fuji Update" 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 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

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. 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.

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.