GTS protocol
Communication with the GTS is done over regular HTTP with http://gamestats2.gs.nintendowifi.net/. The same protocol is used for all five Gen IV games.
HTTP headers
The games don't seem to care about these at all. The GTS sends back a bunch of boilerplate response headers, but the game happily accepts a response with only a Content-Length.
Protocol
All requests to the server are GET requests of the form page.asp?pid=pid
followed by page.asp?pid=pid&hash=hash&data=data
. The hash variable in the latter, is a hash of the string returned by the former, as described bellow.
pid
The pid is an unsigned 32-bit integer that appears to uniquely identify a game cartridge. Although this has not been confirmed entirely: Your PID is generated when you get your friendcode for the first time and is set to: friendcode & 0x7fffffff
When you change the device and are forced to change your friendcode with it, your PID doesn't change but you get a new friendcode. Your new Friend Code appears to be set by appending a random byte (likely sent from Nintendo's servers) to the end of your PID. For example, nicholas's Soul Silver PID is 258303618 (0x8266650f)and his Friend Code is 4297 5503 3218 (0xf8266650f64) taking endianness into account.
Challenge/response
Before each "real" request, the game sends a request of the form page.asp?pid=pid
and the server responds with a 32-byte challenge token. The game computes sha1("sAdeqWo3voLeC5r16DYv" + token)
and uses that as the hash value which it sends to the server. The data parameter is encrypted, as explained further down.
For 5th gen GTS, the hash is instead, generated by sha1("HZEdGCzcGGLvguqUEKQN" + token)
That is, each request looks like the following:
- Game requests
GET /pokemondpds/page.asp?pid=pid
- Server responds with token
- Game requests
GET /pokemondpds/page.asp?pid=pid&hash=sha1(...)&data=data
- Server responds with payload
Data
The data sent to the server consists of a checksum, your pid, and a payload. The 5th gen GTS also adds the length of the payload, after the pid.
Checksum
The checksum is a 32-bit integer, computed by simply taking the sum of every byte of the pid and payload.
It is the first four bytes of the data, xor-ed with 0x4a3b2c1d
(0x2db842b2
for 5th gen) and packed in network byte order (big-endian).
Payload
The pid (, length) and payload are encrypted with a stream cipher in for the 4th gen protocol.
This encryption algorithm, like others used in the game, uses a Linear Congruential Generator (not a very strong choice). The multiplicative constant is 0x45
, and the additive constant is 0x1111
. It appears that the game uses a signed dword to store the seed, which doesn't really matter; it shouldn't affect anything. (It means that the modulus is effectively 0x80000000
.)
GRNG[n+1] = (GRNG[n] * 0x45 + 0x1111) & 0x7fffffff
The GRNG is seeded with the checksum, like so:
GRNG[0] = checksum | (checksum << 16)
The keystream is composed of the lower byte of the high word of successive GRNG values.
keystream[n] = (GRNG[n] >> 16) & 0xff
Xor
the keystream with the plaintext to get the ciphertext. Xor
the keystream with the ciphertext to get the plaintext.
The entire data string is then encoded with urlsafe-base64.
Conversation
The first request the game makes is to /pokemondpds/worldexchange/info.asp
. The server responds with 0x0001.
Platinum, Heart Gold, and Soul Silver will then make a request to /pokemondpds/common/setProfile.asp
. The server responds with eight NULs (0x00000000 0x00000000).
After the above step(s) or performing any of the tasks below other than searching, the game makes a request to /pokemondpds/worldexchange/result.asp
. If the game has had a Pokémon sent to it via a trade, the server responds with the entire encrypted Pokémon save struct. Otherwise, if there is a Pokémon deposited in the GTS, it responds with 0x0004; if not, it responds with 0x0005.
Receiving a traded Pokémon
If the game receives a Pokémon from a successful trade as a response from result.asp
, it next requests /pokemondpds/worldexchange/delete.asp
. The server responds with 0x0001.
A note on sendpkm.py
After doing the above, some Platinum, Heart Gold, and Soul Silver games will report a communication error and dump the player back to the title screen. The Pokémon is still successfully received. At least one person with HG/SS has received a Pokémon from a fake server without getting the error, and Diamond/Pearl have never been reported to have the problem.
Depositing a Pokémon
Pokémon are offered on the GTS by requesting /pokemondpds/worldexchange/post.asp
. The sent data is 300 bytes long, and includes the Pokémon struct. If the Pokémon is rejected by the server, the response is 0x000c; otherwise, if the deposit is successful, 0x0001.
The game then saves. After the save is complete, it issues a request to /pokemondpds/worldexchange/post_finish.asp
.
Retrieving the deposited Pokémon
Checking on the deposited Pokémon is apparently done by /pokemondpds/worldexchange/get.asp
. The response appears to be a Pokémon save struct.
Retrieving the deposited Pokémon is done by /pokemondpds/worldexchange/return.asp
. The response is merely 0x0001; the actual Pokémon data is taken from the get.asp
request.
Searching
Searching is done through /pokemondpds/worldexchange/search.asp
. The sent data is either 15 or 16 bytes long.
The server responds with a full 292-byte Pokémon struct for each result. If there are n results, the response will be 292 * n bytes long. If there are no results, the server will give an empty response (0 bytes).
Pokémon struct
The Pokémon data for the Generation IV GTS is 292 bytes—56 bytes larger than a party Pokémon struct. The extra 56 bytes are GTS-specific data, such as the player's name & country, and what Pokémon they are requesting. In Generation IV, the trainer name is encoded with the Pokémon character table, while in Generation V, the trainer name is Unicode encoded.
They are as follows:
Generation IV Encrypted Bytes
Offset | Contents |
---|---|
0x00-0x01 | Nat. Dex ID |
0x02 | Gender |
0x03 | Level |
0x04-0x05 | Requested Nat. Dex ID |
0x06 | Requested Gender |
0x07 | Requested Min Level |
0x08 | Requested Max Level |
0x09 | Unknown - always 0? |
0x0A | Trainer's Gender |
0x0B | Unknown - always 0? |
0x0C-0x13 | Timestamp - deposited time |
0x14-0x1B | Timestamp - time traded? |
0x1C-0x1F | PID |
0x20-0x2F | Trainer's Name |
0x30-0x31 | Trainer's OT ID |
0x32 | Country |
0x33 | City |
0x34 | Trainer's Sprite |
0x35 | Is Exchanged Flag |
0x36 | Game Version |
0x37 | Language |
The data for the Generation V GTS is 296 bytes and adds the trainer's secret ID, an additional two unknown bytes at the end, and shifts the position of the trainer name to after the trainer and secret IDs.
Generation V Encrypted Bytes
Offset | Contents |
---|---|
0x00-0x01 | Nat. Dex ID |
0x02 | Gender |
0x03 | Level |
0x04-0x05 | Requested Nat. Dex ID |
0x06 | Requested Gender |
0x07 | Requested Min Level |
0x08 | Requested Max Level |
0x09 | Unknown - always 0? |
0x0A | Trainer's Gender |
0x0B | Unknown - always 0? |
0x0C-0x13 | Timestamp - deposited time |
0x14-0x1B | Timestamp - time traded? |
0x1C-0x1F | PID |
0x20-0x21 | Trainer's OT ID |
0x22-0x23 | Trainer's Secret ID |
0x24-0x33 | Trainer's Name |
0x34 | Country |
0x35 | City |
0x36 | Trainer's Sprite |
0x37 | Alternate Form |
0x38 | Game Version |
0x39 | Language |
0x3A-0x3B | Unknown - always 0? |
Pokémon Gender
Value | Type |
---|---|
0x01 | Male |
0x02 | Female |
0x03 | Either/neither |
Trainer Gender
Value | Type |
---|---|
0x00 | Male |
0x01 | Female |
Timestamp Format
The timestamps are set by the server, and are always PST (UTC-8).
Offset | Contents |
---|---|
0x00-0x01 | Year |
0x02 | Month |
0x03 | Day |
0x04 | Hour |
0x05 | Minute |
0x06 | Second |
0x07 | Unknown - always 0? |
Alternate Form
The normal/default form is 0x01; if it is another form, this byte will correspond to the appropriate Pokémon structure alternate form.
Game Version
Value | Version |
---|---|
0x0A | Diamond |
0x0B | Pearl |
0x0C | Platinum |
0x07 | HeartGold |
0x08 | SoulSilver |
0x15 | Black |
0x14 | White |