DEV Community

Cover image for PRUDP: Writing a Nintendo 3DS Network Protocol
Lucas Matheus
Lucas Matheus

Posted on

PRUDP: Writing a Nintendo 3DS Network Protocol

Hello, in this article I will end up detailing how I made a basic and simplified header of a protocol that is used for network communication by some Nintendo software on your 3DS portable. But, before showing the code, I think it's important to clarify some terms for those who are not very familiar with both the language used (C, C++) and how PRUDP works.

PRUDP

PRUDP is an evolution of the User Datagram Protocol (UDP), a network protocol commonly used in online games and other real-time applications, but which does not provide guarantees of packet delivery or order. PRUDP adds reliability and error correction features to UDP, making it more suitable for applications where it is crucial that data is delivered securely and in order.

PRUDP is used in Nintendo game consoles such as the Nintendo 3DS to support online gaming with low latency and high reliability.

There are at least two versions of PRUD: one used in Friends Service (Nintendo's service for player interaction) and another that was used in Pokémon X&Y. The two versions V0 and V1 have few differences, the main difference being in packet decoding.

Newer portable versions like the Switch use Websockets instead of UDP connections. It is worth mentioning that Nintendo uses a rewritten stack from the library.

Quazal Rendez-Vous library

The Quazal Rendez-Vous library is a networking library developed by Quazal Technologies that provides peer-to-peer (P2P) networking functionality for games and other real-time applications. The library is designed to handle peer-to-peer discovery, connection establishment, communication, and session management between devices on a network.

Quazal Rendez-Vous supports several advanced features such as load balancing, failover, security, scalability, fault tolerance, and bandwidth management. It can be used on a variety of platforms, including PC, game consoles, and mobile devices.

How PRUDP works

When a client connects to a server, a packet with the SYN flag is sent so that the server recognizes the connection. After recognition, the server sends a packet with the CONNECT flag and the HANDSHAKE is performed. If the client wants to disconnect, a packet with the DISCONNECTED flag is sent.

The following techniques are used to achieve reliability:

  1. A packet that has FLAG_NEED_ACK defined must be recognized by the receiver. If the sender does not receive an acknowledgment after a certain period of time, it will resend the package.

  2. A sequence ID is sent along with a packet so that the receiver can rearrange the packets if necessary.

  3. To keep the connection alive, both the client and the server can send PING packets to each other after a certain period of time has elapsed.

Below is an example of a session using this protocol:

Handshake 1   → af a1 40 00 00 00 00 00 00 00 00 00 00 00 00 97

Handshake 1   ← a1 af 10 00 00 00 00 00 00 00 00 5f 22 68 ea 3a

Handshake 2   → af a1 61 00 18 5f 22 68 ea 01 00 d4 d6 91 e8 c9

Handshake 2   ← a1 af 11 00 50 d4 d6 91 e8 01 00 00 00 00 00 de

Send data     → af a1 62 00 18 26 b4 01 a1 02 00 00 (25 bytes of encrypted payload) ef

Acknowledge   ← a1 af 12 00 50 78 56 34 12 02 00 00 d1

Send data     ← a1 af 62 00 50 67 dd f9 c3 01 00 00 (255 bytes of encrypted payload) 03

Acknowledge   → af a1 12 00 18 78 56 34 12 01 00 00 97

Send data     → af a1 62 00 18 8d 58 91 c0 03 00 00 (21 bytes of encrypted payload) fa

Acknowledge   ← a1 af 12 00 50 78 56 34 12 03 00 00 d2

Send data     ← a1 af 62 00 50 a9 c5 fa 2e 02 00 00 (130 bytes of encrypted payload) 54

Acknowledge   → af a1 12 00 18 78 56 34 12 02 00 00 98

Hangup        → af a1 63 00 18 5f 22 68 ea 04 00 aa

Hangup        ← a1 af 13 00 50 d4 d6 91 e8 04 00 e2
Enter fullscreen mode Exit fullscreen mode

PRUDP Header

Roughly speaking, the PRUDP header is somewhat similar to the UDP header with the difference that it contains some additional information that changes the logic of its operation to be more reliable and secure than UDP. Below is an image with the content of documentation produced by Yannik Marchand and which can be found at the following link: https://github.com/kinnay/NintendoClients/wiki/PRUDP-Protocol

Basically, each of these fields that make up the package has a type that is extremely important when writing in a programming language. A more detailed header with types can be found at this link: https://www.3dbrew.org/wiki/PRUDP

PRUDP Packet Structure

Offset Description
0x0 Source
0x1 Destination
0x2 Type and Flags
0x4 Session ID
0x5 Packet signature
0x9 Sequence ID

Hands-on!!

Ok, to begin with I had no idea how to write a protocol (maybe I still don't). I know that it was possible to write its representation in any programming language, but in a compilation it might not be very efficient to do this in Python or Java, for example. So with that in mind it had to be C/C++, but how the hell could I make a protocol like ICMP, UDP or TCP? Where would I find the source code to see its operation and rules and conjunction with an operating system? Well, after some research I came across a code repository that has protocols implemented in FREE BSD and there I could see what the headers of the network protocol stack used in this operating system look like. With this I was also able to see some specifications and rules that are extremely important for a protocol.

understand how the UDP code works

Knowing a little about unix I was able to understand that the file that would implement the correct functions and data structure for UDP would be the udp.h file and this file would be located in the library that implements the FreeBSD network protocol stack (sys/netinet)

The “netinet” library is an essential component of FreeBSD and is used by many networking applications on Unix-based operating systems, including routers, web servers, and email servers.

And bingo!! With this we have the source code of a crucial header for the operation of UDP:

/*-
 * Copyright (c) 1982, 1986, 1993
 *  The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *  @(#)udp.h   8.1 (Berkeley) 6/10/93
 * $FreeBSD$
 */

#ifndef _NETINET_UDP_H_
#define _NETINET_UDP_H_

/*
 * UDP protocol header.
 * Per RFC 768, September, 1981.
 */
struct udphdr {
    u_short uh_sport;       /* source port */
    u_short uh_dport;       /* destination port */
    u_short uh_ulen;        /* udp length */
    u_short uh_sum;         /* udp checksum */
};

/* 
 * User-settable options (used with setsockopt).
 */
#define UDP_ENCAP           1

/* Start of reserved space for third-party user-settable options. */
#define UDP_VENDOR          SO_VENDOR

/*
 * UDP Encapsulation of IPsec Packets options.
 */
/* Encapsulation types. */
#define UDP_ENCAP_ESPINUDP_NON_IKE  1 /* draft-ietf-ipsec-nat-t-ike-00/01 */
#define UDP_ENCAP_ESPINUDP      2 /* draft-ietf-ipsec-udp-encaps-02+ */

/* Default ESP in UDP encapsulation port. */
#define UDP_ENCAP_ESPINUDP_PORT     500

/* Maximum UDP fragment size for ESP over UDP. */
#define UDP_ENCAP_ESPINUDP_MAXFRAGLEN   552

#endif

Enter fullscreen mode Exit fullscreen mode

However, for the moment, we will focus on the struct implemented in code that follows what is established in RFC 768.

RFC 768 defines the basic header structure of the User Datagram Protocol (UDP), a data transport protocol used in computer networks. The UDP header is composed of four fields, as described below:

  1. Source Port (2 bytes): A 16-bit field that identifies the sender's port.

  2. destination port (2 bytes): a 16-bit field that identifies the recipient's port.

  3. Length (2 bytes): A 16-bit field that specifies the length of the UDP datagram, including the header and data.

  4. Checksum (2 bytes): An optional 16-bit field that is used to ensure datagram integrity. The checksum is calculated based on the values of the header and data fields.

  5. Checksum (2 bytes): An optional 16-bit field that is used to ensure datagram integrity. The checksum is calculated based on the values of the header and data fields.

UDP Protocol Header

With that, I just had to try to understand why the header was written that way, below we will focus on this Struct and how I used it to write the header that is possibly used in PRUDP.

Analyzing udphdr

/*
  * UDP protocol header.
  * Per RFC 768, September, 1981.
  */
struct udphdr {
u_short uh_sport; /* source port */
u_short uh_dport; /* destination port */
u_short uh_ulen; /* udp length */
u_short uh_sum; /* udp checksum */
};
Enter fullscreen mode Exit fullscreen mode

The udphdr structure defines the UDP header fields. Each field represents a specific value, such as the source port, destination port, UDP datagram length, and checksum. Fields are defined as variables of type u_short, which is a 16-bit integer data type used to represent unsigned values.

The uh_sport field represents the source port, while the uh_dport field represents the destination port. These fields are used to identify the processes that are sending and receiving data.

The uh_ulen field specifies the length of the UDP datagram, including the header and data. This field is useful to ensure that the datagram is transmitted correctly over the network.

The uh_sum field is optional and is used to check datagram integrity. The value of this field is calculated using a checksum algorithm that checks whether data has been corrupted or changed during transmission.

ANSI C(1989)
The following code was written in the ANSI 1989 C specifications: ANSI C is a reference to the C programming language standard that was adopted by ANSI (American National Standards Institute) in 1989. The specification defined many features that are still used in programming today in C, such as defining fixed-size integer types, function prototypes, variable macros, void pointers, and more.

In 1999, ISO (International Organization for Standardization) published a new revision of the C language specification, known as ISO/IEC 9899:1999. This revision is generally referred to as C99 and included several improvements and new features over the original 1989 ANSI C specification. Some of the new additions include additional data types, support for C++ style comments, the ability to declare variables in the middle of a block of code, compound assignment operators, the inline keyword, and a bunch of other things.

Rewriting the UDP header based on the PRUDP header

struct prudphdr {

  uint16_t uh_sport; /* source port rewrite (C 99 ) */
  uint16_t uh_dport; /* destination port */
  uint16_t uh_ulen; /* prudp length */
  uint8_t uh_sum; /* prudp checksum rewrite to 8 bits */
  uint8_t uh_type; /* prudp type msg */
  uint16_t uh_seq_num; /* prudp seq number packet */
  uint16_t uh_session_id; /* session identifier */
  uint32_t uh_timestamp;
  uint16_t uh_payload_size; /* data packet payload size */

};
Enter fullscreen mode Exit fullscreen mode

uh_sport: the source port of the packet.
uh_dport: the destination port of the packet.
uh_ulen: the length of the PRUDP packet, including the header and data.
uh_sum: an 8-bit checksum that helps verify packet integrity.
uh_type: a field that indicates the type of message contained in the PRUDP packet.
uh_seq_num: A sequence number that helps identify unique packets in a given PRUDP session.
uh_session_id: A unique session identifier that helps distinguish separate PRUDP sessions.
uh_timestamp: A field that contains a time value that can be used to help synchronize communications across multiple PRUDP sessions.
uh_payload_size: the size of the data payload contained in the PRUDP packet.
C99
To make the code more “modern” and with some improvements to the original UDP, I decided to use the C99 standard, which has some advantages (and disadvantages) in relation to ANSI C.

It was the third revision of the C standard, succeeding the 1989 ANSI C standard. The C99 version brought several improvements and new features to the language, such as tiadditional data pos, support for single-line comments, variables declared anywhere in scope, support for variable-length arrays, new features for string manipulation, and more. Additionally, the C99 specification includes support for Unicode codes, which allows programs written in C to work with a wide variety of languages and character sets. Most modern C compilers support the C99 standard.

The use of uint16_t, uint8_t and uint32_t is an example of this improvement. This type is defined in the stdint.h standard library, which was introduced in the C99 standard to ensure portability across different computer architectures.

Using size-defined data types, such as uint16_t, helps ensure that a program has the same behavior across different platforms, regardless of the underlying architecture. This is because the size of standard data types such as int and long can vary by platform.

Furthermore, it is possible to know exactly what type of data we are dealing with during the production and maintenance of the code.

Conclusion

Well, to conclude, I'll have to point out that I didn't find much in Nintendo's official documentation. In fact, they made me agree to an NDA just to have access to the 3DS online documentation :v

Much of the content I found was through research on forums and unofficial documentation (of extreme quality) produced by programmers with or without association with Nintendo.

In the end, it was a very interesting exercise, although: writing just the header is not a complicated task and does not even guarantee whether the implementation detailed here is correct.

To correctly implement the PRUDP protocol, it is necessary to follow the protocol's detailed specification, which includes information on how packets should be constructed, transmitted, received and handled in case of errors.

Additionally, you need to test the implementation in a real or simulated environment to verify that it works as expected and that it is compatible with other implementations of the protocol.

Top comments (0)