What follows is my attempt to document the Nintendo Wi-Fi Connection functionality of Pokémon Pearl, which uses Nitro SDK 1.2. Diamond very probably works the same way. It is less obvious whether Platinum, HeartGold, and SoulSilver follow the same protocol. This protocol is different for Generation V (not only are the servers different, but the GameSpy login protocol has changed).
Much of what is in this document depends on previous work by others on decoding the GameSpy protocol, including Wiimm's work on the Mario Kart Wii protocol, teknogods's partial implementation of the GameSpy protocol, and Vetle's work on workaround for, and partial documentation of, the SSL communications with nas.nintendowifi.net.
Note that the following documentation is necessarily incomplete and is based on only partial understanding of the protocols involved, as it is based on observing network traffic originating from the DS and behavior observed when attempting to re-implement the following behaviors. As the protocols depend on two GameSpy protocols, it is possible that work done by others on GameSpy protocols (e.g. that done by Luigi Auriemma) may offer additional insights into the GameSpy protocols and how the Nintendo Wi-Fi Connection servers might work beyond those offered here (e.g. with respect to NAT negotiation), although it is equally possible that such work may be misleading. What I attempt to document here is primarily with respect to the Nintendo Wi-Fi Connection, and not other implementations of the GameSpy protocols and is based solely on the traffic I have observed.
Due to lack of time and effort (as well as the vast variety of possible fail states), I cannot document every possible server-side error state here, but instead focus on successful interactions in the protocol. To the extent that I have encountered error states during work related to the creation of this document, I have listed them. However, I cannot safely state that anywhere close to every error state is listed here. However, it seems plausible to assume that the Pokémon games themselves are "perfect" implementations, so error states are unlikely to be relevant except in so far as a "perfect" server implementation is desirable to help debug novel clients other than the games themselves.
Finally, I would like to note that the example packets (particularly the PCAP traces) have been modified to remove identifying MAC addresses and IP addresses in the data. If you have reason to believe that external IP addresses or MAC addresses are relevant to aspects of the protocol I have presented here, please e-mail me at (my domain) at (my domain).com for the unmodified traces. Also, please note that the DNS queries and HTTPS connection have been made through a proxy host (10.0.1.4) executing a man-in-the-middle attack to obtain the SSL session keys the Nintendo DS (as bridged through the packet capturing interface 10.0.1.6) is using.
TODO: Document Platinum, HeartGold/SoulSilver, Black/White, Black 2/White 2
In all cases, the Nintendo DS performs a DNS lookup of all domains before connecting to them. These queries are typical DNS lookups of the A record, and can be overridden with your hosts file if you are sharing your computer’s wi-fi (for example).
Friend codes of DS games are 48-bit integers. The lowest 32 bits of a friend code indicate the user's profile ID on the GameSpy service. The highest 8 bits are a checksum of the profile ID combined with the 3-character prefix of the game cart code (Pokémon Pearl and Diamond share a prefix: ADA). This checksum algorithm is fully described by CaitSith2's PHP implementation of the verification algorithm (mirror).
All network access uses GameSpy to manage accounts. These accounts are created and managed by the HTTP server nas.nintendowifi.net. Pokémon will not connect to nas.nintendowifi.net without a valid server-side SSL certificate. Such a certificate must be issued by a certificate authority identified as C=US, ST=Washington, O=Nintendo of America Inc, OU=NOA, CN=Nintendo CA/emailAddress=ca@noa.nintendo.com (for American carts at least) and the certificate must be signed by the private keys corresponding to the following public key included in the ROM:
B3 CD 79 97 77 5D 8A AF 86 A8 E8 D7 73 1C 77 DF
10 90 1F 81 F8 41 9E 21 55 DF BC FC 63 FB 19 43
F1 F6 C4 72 42 49 BD AD 44 68 4E F3 DA 1D E6 4D
D8 F9 59 88 DC AE 3E 9B 38 09 CA 7F FF DC 24 A2
44 78 78 49 93 D4 84 40 10 B8 EC 3E DB 2D 93 C8
11 C8 FD 78 2D 61 AD 31 AE 86 26 B0 FD 5A 3F A1
3D BF E2 4B 49 EC CE 66 98 58 26 12 C0 FB F4 77
65 1B EA FB CB 7F E0 8C CB 02 A3 4E 5E 8C EA 9B
Pokémon verifies the server's certificate using this public key and will fail if the server’s certificate is not signed with it. The certificate currently presented by nas.nintendowifi.net may be found here. It is unlikely that the server certificate itself must have the subject C=US, ST=Washington, O=Nintendo of America Inc., OU=Nintendo Wifi Network, CN=nas.nintendowifi.net.
Several workarounds exist to avoid the SSL issue, however all workable solutions require modification of the ROM:
Much of this section is an interpretation of code written by teknogods to implement the GameSpy master servers.
At several places in its Wi-Fi communications, Pokémon uses the GameSpy UDP protocol. This protocol appears to be a variant of the GameSpy Query Protocol in which the roles of client and server are reversed (i.e. the master server to which the DS connects acts as a passive "client" in the query). This protocol, which uses UDP port 27900, appears to consist of two distinct UDP datagrams, depending on whether the data is sent by the client (DS) or the server (in the gs.nintendowifi.net domain):
struct gamespy_client_datagram {
char message_type; // See Message Types, below.
long client_id; // An ID provided by the client.
// It is unknown if this is unique per user or per session.
struct gamespy_client_payload payload[]; // The payload of the datagram.
};
struct gamespy_server_datagram {
short ack; // Always 0xFEFD in network byte order (i.e. "\xfe\xfd")
char message_type; // See Message Types, below.
long client_id; // The ID previously provided by the client.
union gamespy_server_payload payload; // The payload of the datagram.
};
It is unknown how the client_id
is generated, but any server response to a client datagram contains the client_id
originally sent in the client datagram.
The payload
of the
is a sequence of NULL-delimited string values (i.e. non-terminated string values separated by the byte struct
gamespy_client_datagram0x00
), which may typically be interpreted as key-value pairs.
The payload
of the
depends on the message type.struct
gamespy_server_datagram
AVAILABLE (0x09
): Usage unknown; may be used to determine whether GameSpy UDP functionality is online.
The payload
of an AVAILABLE datagram sent by the client contains two items, the GameSpy game name (for Pokémon Pearl, and presumably Diamond as well, this is pokemondpds
), followed by the empty string. Typically, the client_id
of this AVAILABLE datagram is 0x00000000
.
The server will then respond with its own AVAILABLE datagram (elsewhere in this document called AVAILABLE_RESPONSE) with an empty payload.
HEARTBEAT (0x03
): Usage unknown; may be used to initialize and register that a cart is online with the GameSpy service. The cart will send a HEARTBEAT message every 60 seconds following login.
Upon receiving the first HEARTBEAT datagram from a client (which will have a number of values initially populated with the value 0
, the server will respond with a CHALLENGE_RESPONSE datagram.
The payload
of a HEARTBEAT datagram sent by the client contains a number of key-value pairs and may be terminated by a sequence of empty key-value pairs (it is unknown if the sequence is relevant). Observed keys include the following:
localip0
: The device-local IP. May be an internal network IP.localport
: The device-local port, presumably for connections. This is the same as the source UDP port of this message.natneg
: Presumably a boolean flag for NAT negotiation that may take 0
or 1
as values. Practical use unknown.statechanged
: Probably a flag marking some state change (1
may represent start of availability for connections, 2
may represent end of availability for connections, and 3
may represent an error or retry state, such as if no CHALLENGE_RESPONSE message is received in response to this message).gamename
: The GameSpy game name.publicip
: Initially 0
, according to teknogods, this value is populated with the external IP of the game after completing the CHALLENGE_RESPONSE sequence.publicport
: Initially 0
, according to teknogods, this value is populated with the external port of the game after completing the CHALLENGE_RESPONSE sequence.numplayers
: Initially 0
, this may indicate the number of players in this game's session.maxplayers
: Initially 0
, this may indicate the maximum number of players in this game's session.unknown
: May be observed in the initial HEARTBEAT datagram. Likely a placeholder for game-specific properties. May take any value.dwc_pid
: Nintendo Wi-Fi Connection specific value. Profile ID of the user. In HEARTBEAT datagrams sent from Pokémon Pearl, this value replaces the first unknown
property after the initial HEARTBEAT has been sent.dwc_mtype
: Nintendo Wi-Fi Connection specific value. Unknown value. May always be 2. In HEARTBEAT datagrams sent from Pokémon Pearl, this value replaces the second unknown
property after the initial HEARTBEAT has been sent.dwc_mresv
: Nintendo Wi-Fi Connection specific value. Unknown value. May always be 0. In HEARTBEAT datagrams sent from Pokémon Pearl, this value replaces the third unknown
property after the initial HEARTBEAT has been sent.dwc_mver
: Nintendo Wi-Fi Connection specific value. Unknown value. May always be 3. In HEARTBEAT datagrams sent from Pokémon Pearl, this value replaces the fourth unknown
property after the initial HEARTBEAT has been sent.dwc_eval
: Nintendo Wi-Fi Connection specific value. Unknown value. May always be 1. In HEARTBEAT datagrams sent from Pokémon Pearl, this value replaces the fifth unknown
property after the initial HEARTBEAT has been sent.CHALLENGE_RESPONSE (0x01
): This datagram is part of the CHALLENGE_RESPONSE algorithm that authenticates the client with the server when the server receives the initial HEARTBEAT datagram from a client.
The payload
of a CHALLENGE_RESPONSE datagram from the server contains five concatenated values (with no separators):
0x21
to 0x7F
)00
inet_aton(external_ip)
(to replace the value of publicip
in future HEARTBEAT datagrams)htons(external_port)
(to replace the value of publicport
in future HEARTBEAT datagrams)The client will then respond with a CHALLENGE_RESPONSE datagram of its own containing a single value followed by a single NULL byte terminator:
1vTlwb
for Pokémon Diamond & Pearl).RESPONSE_CORRECT (0x0A
): If sent by the server, the client's response to the CHALLENGE_RESPONSE datagram sent by the server was correct.
The payload
of an RESPONSE_CORRECT datagram is empty.
KEEPALIVE (0x08
): The client will send this datagram every 20 seconds following reception of the RESPONSE_CORRECT packet to keep the connection alive with the master server. KEEPALIVE datagrams have no payload.
Much of this section is an interpretation of code written by teknogods to implement the GameSpy TCP protocol. Definitions of some record types were originally described by Wiimm in his documentation of the Mario Kart Wii protocol
With the significant exception of the Global Trading System, almost all Pokémon network traffic uses the GameSpy TCP protocol. This protocol, which uses TCP port 29900, consists of a series of "records" transmitted between client and server. These records consist of a series of key-value pairs with (string) key and (string) value each preceded by a single backslash (C-string "\\"
). Empty string values are allowed
A record consists of an initial "record type" key-value pair, where the key indicates the type of record, and the value (if non-empty) is an stringified integer which appears to obliquely indicate the version or subtype of the record type. Additional key-value pairs follow until a final
key, with empty value is read, which signifies the end of the record.
Most records are identified by an id
key, which indicates the sequence number of the request or response. Responses to requests should contain the same id
value as the original request.
It is unknown if order of keys besides the record type and final
is significant.
The following are known record types (with the record type key-value pair given in parentheses):
\lc\1
): Presented by the server to offer a challenge which the client must meet to log in to GameSpy. This record is sent to the client immediately upon connection. This record has two keys:challenge
: A random 10-character string of ASCII uppercase lettersid
: The sequence ID (usually 1
, as it is the first record sent to the client.\login\
): Response by the client to a LOGIN_CHALLENGE record. This record has nine keys:challenge
: A random 32-character string of ASCII uppercase and lowercase letters. This challenge string is generated by the client to verify the server.authtoken
: An authentication token used to identify the user this client wishes to log in as (this authentication token should already be known by the server)response
: A response to the challenge
token presented in the previous LOGIN_CHALLENGE record. The server will validate this value. This value is calculated as md5sum(md5sum(challenge) + (" "
* 48) + authtoken + clientChallenge + serverChallenge + md5sum(challenge))
, where md5sum
returns the MD5 message digest as a 32 character string of hexadecimal digits and letters in ASCII. challenge
and authtoken
should be the values returned by nas.nintendowifi.net
. serverChallenge
and clientChallenge
, on the other hand, are the challenge strings provided in the previous LOGIN_CHALLENGE and this LOGIN record respectively. This field can be safely ignored when implementing a server that does not need to validate clients.firewall
: Possibly a boolean flag (1
or 0
) to mark whether the client is behind a firewall.port
: Possibly an indicator of the port open on the device for communications.productid
: Probably a GameSpy-specific ID indicating the product attempting to connect to GameSpy (presumably to differentiate between identical users using different games)gamename
: GameSpy-specific name of the gamenamespaceid
: Unknown. May indicate a level of login-details, as this takes the value 0
with Pokémon Pearl, but 16
with Mario Kart Wii, which has additional fields in the LOGIN record, such as partnerid
and sdkrevision
id
: The sequence ID of the previous LOGIN_CHALLENGE record (usually 1)\lc\2
): This response is returned to the client to notify them of the session key and profile information after receiving a LOGIN record. Once this message is received, all other records may be sent. This record has seven keys:sesskey
: A unique 32-bit integer session key used to identify this GameSpy session. The value must be less than 2^31. Values greater than (2^31 - 1) may be floored by the client to (2^31 - 1). This key is to be provided in all subsequent records.proof
: An MD5 sum that provides proof to the client that it has logged in. This value must be correct for login to have succeeded. This value is calculated as md5sum(md5sum(password) + (" "
* 48) + username + serverChallenge + clientChallenge + md5sum(password))
, where md5sum
returns the MD5 message digest as a 32 character string of hexadecimal digits and letters in ASCII. The username
is equivalent to the authtoken
provided in the previous LOGIN record, while the serverChallenge
and clientChallenge
are the challenge
strings provided in the previous LOGIN_CHALLENGE and LOGIN records respectively. The password
is user-specific and, for Nintendo Wi-Fi Connection games, is the challenge
returned with the authtoken
received from nas.nintendowifi.net
.userid
: A unique integer user ID.profileid
: A unique integer profile ID. This number serves as the 32 least significant bits of the friend code. (The 7 most significant bits are a checksum)uniquenick
: A random 8-character string consisting of lowercase ASCII letters and digits. It is very likely that this must be unique for all users on the server.lt
: Probably a login ticket. A base64-encoded random 16-byte string with "="
, "+"
, and "/"
replaced with "_"
, "["
, and "]"
respectively, according to Leseratte of Wii-Homebrew.com, who claims that this is the case for Mario Kart Wii.id
: The sequence ID of the previous LOGIN record (usually 1)\getprofile\
): This record may be submitted by the client to get the content of a profile (i.e. associated with a friend code) by profileid.sesskey
: The session key.profileid
: The profileid to fetch data for.id
: The sequence ID of this record.\pi\
): This record is sent as a response to a GETPROFILE record from the the client.profileid
: The profileid of the profile.nick
: The nickname associated with this profile.userid
: The userid of the profile.email
: The e-mail address of the profile.sig
: An MD5 signature of the profile. It is not known how this signature is generated.uniquenick
: The unique nickname associated with the profile.pid
: Unknown.lastname
: May be omitted if not previously set. Probably the last name of the user.lon
: Longitude of the userlat
: Latitude of the userloc
: Location of the userid
: The sequence ID of the GETPROFILE record that prompted this record to be sent.\updatepro\
): This record may be appended to a GETPROFILE record (after its \final\
record terminator) to modify a user's profile. Note that, as it is does not have a corresponding response record (or perhaps because it is appended to a GETPROFILE record), there is no id
key in this record.sesskey
: The session key.lastname
: The lastname to set on the profile.\logout\
): This record is submitted to log out of the GameSpy service. The server will close the connection following its receipt of this record.sesskey
: The session key.\status\...
): The purpose of STATUS records is unknown, although the version number (marked ...
above) may take one of a number of integer values (1
appears to denote a transient state, such as "logging in" or "ending trade"; 6
seems to denote a standby announcement state; 5
seems to denote a state in which a game is attempting to join another game, and 2
seems to denote that a game has currently joined another game).sesskey
: The session key.statstring
: Probably a status string.locstring
: Probably a location string.\addbuddy\
): Notify the server that the user wishes to add a buddy to their server-side buddy list.sesskey
: The session key.newprofileid
: The profile ID of the buddy to be added.reason
: Presumably a string to be included in the ADDBUDDY_REQUEST to the buddy to be added. Always empty for Nintendo Wi-Fi Connection games.\delbuddy\
): Notify the server that a buddy should be removed from the server-side buddy list.sesskey
: The session key.delprofileid
: The profile ID of the buddy to be removed.\authadd\
): Notify the server that a request for a buddy to be added to a buddy list is authorized by the user who is to be added to the buddy list.sesskey
: The session key.fromprofileid
: The profile ID of the buddy who placed the original ADDBUDDY request.sig
: The 64-character (probably MD5 hash) signature code provided to the user in the ADDBUDDY_AUTHORIZED record which notified this user of the original ADDBUDDY request.ADDBUDDY_AUTHORIZED/PEER_MESSAGE (\bm\1
): This is sent by the server to a user which has previously submitted an ADDBUDDY record to notify them that the buddy who was requested to be added to the user's buddy list has authorized the request with an AUTHADD record.
This message may also be sent by a game (such as when accepting an offer to trade) in which case it is forwarded by the server to the recipient marked with the t
property after replacing the t
property with the f
property to indicate from whom the message came.
We distinguish between these two kinds of messages by calling the former (sent in response to an AUTHADD record) an ADDBUDDY_AUTHORIZED record, and the latter (sent as part of a server-mediated game-to-game message) a PEER_MESSAGE record.
As a type of BUDDYMESSAGE record, this shares the same basic fields:
t
: (Present only in messages sent to the server) The profile ID of the recipient of this PEER_MESSAGE.f
: (Present only in messages sent by the server) The profile ID of the buddy who sent the AUTHADD record or on whose behalf this PEER_MESSAGE record is being sent.date
: This optional field is sent only if the AUTHADD record that caused this record to be sent was sent while the recipient of this record was offline. If present, this contains a string representation of the Unix timestamp (in UTC) that the first AUTHADD record was sent since the user last logged in. Subsequent AUTHADD records for this same user are probably ignored for the purpose of this field.msg
: The body of the ADDBUDDY_AUTHORIZED/PEER_MESSAGE record. This message contains only a message body. For Nintendo Wi-Fi Connection ADDBUDDY_AUTHORIZED records, the body is always the C-string "I have authorized your request to add me to your list"
, for PEER_MESSAGE records, this is specified by the sender and unmodified in the PEER_MESSAGE record sent by the server to the recipient. There are no additional fields in the body, so no trailing pipe character is present.\bm\2
): Sent to the buddy that has been requested to be added to a buddy list by another user's ADDBUDDY record. As a type of BUDDYMESSAGE record, this shares the same basic fields:f
: The profile ID of the buddy who placed the original ADDBUDDY request.date
: This optional field is sent only if the ADDBUDDY record that caused this record to be sent was sent while the recipient of this record was offline. If present, this contains a string representation of the Unix timestamp (in UTC) that the first ADDBUDDY record was sent since the user last logged in. Subsequent ADDBUDDY records for this same user are ignored for the purpose of this field.msg
: The body of the ADDBUDDY_REQUEST record. This message contains, in order, a message body (for Nintendo Wi-Fi Connection records, the body is always the C-string "\r\n\r\n"
), followed by a pipe character (|
), followed by the following pipe-delimited key-value pairs:signed
: A 64-byte hexadecimal string (i.e. consisting of an ASCII hexadecimal representation of a 32-byte string). Probably an MD5 hash. Appears to be a constant for each user. This value is returned in any subsequent AUTHADD record.\bm\100
): This message is sent to a user to notify them of the initial status (and subsequent status changes) of buddies on the user's server-side buddy list. As a type of BUDDYMESSAGE record, this shares the same basic fields:f
: The profile ID of the buddy whose status this message represents.date
: This field is never sent in practice.msg
: The body of the BUDDY_STATUS record. This message contains, in order, a message body (for Nintendo Wi-Fi Connection records, the body is always the empty string), followed by a pipe character (|
), followed by the following pipe-delimited key-value pairs:s
: The type of status last sent by the buddy to the server (e.g. if a STATUS(1) record was sent, this is the C-string "1"
, if a STATUS(6) record was sent, this is the C-string "6"
, etc.). The C-string "0"
is used if the user is offline.ss
: The statstring of the last status message sent by the buddy to the server. If the user is offline, the value is Offline
ls
: The locstring of the last status message sent by the buddy to the server. Omitted if the BUDDY_STATUS record is being sent at login time and the buddy is offline.ip
: A string containing the decimal representation of the network-endian 32-bit integer representing the public IPv4 address of the user (i.e. inet_aton(external_ip)
). Omitted if the BUDDY_STATUS record is being sent at login time and the buddy is offline.p
: Probably a string containing the decimal representation of the network-endian 16-bit integer representing the TCP port of the user. In practice, this is always the C-string "0"
. Omitted if the BUDDY_STATUS record is being sent at login time and the buddy is offline.qm
: Unknown. Probably always the C-string "0"
. Omitted if the BUDDY_STATUS record is being sent at login time and the buddy is offline.\error\
): This message is sent to a client whenever there is an error in what the client has sent to the server.err
: The error code. The following error codes are known to exist:266
: There was an error validating the LOGIN challenge.1539
: The user has sent an ADDBUDDY record to add a buddy which is already present on the user's buddy list.fatal
: Optional. Has the empty string as a value. If specified, the error is a fatal error and the connection will be closed by the server following this message.errmsg
: An English-language string describing the error. Each error code appears to have a standard errmsg
value:266
: "There was an error validating the pre-authentication."
1539
: "The profile requested is already a buddy."
id
: Optional. If the record that created this record includes an ID number (e.g. error 266, during login), the ID number is returned.\ka\
): Sent by the server if the connection is idle (i.e. no records have been sent) for approximately 120 seconds. Presumably this is used to test whether the TCP socket should be left open (if there is an ACK from the game). The record has no fields.\lt\...
): Presumably updates the login ticket. Sent by the server to the client approximately every 250 seconds following login. This record has no fields, but the "value" of the field identifier (i.e. what is noted as ...
in the description) is an updated login ticket (as described in the LOGGED_IN record).In a number of places throughout the networking code (usually when starting the networking stack), the Nintendo DS will perform a test of the Nintendo Wi-Fi Connection by making a simple HTTP request of http://conntest.nintendowifi.net/ and checking for a 200 OK
response. The content of the response is not checked outside of the presence of a 200 OK
.
If the HTTP connection was refused by the server or invalid data is returned by the server, the DS will do a DNS lookup of conntest.nintendowifi.net three more times (making four attempts total) before giving up with error code 52203. Presumably, in doing these lookups, the DS is hoping to receive another IP address to try connecting to. However, it will not try recontacting the original server if its IP address happens to be returned in any of the subsequent DNS responses.
If a connection is made and dropped by the server or the server responds with an unexpected HTTP response code (e.g. 404 Not Found
), the DS will immediately respond with error code 52203 without trying the server again.
200 OK
response code.An example PCAP file is available.
47 45 54 20 2F 20 48 54 54 50 2F 31 2E 30 0D 0A
|
GET / HTTP/1.0..
|
GET / HTTP/1.0\r\n
- HTTP GET request for "/"Host: conntest.nintendowifi.net\r\n
- Host HTTP headerConnection: close\r\n
- Connection HTTP header\r\n
- HTTP header terminator
48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B 0D
|
HTTP/1.0 200 OK.
|
HTTP/1.0 200 OK\r\n
- HTTP 200 OK responseContent-type: text/html\r\n
- Content-type HTTP headerX-Organization: Nintendo\r\n
- Custom X-Organization HTTP headerServer: BigIP\r\n
- Server HTTP headerConnection: close\r\n
- Connection HTTP headerContent-Length: 246\r\n
- Content-Length HTTP header\r\n
- HTTP header terminatorNintendo Wi-Fi Connection for DS and Wii games uses GameSpy to manage account information. These accounts are managed automatically in the process of setting up your user information in a game and are why friend codes are not portable (as the GameSpy account is created specific to a game card and console, and can not be shared between multiple games). For similar reasons, the Pal Pad must be erased between consoles ("friends" are managed through the GameSpy protocol).
The process of setting up new Nintendo Wi-Fi Connection User Information, then, is a function of the GameSpy protocol and proceeds as follows:
NOTE: On rare occasions, the DS can enter a locked state if connections are refused to both pokemondpds.available.gs.nintendowifi.net and nas.nintendowifi.net, necessitating a hard reset.
It is also possible to get the DS into an inconsistent state if gpcm.gs.nintendowifi.net fails to return a valid LOGGED_IN response (e.g. if the proof-of-login is incorrect). In this case, the user must restart the DS with a soft reboot before Wi-Fi connections can be made. Until the user does so, all attempts to connect will fail at the connection test stage, as if the connection test server did not respond.
A number of error codes may be encountered at registration time, in addition to the codes that may arise from the connection test, including:
405 Method Not Allowed
)An example PCAP file is available (including SSL session keys).
(See GameSpy UDP Protocol for more details)
09 00 00 00 00 70 6F 6B 65 6D 6F 6E 64 70 64 73
|
.....pokemondpds
|
(See GameSpy UDP Protocol for more details)
FE FD 09 00 00 00 00
|
.......
|
Credit for interpreting the key-value pairs goes in part to Vetle, whose partial documentation of the login POST helped to explain some of the fields.
NOTE: This is normally encrypted with SSL.
50 4F 53 54 20 2F 61 63 20 48 54 54 50 2F 31 2E
|
POST /ac HTTP/1.
|
POST /ac HTTP/1.0\r\n
- HTTP POST request for "/ac"Content-type: application/x-www-form-urlencoded\r\n
- Content-type HTTP headerHost: nas.nintendowifi.net\r\n
- Host HTTP headerUser-Agent: Nitro WiFi SDK/1.2\r\n
- User-Agent HTTP headerHTTP_X_GAMECD: APAE\r\n
- Custom HTTP_X_GAMECD header containing the game code (e.g. APAE for the US Pokemon Pearl)Connection: close\r\n
- Connection HTTP headerContent-Length: 304\r\n
- Content-Length HTTP header\r\n
- HTTP header terminatorThe payload of this POST request is an application/x-www-form-urlencoded collection of key-value pairs. The values are encoded using base64 with the special character =
replaced by *
, presumably to avoid escaping it.
The key-value pairs in a new account request are:
action
- This is always the string login
gsbrcd
- Usage unknown, but probably stands for "GameSpy [something] CoDe". Appears to be constant with respect to a given save file, but it's at least partly dynamically generated (the first four characters are fixed to the original game code). It's possible that this code is randomly generated when the game detects that it is part of a new game-pak/DS pair (it does not, however, appear to be constant with respect to the same game-pak and DS). This field is left empty if logging into GTS.sdkver
- The version of the Nitro SDK used by the game, formatted using the format string "%03d%03d"
% (major, minor)
(e.g. 001002
for Nitro SDK 1.2)userid
- Usage unknown. A 13-digit string formatted using the format string "%013llu"
. Appears to be constant with respect to a given save file, but may be constant with respect to all identical ROM files.passwd
- Usage unknown. A 3-digit string formatted using the format string "%03u"
. Appears to be constant with respect to a given save file, but may be constant with respect to all identical ROM files.bssid
- The BSSID of the Wi-Fi AP that the DS is connected to as lower-case hex. (e.g. BSSID 00:0d:0b:f8:53:70 would be encoded as 000d0bf85370
)apinfo
- The index of the access point in the Nintendo Wi-Fi Connection Settings used for this connection, formatted using the format string "%02d:0000000-00"
. (e.g. 00:0000000-00
is used if this is the first AP in the DS settings) The special value 03:0000000-00
is used if the access point is a Nintendo Wi-Fi USB Connector.gamecd
- The game's code, as issued by Nintendo (e.g. APAE
)makercd
- Probably the code for the company that made the game, probably formatted using the format string "%02d"
(Nintendo uses 01
)unitcd
- Unknown. Always 0
macadr
- The MAC address of the Nintendo DS (e.g. 00:09:bf:c3:93:c8 would be encoded as 0009bfc393c8
)lang
- The language code (of the game? of the system?), probably formatted using the format string "%02d"
(English is 01
)birth
- The birth date of the user of the Nintendo DS, formatted using the format string "%02x%02x"
% (month, day)
(e.g. 071b
for July 27)devtime
- The local time of the Nintendo DS, as if formatted using the Unicode date format pattern "yyMMddHHmmss"
(e.g. 140312003606
for ISO date "2014-03-12T00:36:06")devname
- The name of the user of the Nintendo DS encoded in little-endian UTF-16 (UTF-16LE) (e.g. {0x4D
, 0x00
, 0x69
, 0x00
, 0x6B
, 0x00
, 0x65
, 0x00
}
for Mike
)ingamesn
- Same as devname
, omitted when logging into GTSIt is unclear which of these exactly serves as the key to lookup the newly created account later, although it appears that userid
, passwd
and gamecd
are sufficient (userid
and passwd
alone appear to only identify the DS device).
Credit for interpreting the key-value pairs goes in part to Vetle, whose partial documentation of the login POST helped to explain some of the fields.
NOTE: This is normally encrypted with SSL.
48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D
|
HTTP/1.1 200 OK.
|
HTTP/1.1 200 OK\r\n
- HTTP 200 OK responseNODE: wifiappw1\r\n
- Custom NODE HTTP header (probably used to debug load-balancing issues, as it may randomly take either wifiappw1
or wifiappw2
as a value)Content-type: text/plain\r\n
- Content-type HTTP headerContent-Length: 287\r\n
- Content-Length HTTP headerDate: Sun, 09 Mar 2014 00:24:16 GMT\r\n
- Date HTTP headerConnection: close\r\n
- Connection HTTP headerServer: Nintendo Wii (http)\r\n
- Server HTTP header\r\n
- HTTP header terminatorLike the payload of the POST request, the body of this HTTP response is an application/x-www-form-urlencoded collection of key-value pairs. Again, the values are encoded using base64 with the special character =
replaced by *
.
The key-value pairs in a new account request response are:
challenge
- A random 8-character challenge string (upper-case ASCII only) to be used as the password when logging in to GameSpy for this sessionlocator
- Usage unknown. Always gamespy.com
retry
- Probably a flag to retry creating the account due to an error. In practice, this is always 0
, presumably signifying that no retry was needed. If the request data is incorrect (e.g. if the password doesn't match Nintendo's records), this value will be 1
and only returncd
and datetime
will be included in the response.returncd
- Probably a return code to signify success or failure. In practice, this is always 001
, presumably signifying success. It is possible to get a value 109
if there was an error in interpreting the request (e.g. if the password presumably doesnt match the username) but the exact meaning of this code is unknown. In this case, retry
will be 1
and all other fields except datetime
will be missing.token
- An authentication token to present to the GameSpy servers as the username for this session. Appears to be a random string of 96 bytes, which is then base64-encoded and prefixed by "NDS"
datetime
- The time of the server in GMT, as if formatted using the Unicode date format pattern "yyyyMMddHHmmss"
(e.g. 20140312053512
for ISO date "2014-03-12T05:35:12")NOTE: Pokémon Pearl does not appear to check the NODE, Content-type, or Server headers, so they need not be correct (or present).
(See GameSpy TCP Protocol for more details)
5C 6C 63 5C 31 5C 63 68 61 6C 6C 65 6E 67 65 5C
|
\lc\1\challenge\
|
\lc\1
- LOGIN_CHALLENGE record type\challenge\SEXHIYECYY
- Random challenge string consisting of 10 uppercase ASCII characters (SEXHIYECYY)\id\1
- The sequence number (1)\final\
- Record terminator(See GameSpy TCP Protocol for more details)
NOTE: This record is not actually sampled from the same sequence as the previous LOGIN_CHALLENGE record, so the correct response
value for that record may differ.
5C 6C 6F 67 69 6E 5C 5C 63 68 61 6C 6C 65 6E 67
|
\login\\challeng
|
\login\
- LOGIN record type\challenge\TkEr4NFvA4fsERpcIgiDrUP1QyMCtlJo
- Random challenge string consisting of 32 ASCII upper-case and lower-case letters and digits\authtoken\NDSX0zyY6Wc6SQ6GnvXStABwbFCBjgt+MVQyhs1vMO5qsMnBePlcnGOjjPTcloogWX03yHVP9Q5xnUms8jZUzyd2W9ytWFtlwUOhAcO0x9WfFv2qPNFNr9O0ehktRYRcv89
- The authtoken
returned by nas.nintendowifi.net
\response\cc1da89485b77adee231a6a87833b996
- A response to the challenge
token presented in the previous LOGIN_CHALLENGE record. The server will validate this value. This value is calculated as md5sum(md5sum(challenge) + (" "
* 48) + authtoken + clientChallenge + serverChallenge + md5sum(challenge))
, where md5sum
returns the MD5 message digest as a 32 character string of hexadecimal digits and letters in ASCII. challenge
and authtoken
should be the values returned by nas.nintendowifi.net
. serverChallenge
and clientChallenge
, on the other hand, are the challenge strings provided in the previous LOGIN_CHALLENGE and this LOGIN record respectively.\firewall\1
- Probably indicates whether there is a firewall (or NAT?) between the server and the client. Can probably be ignored.\port\0
- Probably the local port on the DS used for hosting games. Always 0
in practice.\productid\10727
- The GameSpy unique product ID for this game. 10727 for Pokémon Pearl, and possibly Diamond as well.\gamename\pokemondpds
- The GameSpy unique gamename for this game. pokemondpds
for Pokémon Pearl, and probably Diamond as well.\namespaceid\0
- Usage unknown; always 0
\id\1
- The sequence number of the previous LOGIN_CHALLENGE record (1)\final\
- Record terminator(See GameSpy TCP Protocol for more details)
5C 6C 63 5C 32 5C 73 65 73 73 6B 65 79 5C 32 31
|
\lc\2\sesskey\21
|
\lc\2
- LOGGED_IN record type\sesskey\210375287
- Unique signed 32-bit integer to use as session key by the client. Values greater than (2^31 - 1) will be floored to (2^31 - 1)) by the client. This key is included in all subsequent records, although it does not appear to be validated. Nintendo serves increasing values of this key.\proof\f84ed94d88f19722ede4e023eae54a00
- A proof-of-login token calculated by the server. This value must be correct for Pokémon Pearl to log in. This value is calculated as md5sum(md5sum(challenge) + (" "
* 48) + authtoken + serverChallenge + clientChallenge + md5sum(challenge))
, where md5sum
returns the MD5 message digest as a 32 character string of hexadecimal digits and letters in ASCII. challenge
and authtoken
should be the values returned by nas.nintendowifi.net
. serverChallenge
and clientChallenge
, on the other hand, are the challenge
strings provided in the previous LOGIN_CHALLENGE and LOGIN records respectively.\userid\443357202
- A unique userid. Unclear how this value is calculated, but uniqueness is probably sufficient.\profileid\475475956
- The unique profileid. It does not need to be the same as the userid. This ID serves as the 32 least-significant bits of the friend code. The highest 7 bits of the friend code are a checksum.\uniquenick\1e1bf1kj1ADAJ20m3lj3
- A unique nickname for the user. This appears to be a partially random set of 32 ASCII upper- and lower-case letters and digits, where the last 11 characters are fixed to the gsbrcd
value passed to nas.nintendowifi.net
. Pokémon Pearl does not appear to validate this however. The first 9 characters appear to be specific to the DS console (the same characters are provided when any game is registered). Both the DS and server appear to be aware of what the value is independent of each other, so it may be related to the username or password of the request to nas.nintendowifi.net.\lt\Ne[EiaLbCydDhYmM]OHXac__
- A unique login ticket for this session. Appears to be a base64-encoded random 16-byte string with "="
, "+"
and "/"
replaced by "_"
, "["
and "]"
respectively. It is not known if this ticket has any significance.\id\1
- The sequence number of the previous LOGIN record (1)\final\
- Record terminator(See GameSpy TCP Protocol for more details)
5C 67 65 74 70 72 6F 66 69 6C 65 5C 5C 73 65 73
|
\getprofile\\ses
|
\getprofile\
- GETPROFILE record type\sesskey\210375287
- The session key\profileid\475475956
- The profileid of the profile to get (when creating an account, this is the profileid returned by the LOGGED_IN record).\id\2
- The sequence number of this request (2)\final\
- Record terminator(See GameSpy TCP Protocol for more details)
5C 70 69 5C 5C 70 72 6F 66 69 6C 65 69 64 5C 34
|
\pi\\profileid\4
|
\pi\
- PROFILEINFO record type\profileid\475475956
- profileid of the requested profile\nick\1e1bf1kj1ADAJ20m3lj3
- nickname of the requested profile (same as uniquenick
)\userid\443357202
- userid of the requested profile (does not need to be the same as the profileid, but should be consistent with it)\email\1e1bf1kj1ADAJ20m3lj3@nds
- E-mail address of the requested profile. In practice, this is always uniquenick + "@nds"
.\sig\8048f02092214a527427b51b91c39804
- Probably a signature of the profile info. Pokémon Pearl does not appear to validate the signature.\uniquenick\1e1bf1kj1ADAJ20m3lj3
- uniquenick of the requested profile\pid\11
- Usage unknown; always 11
\lon\0.000000
- Longitude of the user. Always 0.000000
\lat\0.000000
- Latitude of the user. Always 0.000000
\loc\
- Location of the user. Always empty.\id\2
- The sequence number of the previous GETPROFILE record (2)\final\
- Record terminatorThis is the default state of a profile when it has been initially created.
(See GameSpy TCP Protocol for more details)
5C 67 65 74 70 72 6F 66 69 6C 65 5C 5C 73 65 73
|
\getprofile\\ses
|
\getprofile\
- GETPROFILE record type\sesskey\210375287
- The session key\profileid\475475956
- The profileid of the profile to get (when creating an account, this is the profileid returned by the LOGGED_IN record).\id\3
- The sequence number of this request (3)\final\
- Record terminator\updatepro\
- UPDATE_PROFILE record type\sesskey\210375287
- The session key\lastname\1e1bf1kj1ADAJ20m3lj3
- Lastname of the player. The reason for setting its value (or how it is generated) is unknown, but it appears to be set to the same value as the uniquenick
. On occasion, this value may be different, however, but the exact circumstances in which this value is different are unknown (it may be related to limited circumstances in which the Pal Pad is preserved, or when a Union Plaza friend has been made before a Pal Pad has been registered with Nintendo Wi-Fi Connection).\final\
- Record terminator(See GameSpy TCP Protocol for more details)
5C 70 69 5C 5C 70 72 6F 66 69 6C 65 69 64 5C 34
|
\pi\\profileid\4
|
\pi\
- PROFILEINFO record type\profileid\475475956
- profileid of the requested profile\nick\1e1bf1kj1ADAJ20m3lj3
- nickname of the requested profile (same as uniquenick
)\userid\443357202
- userid of the requested profile (does not need to be the same as the profileid, but should be consistent with it)\email\1e1bf1kj1ADAJ20m3lj3@nds
- E-mail address of the requested profile. In practice, this is always uniquenick + "@nds"
.\sig\8048f02092214a527427b51b91c39804
- Probably a signature of the profile info. Pokémon Pearl does not appear to validate the signature.\uniquenick\1e1bf1kj1ADAJ20m3lj3
- uniquenick of the requested profile\pid\11
- Usage unknown; always 11
\lastname\1e1bf1kj1ADAJ20m3lj3
- Lastname of the user. Previously set by the client in a UPDATE_PROFILE record.\lon\0.000000
- Longitude of the user. Always 0.000000
\lat\0.000000
- Latitude of the user. Always 0.000000
\loc\
- Location of the user. Always empty.\id\4
- The sequence number of the previous GETPROFILE record (4; I have omitted the response to record 3 and the GETPROFILE request of 4 for brevity)\final\
- Record terminator(See GameSpy TCP Protocol for more details)
5C 6C 6F 67 6F 75 74 5C 5C 73 65 73 73 6B 65 79
|
\logout\\sesskey
|
\logout\
- LOGOUT record type\sesskey\210375287
- The session key\final\
- Record terminator(See GameSpy UDP Protocol for more details)
03 82 2B 3E 31 6C 6F 63 61 6C 69 70 30 00 31 39
|
..+>1localip0.19
|
The payload of this request is as expected for an initial HEARTBEAT datagram, with the publicip
and publicport
properties initially set to 0
.
The properties that are initially set include:
localip0
- The local IP address of the DSlocalport
- Unknown. May be the UDP port on the DS that the packet was sent from, but it is unclear if this is the case.natneg
- Unknown. May always be 1
.statechanged
- Unknown. May always be 1
.gamename
- The GameSpy game name. For Pokémon Pearl (and presumably Diamond), this is pokemondpds
.publicip
- Initially 0
.publicport
- Initially 0
.numplayers
- Initially 0
.maxplayers
- Initially 0
.unknown
- Five unknown
keys are eventually replaced with dwc_pid
, dwc_mtype
, dwc_mresv
, and dwc_eval
, in that order, but take their respective values immediately, except for dwc_mtype
, which is initially 0
. The meaning of their values is unknown, except for dwc_pid
, which is the profileid
of the user.(See GameSpy TCP Protocol for more details)
5C 73 74 61 74 75 73 5C 31 5C 73 65 73 73 6B 65
|
\status\1\sesske
|
\status\1
- STATUS(1) record type\sesskey\210375303
- The session key. Note that it differs from the previous session key, because the DS has logged out and logged back in.\statstring\
- Probably a status string. Initially empty.\locstring\
- Probably a location string. Initially empty.\final\
- Record terminator(See GameSpy UDP Protocol for more details)
FE FD 01 82 2B 3E 31 30 4C 7B 6F 7E 5A 30 30 30
|
....+>10L{o~Z000
|
0x21
and 0x7F
inclusive.inet_aton
. For example the IP 10.0.0.6 becomes 0A000006
.htons
. For example port 40726 becomes 9F16
.(See GameSpy UDP Protocol for more details)
01 82 2B 3E 31 55 71 35 72 45 70 68 79 75 30 48
|
..+>1Uq5rEphyu0H
|
(See GameSpy UDP Protocol for more details)
FE FD 0A 3D B0 A8 D6 00 00 00 00 00 00 00 00 00
|
...=............
|
5C 73 74 61 74 75 73 5C 36 5C 73 65 73 73 6B 65
|
\status\6\sesske
|
\status\6
- STATUS(6) record type\sesskey\210375303
- The session key. Note that it differs from the previous session key, because the DS has logged out and logged back in.\statstring\/SCM/2/SCN/1/VER/3
- Probably a status string. Value is unknown, although Wiimm claims that the last "VER" value represents dwc_mver
.\locstring\
- Probably a location string.\final\
- Record terminator(See GameSpy UDP Protocol for more details)
08 82 2B 3E 31
|
..+>1
|
(See GameSpy UDP Protocol for more details)
03 82 2B 3E 31 6C 6F 63 61 6C 69 70 30 00 31 39
|
..+>1localip0.19
|
The payload of this request is shorter than the initial HEARTBEAT, perhaps because it means that the game is logging out.
The properties that are set include:
localip0
- The local IP address of the DSlocalport
- Unknown. May be the UDP port on the DS that the packet was sent from, but it is unclear if this is the case.natneg
- Unknown. May always be 1
.statechanged
- Unknown. Changed to 2
, perhaps to signal logging out.gamename
- The GameSpy game name. For Pokémon Pearl (and presumably Diamond), this is pokemondpds
.publicip
- The public IP previously returned in the initial CHALLENGE_RESPONSE datagram from the server, reinterpreted as a little-endian 4-byte integer (e.g. 10.0.0.6 = 0A000006
= 0x0600000A
= 100663306
, not 0x0A000006
= 167772166
as might be assumed).publicport
- The public port previously returned in the initial CHALLENGE_RESPONSE datagram from the server.The basics of this protocol have been previously documented on the Project Pokemon Wiki and can be derived from a number of applications designed to re-implement GTS. As such, I do not intend to detail this protocol here until other, currently undocumented protocols have been documented.
That said, it should be noted that initial contact to http://gamestats2.gs.nintendowifi.net/pokemondpds/worldexchange/info.asp?pid=[PID] is preceded by the standard login protocol of first contacting conntest.nintendowifi.net and then contacting nas.nintendowifi.net. The POST payload to nas differs from that used when connecting to the Wi-Fi Club in that the gsbrcd value is left empty (because the GameSpy TCP protocol is not used)
The registration mechanism above is effectively a special variant of the more general-purpose login mechanism, which is as follows:
gsbrcd
field is not specified if the game is logging into GTS.Logging out is simpler, and follows the last step of the registration mechanism. Namely:
If there is an error in the LOGIN record (e.g. if the challenge value is incorrect), an ERROR record (error code 266) will be sent to the client and the server will close the connection.
Several additional records are common to all connections. gpcm.gs.nintendowifi.net will send the client a KEEPALIVE record to the game if there are no communications between the two for approximately 120 seconds. No record is sent in response (a TCP ACK is sufficient to notify the server that the connection is still alive).
In addition, a LOGIN_TICKET record will be sent to the game approximately every 250 seconds following a successful login.
(TODO: What is the use of gpsp? \search
and \bsrdone\
records on gpsp)
5C 73 74 61 74 75 73 5C 31 5C 73 65 73 73 6B 65
|
\status\1\sesske
|
\status\1
- STATUS(1) record type\sesskey\210375303
- The session key.\statstring\
- Probably a status string. Initially empty.\locstring\lQGCAe0B9ABjAWMBAAAAAAAAAAAAAAAAAAEBEAD-AAAAAQEA
- Probably a location string. In the second STATUS(1) record sent, this contains the initial location string. Details about this string will be provided later on.\final\
- Record terminatorAt login time, any DELBUDDY records and ADDBUDDY records will be prepended to this record. Also, the following record will be appended.
5C 73 74 61 74 75 73 5C 36 5C 73 65 73 73 6B 65
|
\status\6\sesske
|
\status\6
- STATUS(6) record type\sesskey\210375303
- The session key.\statstring\/SCM/2/SCN/1/VER/3
- Probably a status string. Value is unknown, although Wiimm claims that the last "VER" value represents dwc_mver
.\locstring\lQGCAe0B9ABjAWMBAAAAAAAAAAAAAAAAAAEBEAD-AAAAAQEA
- Probably a location string. In the second STATUS(1) record sent, this contains the initial location string. Details about this string will be provided later on.\final\
- Record terminatorAt login time, the previous STATUS(1) record will be prepended.
5C 65 72 72 6F 72 5C 5C 65 72 72 5C 32 36 36 5C
|
\error\\err\266\
|
\error\
- ERROR record type\err\266
- The error code (266)\fatal\
- The error is a fatal error and the server will close the connection following this error.\errmsg\There was an error validating the pre-authentication.
- An English-language representation of the error (this value is always the same for error code 266).\id\1
- The ID of the previous record send by the client (the LOGIN record)\final\
- Record terminator
5C 6B 61 5C 5C 66 69 6E 61 6C 5C
|
\ka\\final\
|
\ka\
- KEEPALIVE record type\final\
- Record terminator
5C 6C 74 5C 52 70 69 48 43 47 42 4A 5B 61 73 54
|
\lt\RpiHCGBJ[asT
|
\lt\RpiHCGBJ[asTE3WJhrDYHe__
- LOGIN_TICKET record type with new login ticket value. This value is not the same login ticket as the previously provided one. Appears to be a base64-encoded random 16-byte string with "="
, "+"
and "/"
replaced by "_"
, "["
and "]"
respectively. It is not known if this ticket has any significance.\final\
- Record terminatorAdding and removing friends through the Pal Pad is a process that requires confirmation not only from the server but from the desired friend as well before the friend can be considered "fully registered". For the purpose of this explanation, I will refer to two friends, named Alice and Bob.
The process begins when Alice adds Bob's friend code to her Pal Pad. Once Bob has been registered on her Pal Pad, at her next login to the Nintendo Wi-Fi Connection (and every subsequent login until Bob is either "fully registered" or Alice removes Bob's entry from the Pal Pad), Alice's game will send an ADDBUDDY record regarding Bob to gpcm.gs.nintendowifi.net together with her initial STATUS(1) and STATUS(6) records (i.e. those with the locstring set). This ADDBUDDY record includes Bob's profile ID (i.e. the lower 32 bits of his friend code).
Upon receiving the ADDBUDDY record, the server will check to see if Bob is online. If so, Bob will immediately receive an ADDBUDDY_REQUEST record (i.e. a BUDDYMESSAGE_2 record). If Bob is not online, he will receive an ADDBUDDY_REQUEST record at his next login. This delayed record will include the timestamp of the first time that Alice sent the server an ADDBUDDY_REQUEST since the previous time Bob logged in. Only one such delayed record will be received by Bob regardless of the number of times that Alice logs in and sends an ADDBUDDY_REQUEST about Bob.
After Bob receives the ADDBUDDY_REQUEST record, he will send the server a GETPROFILE record requesting Alice's profile, to which the server will respond with her PROFILEINFO record. He will always request such a record, even if Alice is not listed in his Pal Pad.
If Alice is also listed in Bob's Pal Pad, Bob's game will then send an AUTHADD and an ADDBUDDY record to the server to authorize Alice's request and then request that Alice add Bob as a buddy as well.
Once the server processes the AUTHADD, the registration process is partially complete. Alice will now be able to see Bob's status until she removes Bob from her Pal Pad (even if Bob removes Alice from his Pal Pad). The server will immediately send a BUDDY_STATUS record including Bob's most recent status to Alice.
In addition, the server will send an ADDBUDDY_AUTHORIZED record to Alice after which Alice's game will officially mark Bob as registered (her game will no longer send an ADDBUDDY record for Bob on any subsequent login unless she has removed Bob from her Pal Pad). Appended to this record will be an ADDBUDDY_REQUEST record from Bob. Like the original ADDBUDDY_REQUEST record sent to Bob, these records will be held on the server if Alice is not logged in, and will be sent to Alice dated with the date they were initially sent to the server at her next login. This allows for Bob and Alice to mark each other as buddies without having to be on at the same time.
Alice will respond like Bob did, first requesting a GETPROFILE record for Bob and receiving a PROFILEINFO record from the server containing Bob's information. For an unknown reason, as the second person to receive an ADDBUDDY_REQUEST, Alice will actually place two GETPROFILE records with separate request IDs (and consequently get two separate PROFILEINFO records). It's possible that Alice requests the first due to the ADDBUDDY_AUTHORIZED record.
Assuming that Alice has added Bob as a buddy to her Pal Pad (which should go without saying, given that she started the process), she will similarly submit an AUTHADD record and (oddly enough) an ADDBUDDY record to the server.
Again, Bob will immediately be informed of Alice's status in a BUDDY_STATUS record and then be sent an ADDBUDDY_AUTHORIZED record.
Unlike when Alice received Bob's ADDBUDDY_AUTHORIZED record, however, Bob does not receive the ADDBUDDY_REQUEST record. Instead, the server responds to Alice with an ERROR record (error code 1539) indicating that Bob has already been added as a buddy.
Bob will finalize his registration of Alice as a buddy by sending a GETPROFILE record for Alice to the server and receiving a PROFILEINFO record in return.
Removing a buddy is much simpler than adding a buddy. At the first login following Alice's removal of Bob from her Pal Pad, her game will send a DELBUDDY record, at which point the server will delete Bob from Alice's buddy list and no longer send status updates about Bob to Alice.
Alice will not send a DELBUDDY record if she has not finished marking Bob as a friend on the server (i.e. she has not received an ADDBUDDY_AUTHORIZED message). Should Alice receive an ADDBUDDY_AUTHORIZED message after this (if, for example, Bob only logs in and responds to the original ADDBUDDY_REQUEST record with an AUTHADD record of his own after Alice has already removed him from her Pal Pad), she will immediately respond to the server with a DELBUDDY message.
Removing a buddy using DELBUDDY is a one-way operation. Bob may still see Alice's status (and will be informed of her status changes) until he removes Alice from his Pal Pad. However, Alice will not respond to any BUDDYMESSAGE records originating from Bob unless Bob is in her Pal Pad. This is why the Nintendo Wi-Fi Connection gives an ambiguous error as to whether a "failure to connect" is due to a real connection failure or due to Alice having removed Bob from her Pal Pad. In both cases, Alice will fail to respond.
NOTE: It is possible for Bob to send two ADDBUDDY records, if he processes Alice's ADDBUDDY_REQUEST record at login time before Bob has had the chance to send his own ADDBUDDY record for Alice to the server. In this case, depending on whether the server has sent an Bob's first ADDBUDDY_REQUEST record to Alice (or perhaps whether the server has seen an AUTHADD in response) the server will send one or two ADDBUDDY_REQUEST records to Alice. Each ADDBUDDY_REQUEST will be responded to, regardless of whether one has been processed previously (and Bob may thus receive two ADDBUDDY_AUTHORIZED records and respond appropriately).
If, at any point, the server stops sending BUDDY_STATUS records for Alice to Bob, Bob will return to the initial state and will send ADDBUDDY records to the server regarding Alice until BUDDY_STATUS records begin to be sent once more.
5C 61 64 64 62 75 64 64 79 5C 5C 73 65 73 73 6B
|
\addbuddy\\sessk
|
\addbuddy\
- ADDBUDDY record type\sesskey\210375303
- The session key.\newprofileid\475776775
- The profile ID of the buddy to add to the user's buddy list.\reason\
- Probably a text string offering a reason to add the buddy. Always empty.\final\
- Record terminator
5C 62 6D 5C 32 5C 66 5C 34 37 35 37 37 36 37 37
|
\bm\2\f\47577677
|
\bm\2
- ADDBUDDY_REQUEST record type\f\475776775
- The profile ID of the buddy who requested that the user be added to their buddy list.\msg\....|signed|40f3d0689abf2ac466e30bb0b40719a1
- The body of the BUDDYMESSAGE. The message itself is always "\r\n\r\n"
but is followed by a signed
subfield. This subfield's value appears to be a constant MD5 hash value for a given requestor-buddy pair, but a server may return a random 64-byte hexadecimal string.\final\
- Record terminator
5C 62 6D 5C 32 5C 66 5C 34 37 35 37 37 36 37 37
|
\bm\2\f\47577677
|
\bm\2
- ADDBUDDY_REQUEST record type\f\475776775
- The profile ID of the buddy who requested that the user be added to their buddy list.\date\1394923862
- The date that the other user first sent an ADDBUDDY record since this user last logged in.\msg\....|signed|40f3d0689abf2ac466e30bb0b40719a1
- The body of the BUDDYMESSAGE. The message itself is always "\r\n\r\n"
but is followed by a signed
subfield. This subfield's value appears to be a constant MD5 hash value for a given requestor-buddy pair, but a server may return a random 64-byte hexadecimal string.\final\
- Record terminator
5C 61 75 74 68 61 64 64 5C 5C 73 65 73 73 6B 65
|
\authadd\\sesske
|
\bm\2
- AUTHADD record type\sesskey\210375303
- The session key.\fromprofileid\475776775
- The profile ID of the user who requested the user be added to their buddy list.\sig\40f3d0689abf2ac466e30bb0b40719a1
- The signed
value given in the preceding ADDBUDDY_REQUEST record.\final\
- Record terminator\addbuddy\
- ADDBUDDY record type\sesskey\210375303
- The session key.\newprofileid\475776775
- The profile ID of the buddy to add to the user's buddy list.\reason\
- Probably a text string offering a reason to add the buddy. Always empty.\final\
- Record terminator
5C 62 6D 5C 31 5C 66 5C 34 37 35 37 37 36 37 37
|
\bm\1\f\47577677
|
\bm\1
- ADDBUDDY_AUTHORIZED record type\f\475776775
- The profile ID of the user who authorized an ADDBUDDY request.\msg\I have authorized your request to add me to your list
- The text message associated with the authorization (always I have authorized your request to add me to your list
\final\
- Record terminator
5C 65 72 72 6F 72 5C 5C 65 72 72 5C 31 35 33 39
|
\error\\err\1539
|
\error\
- ERROR record type\err\1539
- The error code (1539)\errmsg\The profile requested is already a buddy.
- An English-language representation of the error (this value is always the same for error code 1539).\final\
- Record terminator
5C 64 65 6C 62 75 64 64 79 5C 5C 73 65 73 73 6B
|
\delbuddy\\sessk
|
\delbuddy\
- DELBUDDY record type\sesskey\210375303
- The session key.\delprofileid\475776775
- The profile ID to remove from the buddy list.\final\
- Record terminatorIf the server knows Bob to be on Alice's Pal Pad (because she has previously successfully marked him as a friend and completed the process by receiving an ADDBUDDY_AUTHORIZED record), Alice will receive a BUDDY_STATUS record for Bob from the server every time she logs in or whenever Bob changes his status using a STATUS record while she is logged in. These messages will be sent until Alice has completed the removal of Bob from her Pal Pad by sending a DELBUDDY record to the server. Bob may remove Alice from his Pal Pad, but Alice will continue to receive BUDDY_STATUS records regarding his activity following any DELBUDDY record he may send.
The BUDDY_STATUS records include all information sent in the corresponding STATUS record that generated them. This usually includes the locstring, which appears to contain the most significant information about the status of the player, including the player's sprite and the player's status (e.g. requesting a trade, requesting a single battle, etc.). However, the server need not be aware of how this string is interpreted, but must only be able to save this state to be echoed to any buddy of the player who logs in.
Any user who changes their status (e.g. from standby to requesting a trade, cancelling such a request, etc.) will send a new STATUS(6) record with an updated locstring indicating the change in status.
When a player sends a LOGOUT record, a BUDDY_STATUS record indicating that the buddy logged out is sent to anyone logged in who has the player marked as a buddy. While logged out, a shorter logged out BUDDY_STATUS record will be sent at login time to any buddy who logs in while the user is logged out.
The value of locstring for Pokémon games is not well understood, but it appears to be two separate base64 strings, the first 40 bytes long, the second 8 bytes long, concatenated together. In both encodings, the "="
character appears to be replaced by "-"
.
The initial bytes appear to be a string of 16-bit little-endian integers and may represent some encoding of the player's name, but this does not appear to agree with any known text encoding for the Pokémon games.
Byte 27 (i.e. the 28th byte) indicates the current status of the user and what, if anything, the user is requesting to do. The known values for this byte include:
0x01
: Voice Chat0x02
: (Possibly) In Lv. 50 Single0x03
: (Possibly) In Lv. 100 Single0x04
: Battling (In Free Single)0x05
: (Possibly) In Lv. 50 Double0x06
: (Possibly) In Lv. 100 Double0x07
: (Possibly) In Free Double0x08
: Trading (In Trade)0x09
: Lv. 50 Single0x0A
: Lv. 100 Single0x0B
: Free Single0x0C
: Lv. 50 Double0x0D
: Lv. 100 Double0x0E
: Free Double0x0F
: Trade0x10
: Standby (i.e. no current request)0x12
: Cooking (In Poffin Cook) (Platinum only)0x13
: Poffin Cook (Platinum only)0x16
: In Swalot Plop (Platinum, HeartGold and SoulSilver only)0x17
: Swalot Plop (Platinum, HeartGold and SoulSilver only)0x18
: In Mime Jr. Top (Platinum, HeartGold and SoulSilver only)0x19
: Mime Jr. Top (Platinum, HeartGold and SoulSilver only)0x1A
: In Wobbuffet Pop (Platinum, HeartGold and SoulSilver only)0x1B
: Wobbuffet Pop (Platinum, HeartGold and SoulSilver only)Byte 29 (i.e. the 30th byte) is optional (it may be missing if the first base64 string terminates early with a "-"
character). If present, it indicates the trainer sprite displayed in the Pal Pad (or, presumably, in Platinum, HeartGold, and SoulSilver, the sprite displayed in the Wi-Fi Club lobby). Known values of this byte include:
0x03
: School Kid ♂0x05
: Bug Catcher0x0B
: Ace Trainer ♂0x1F
: RoughneckIt is very probable that other values of this byte value match the byte values used in the ActionReplay code which changes the character's sprite in Pokémon Pearl and HeartGold/SoulSilver, as the above four values match those in the code lists.
It is also probable that the trainer ID (or at least the last several binary digits of it) and gender is encoded in the base64 string to allow for the default sprite to be calculated if byte 29 is missing (unfortunately, the algorithm for deriving the default avatar is no longer posted on MetalKid's website). For example, in the locstring value above, "lQGCAe0B9ABjAWMBAAAAAAAAAAAAAAAAAAEBEAD-AAAAAQEA"
, the player is displayed as a School Kid ♂, which is the default for a male trainer with trainer ID (00000).
Upon seeing a request for a battle or trade in a friend's locstring, a user may accept the request to start the battle or trade. This is done as follows (assuming that Alice is announcing, and Bob is accepting the announcement):
1vTlwb
for Pokémon Diamond and Pearl).{0x4C
, 0xA4
, 0x97
}
, or a continuation of the previous encrypted stream ({0xB0
, 0x9A
, 0x2C
}
), to which the client appears to respond {0x00
, 0x03
, 0x03
}
/SCM/2/SCN/2/VER/3
instead of /SCM/2/SCN/1/VER/3
0x08
)dwc_mresv
field and a dwc_mtype
value of 3
./SCM/2/SCN/1/VER/3
statstring)
5C 62 6D 5C 31 30 30 5C 66 5C 34 37 35 34 37 35
|
\bm\100\f\475475
|
\bm\100
- BUDDY_STATUS record type\f\475475956
- The profile ID of the buddy whose status is being reported.\msg\|s|6|ss|/SCM/2/SCN/1/VER/3|ls|lQGCAe0B9ABjAWMBAAAAAAAAAAAAAAAAAAEBEAD-AAAAAQEA|ip|167772166|p|0|qm|0
- The body of the BUDDYMESSAGE. The message itself is always the empty string but is followed by several key-value subfields:s|6
- The STATUS id of the last STATUS record sent to the server by the buddyss|/SCM/2/SCN/1/VER/3
- The statstring of the last STATUS record sent to the server by the buddyls|lQGCAe0B9ABjAWMBAAAAAAAAAAAAAAAAAAEBEAD-AAAAAQEA
- The locstring of the last STATUS record sent to the server by the buddyip|167772166
- The last public IP of the user interpreted as a big-endian 32-bit integer.p|0
- The last public port of the user (always 0).qm|0
- Unknown. Appears to always be 0.\final\
- Record terminatorNOTE: Unlike the other examples in this section, this is not one which would be received by profile ID 475475956, but rather, is an example of a BUDDY_STATUS record that might be sent to 475475956's buddies as a result of this STATUS(6) record.
5C 62 6D 5C 31 30 30 5C 66 5C 34 37 35 37 37 36
|
\bm\100\f\475776
|
\bm\100
- BUDDY_STATUS record type\f\475776775
- The profile ID of the buddy whose status is being reported.\msg\|s|0|ss|Offline|ls||ip|167772166|p|0|qm|0
- The body of the BUDDYMESSAGE. The message itself is always the empty string but is followed by several key-value subfields:s|0
- The STATUS id (0
for logged-out)ss|Offline
- The statstring (Offline
if logged out)ls|
- The locstring (empty if logged-out)ip|167772166
- The last public IP of the user interpreted as a big-endian 32-bit integer.p|0
- The last public port of the user (always 0).qm|0
- Unknown. Appears to always be 0.\final\
- Record terminator
5C 62 6D 5C 31 30 30 5C 66 5C 34 37 35 37 37 36
|
\bm\100\f\475776
|
\bm\100
- BUDDY_STATUS record type\f\475776775
- The profile ID of the buddy whose status is being reported.\msg\|s|0|ss|Offline
- The body of the BUDDYMESSAGE. The message itself is always the empty string but is followed by several key-value subfields (some which are normally present are omitted when the buddy is logged out when a user is logging in):s|0
- The STATUS id (0
for logged-out)ss|Offline
- The statstring (Offline
if logged out)\final\
- Record terminatorTODO
TODO
NOTE: This is the counterpart to the previous record, sent from the server to the game currently offering to trade. Note that the t
field has been rewritten as the f
field.
TODO
NOTE: Though not depicted here, there is, of course, a forwarded copy of this message that is sent to the recipient in the t
field.
TODO
NOTE: Though not depicted here, there is, of course, a forwarded copy of this message that is sent to the recipient in the t
field.
TODO
TODO
NOTE: This message is normally encrypted using the "enctypeX" encryption described by Luigi Auriemma with the 8-byte validation string defined in the previous FIND_PEER message and the 6-byte game key (1vTlwb
for Pokémon Diamond and Pearl). It has been decrypted here to show the contents of the message.
TODO
NOTE: For unknown reasons, this message is typically divided into two TCP packets, where the header is sent in the first packet and the body in the second.
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
While Pokémon Platinum's use of Nintendo Wi-Fi Connection is fundamentally similar to that of Diamond and Pearl (as one would expect given its interoperability with the original games), several new features are introduced in Platinum that differ from its predecessors:
HTTP_X_GAMECD
header and the gamecd
parameter sent when logging in, CPUE
(encoded as Q1BVRQ**
in the POST body)Nitro WiFi SDK/2.2
as its User-Agent
string in HTTP requests to nas.nintendowifi.net and will use the value 002002
(encoded as MDAyMDAy
) for the sdkver
parameter sent when logging in.0x01
, even in Diamond and Pearl. However, these two bytes appear to be set to 0x00
when voice-chat is disabled in Platinum.The Wi-Fi Plaza is a feature (unique to Platinum?) which makes use of the GameSpy Peerchat protocol. This protocol is an encrypted form of IRC, the encryption of which has been documented by Luigi Auriemma.
(TODO: But how does it work?)
Incomplete notes to be expanded on:
Appears to log in to the TCP server, and, once empty STATUS(1) is sent, connect to pokemonplatds.peerchat.gs.nintendowifi.net
Upon encrypted login, connect to gamestats2.gs.nintendowifi.net, get /pokemondpds/web/enc/lobby/checkProfile.asp?pid=[pid], get back a token.
Then in a separate connection, get /pokemondpds/web/enc/lobby/checkProfile.asp?pid=[pid]&hash=[hash] containing unknown bytestream. This appears to mirror the challenge/response framework of GTS, but with the secret token uLMOGEiiJogofchScpXb
instead of sAdeqWo3voLeC5r16DYv
. It also probably requires the server to sign all responses like Gen V (again, with the secret token uLMOGEiiJogofchScpXb
.
Then in a separate connection, get /pokemondpds/web/enc/lobby/getSchedule.asp?pid=[pid]&hash=[hash] containing unknown bytestream.
Then in a separate connection, get /pokemondpds/web/enc/lobby/getVIP.asp?pid=[pid]&hash=[hash] containing unknown bytestream.
Then in a separate connection, get /pokemondpds/web/enc/lobby/getQuestionnaire.asp?pid=[pid]&hash=[hash] containing unknown bytestream.
Remaining communications use pokemonplatds.peerchat.gs.nintendowifi.net
May use pokemondpds.ms4.gs.nintendowifi.net to negotiate P2P Plaza games
All Battle Subway data is (probably) handled like the Battle Tower in Gen IV, making use of the gamestats2.gs.nintendowifi.net HTTP server. That is,
Common data structures (all multi-byte values little-endian):
struct btlsub_pokemon {
unsigned short species_id;
unsigned short held_item_id;
unsigned short move_id[4];
unsigned short original_trainer_id;
unsigned short original_trainer_secret_id;
unsigned long personality_id;
unsigned long packed_ivs; /* packed 5-bits a piece, the following IV values from least-significant-bit to most-significant-bit: HP, Attack, Defense, Speed, SP Attack, SP Defense. Two most significant bits are 0. */
unsigned char hp_ev;
unsigned char attack_ev;
unsigned char defense_ev;
unsigned char speed_ev;
unsigned char sp_attack_ev;
unsigned char sp_defense_ev;
unsigned char unknown; /* to analyze */
unsigned char original_language; /* see Project Pokemon */
unsigned char ability;
unsigned char friendship;
wchar_t name[11]; /* Terminated with 0xFFFF. Contents of unused string data undefined. */
unsigned long zero; /* unused, always 0x00000000? */
};
struct btlsub_trainer_record {
wchar_t name[8]; /* Terminated with 0xFFFF. Contents of unused string data undefined. */
unsigned char unknown; /* to analyze */
unsigned char language; /* see Project Pokemon */
unsigned char country; /* TODO: create list */
unsigned char province; /* 0x00 if no provinces known for country. TODO: create list */
unsigned short trainer_id;
unsigned short trainer_secret_id;
unsigned short unknown2; /* to analyze */
unsigned short record_text_pattern_id; /* TODO: create list */
unsigned short record_text_keyword_id; /* TODO: create list */
unsigned short unknown3; /* always 0xFFFF? second keyword ID? */
unsigned char gender; /* 0x00 - Male, 0x02 - Female */
unsigned char unknown4; /* to analyze */
};
struct btlsub_trainer {
struct btlsub_pokemon pokemon[3];
struct btlsub_trainer_record trainer;
unsigned char unknown[26]; /* to analyze */
};
The contents of download.asp:
struct btlsub_download {
struct btlsub_trainer trainers[7]; /* Encountered in order(?). Final entry is current record holder. */
struct btlsub_trainer_record successive_records[30]; /* Displayed in order */
char hash[40];
};
The payload of validate:
struct btlsub_validate {
char base64[88]; /* unknown base64 id, to analyze */
unsigned char zero; /* always 0x00? */
unsigned short four_hundred; /* always 0x0400? */
struct btlsub_pokemon pokemon[3]; /* the three pokemon to be submitted as part of the trainer's pokemon in upload.asp */
};
The payload of upload.asp:
struct btlsub_upload {
unsigned long unknown; /* to analyze */
unsigned long size; /* size of remaining payload after these first 8 bytes */
struct btlsub_trainer trainer;
unsigned char unknown2[16]; /* to analyze */
unsigned char validation_signature[128]; /* signature received from pkvldtprod.nintendo.co.jp */
unsigned long size2; /* size of signature(?) 0x00000080? */
};