3.3 KiB
NXST Wire Protocol
Overview
NXST uses two sockets per transfer session:
| Socket | Purpose |
|---|---|
| UDP multicast | Receiver advertisement (discovery) |
| TCP | File data stream |
Both sides must be on the same local network segment. The sender (Transfer mode) initiates; the receiver (Receive mode) listens.
Discovery — UDP Multicast
| Parameter | Value |
|---|---|
| Group | 239.0.0.1 |
| Port | 8081 |
| Direction | sender → receiver |
Flow:
- Receiver joins multicast group and binds
0.0.0.0:8081. - Sender sends
"DISCOVER_SERVER"(15 bytes, no null terminator) to239.0.0.1:8081. - Receiver replies
"SERVER_HERE"(11 bytes) to the sender's source address. - Sender extracts the receiver's IP from the reply source and closes the UDP socket.
Sender polls in 100 ms slices for up to 3 seconds. Cancel is checked each slice.
File Transfer — TCP
| Parameter | Value |
|---|---|
| Port | 8080 |
| Direction | sender connects → receiver listens |
| Buffer size | 65 536 bytes (proto::BUF_SIZE) |
Connection:
- Receiver listens on
0.0.0.0:8080(started concurrently with multicast listener). - Sender connects after receiving
"SERVER_HERE".
Wire layout — one file:
┌─────────────────────────────────┐
│ filename_len │ uint32_t LE │ 4 bytes
├─────────────────────────────────┤
│ filename │ filename_len │ bytes, no null terminator
│ │ bytes │
├─────────────────────────────────┤
│ file_size │ uint64_t LE │ 8 bytes
├─────────────────────────────────┤
│ file_data │ file_size │ bytes
│ │ bytes │
└─────────────────────────────────┘
Files are sent sequentially. The stream ends with a sentinel frame:
filename_len == 0 (proto::EOF_SENTINEL)
No filename or file_size field follows the sentinel.
Constraints:
filename_len > proto::MAX_FILENAME(4 096) is treated as a protocol error; the receiver aborts.- Filenames are full paths as produced by
io::backup(e.g./switch/NXST/<title>/<user>/...). - On the receiver, the username path component is rewritten to match the local user's nickname before writing to disk.
Post-Transfer
After the TCP stream closes (sentinel received), the receiver calls io::restore:
- Mounts the title's save filesystem.
- Clears existing save data.
- Copies received files into
save:/. - Commits via
fsdevCommitDevice("save").
The sender creates a local backup via io::backup before connecting, so the sender's own save is never at risk.
Cancellation
Either side can cancel at any time by closing the relevant socket (shutdown + close). The other side's blocking read/write will return an error and the transfer loop exits cleanly. The receiver's accept thread sets receiver_state.done = true regardless of how the connection ends, so the UI poll loop always terminates.