Pokemon Trading Protocol
I originally posted a breakdown of how trading in Pokémon Red, Blue, and Yellow works as a thread on BlueSky, but I wanted to share it here with additional supporting information (and corrections, becuase it had been over a year since I touched this project when I posted it). Hopefully you enjoy!
I was not the first person to do it, but I reverse engineered the Pokémon Gen 1 trading protocol some time ago I reminisce about this project often, so I thought I'd make a thread about how it works! #reverseengineering #retrogamedev #retrogaming #pokemon [1/18]
So the first thing to understand is that the Game Boy used a bastardized version of SPI for communication, just without the Chip Select line since the Game Boy could be connected to only one other device... but if that device is another Game Boy, who's in control of the clock? [2/18]
Like I said, the GB/C link cable is bastard of SPI. Below is the pinout of the port
_________ 1 -> 5v
/ 1 2 3 \ 2 -> RX
/ \ 3 -> CLK
| | 4 -> TX
| 4 5 6 | 5 -> Unused
+-----------+ 6 -> GND
* 5v: +5 Volt Power
* RX: Receive Data
* CLK: Clock In/Out (Slave/Master respectively)
* TX: Transmit Data
* Unused: A reserved pin
* GND: Ground
You may notice Chip Select is missing. Since the Game Boy only expects to have one master and one slave, so it's not needed for switching between multiple slave devices.
If you are unfamiliar with serial transfers, you may also notice two pins are for receiving and sending respectively. They are typically wired like so:
Master Slave
| TX --------> RX |
| RX <-------- TX |
This is to trade data at the same time each clock cycle. The master will send the next byte while the slave responds to the previous byte. This would create a way to perform an optimistic trade where the master assumes the slave has a successful response while it continues to send data until told otherwise, but the Pokémon games send a filler byte between transfers while they wait for a response. These filler bytes will be talked about later, but keep them in mind.
Additionally, the Game Boy is active low, meaning that the clock is normally high and goes low when data is being sent. This is important to know when you are trying to send and read data using a microcontroller.
That is basically all we need to know about the hardware, as this is really about the program that drives this. Connecting to the Game Boy with other hardware may require a minor amount of work to implement this SPI-clone, but beyond that, it is basically unnecessary. My implementation runs on the Game Boy as a homebrew program, so I rely on the hardware to handle shifting data out and in.
From this point forward we will mostly only be talking about the firmeware/software, specifically the handle_byte
function that is called every time the slave receives a byte, which sends a byte back.
The quirky part of this is that they have a pecking order in Pokémon to decide who's in control. The first one to successfully send a byte becomes the Primary (formerly "master") and the receiver becomes the Secondary (formerly "slave")... how do you make this consistent? [3/18]
This "pecking order" is literally just both Game Boys sending "I'm master" signals (0x01
) to each other until the other submits and agknowledges itself as slave (0x02
). Once they both agree to the arrangement with 0x60
, the Game Boys know their place and can start driving the trade.
I incorrectly stated on BSky that this is how they decide who drives the clock. You don't need to be master to drive the clock. The clock-driver is decided by the hardware, and if CLK is held LOW, the hardware sets itself as the "hardware slave." The "software slave" is the one that submits the 0x02
signal.
Well, you of course give up control, and you never send a byte until the other GB takes control. This is how I managed the state of the trades, because by always waiting for the other GB, the Primary always polled when ready. Now what? [4/18]
What I mean by this is that we don't want to be the "hardware master." This ensures that we aren't going too fast for the actual game, and it will let us know when it's ready to continue.
Well, the Primary sends packets to check if we're still there, which we acknowledge (0x00) in a response. You do this until the other GB reports that it is at a Pokémon Center and is at the link counter. When they are ready, we start the handshake process. [5/18]
When the master sends us an ACK (acknowledgement) signal (0x00
), we usually respond with a 0x00
Here's where the actual protocol starts. Since we're the Secondary, we are being polled by the Primary so that we don't accidentally go too fast Now we say we are also at the link counter and want to trade. [6/18]
It is entirely possible to be the hardware master, but this was far faster than arbitrary delays to give the game time to ready its response.
This is also my memory failing me, and conflating software and hardware roles.
Above this point we have been talking entirely about how the hardware roles are decided and how the software roles are decided. The software roles are almost entirely handled by this section of code:
uint8_t handle_byte(uint8_t in, uint8_t *out) {
// ...
switch (connection_state) { // enum holding the state of the connection
case NOT_CONNECTED:
switch (in)
{
// The other is the master
case PKMN_MASTER: // 0x01
out[0] = PKMN_SLAVE; // 0x02
break;
case PKMN_BLANK: // 0x00
out[0] = PKMN_BLANK; // 0x00
break;
case PKMN_CONNECTED: // 0x60
connection_state = CONNECTED; // enum holding the state of the connection
out[0] = PKMN_CONNECTED; // 0x60
break;
}
break;
// case ...
}
}
We wait for a READY (0xFD) and respond READY. We do it again... and again... and again, but this time we respond with a 0x00. I don't actually know why this was necessary 4 times. Now we are in a "waiting" state. This state is used to send our Pokemon in their entirety to the other GB. [7/18]
Once the above switch statement has concluded that the connection is established, we wait for the game to send us the TRADE_CENTER (0xD4
) signal to say they are at the counter and are preparing to enter the trade room. Here is the code for this section
uint8_t handle_byte(uint8_t in, uint8_t *out) {
// ...
switch (connection_state) { // enum holding the state of the connection
// case ...
case CONNECTED:
switch (in)
{
case PKMN_CONNECTED: // 0x60
out[0] = PKMN_CONNECTED; // 0x60
break;
case PKMN_TRADE_CENTER: // 0xD4
// No byte response known; just move on the next case (we'll discuss later)
connection_state = TRADE_CENTER; // enum holding the state of the connection
break;
case PKMN_COLOSSEUM: // 0xD5
// We're not implementing this so just break the link
connection_state = NOT_CONNECTED; // enum holding the state of the connection
out[0] = PKMN_BREAK_LINK; // 0xD6
break;
case PKMN_BREAK_LINK: // 0xD6
case PKMN_MASTER: // 0x01
connection_state = NOT_CONNECTED; // enum holding the state of the connection
out[0] = PKMN_BREAK_LINK; // 0xD6
break;
// By default, these are just filler bytes, so just echo them back to the master
default:
out[0] = in; // echo back the byte
break;
}
break;
case TRADE_CENTER:
// Protocol for the trade center
// ...
break;
// case ...
}
}
Here's where we take a detour from the protocol itself We don't just send basic info about our 'mons, we send every byte of the Pokemon almost exactly as they would be in our party and some info about ourselves. [8/18]
Let's derail this train...
In short, here is the order of what we are sending: - Trainer Name (11 bytes, 0x50 terminated string [in English]) - Selected Pokemon (1 byte for num of mons in party + 6 bytes each representing the mons' true ID) - Party Member (6 whole Pokemon, each 44 bytes [discussed later]) [9/18]
Here is the exceprt of the Trade Center packet:
typedef struct TraderPacket {
// Name must not exceed 10 characters + 1 STOP_BYTE
// Any leftover space must be filled with STOP_BYTE (actually I'm not sure that's true, but you should do it)
unsigned char name[11];
struct SelectedPokemon selected_pokemon;
struct PartyMember pokemon[6];
unsigned char original_trainer_names[6][11];
unsigned char pokemon_nicknames[6][11];
} TraderPacket;
[cont] - OG Trainer Names (an array containing 6 names, each 11 bytes long and 0x50 terminated [in english], with the same index as the party mons) - Mon Nicknames (^ read above note) [10/18]
To explain how the mons are structured, here is the struct I made [left] and the function that turns the struct to an array of bytes (because I was using u32 for u24 numbers, u8 for nibbles, and the wrong endianness, it didn't align) [right] I wish I had space to explain this better [11/18]
typedef struct PartyMember {
enum gen_one_dex_t pokemon;
uint16_t current_hp;
uint16_t max_hp;
uint8_t level;
enum status_condition_t status;
enum poke_type_t type1;
enum poke_type_t type2; // If only one type, copy the first
uint8_t catch_rate_or_held_item; // R/G/B/Y (catch rate), G/S/C (held item), and Stadium (held item) use this byte differently
enum poke_move_t move1;
enum poke_move_t move2;
enum poke_move_t move3;
enum poke_move_t move4;
uint16_t original_trainer_id; // In decimal, these are the funny numbers
// - Experience is complicated. You must look up the Pokemon you are trying to trade
// in the following table and apply the experience points that match the level.
// EXP LVL Table for gen 1: https://pwo-wiki.info/index.php/Generation_I_Experience_Charts
// That source was the best I could find for Gen 1. If you find another, submit a PR or open an issue and I'll fix it
// - Experience is a 24bit number, we will be dropping the MSB to acheive that
uint32_t experience;
// Effort Values
// These are very specific to the Pokemon and who they battled in the past or what vitamins they were fed
// Luckily, these get recalculated when you level them up, or when you put them in a box and then put them back in your party
// For this example, I will take the max value and scale it to the level (65535 * 0.40) = 26214
uint16_t HP_ev;
uint16_t attack_ev;
uint16_t defense_ev;
uint16_t speed_ev;
uint16_t special_ev;
// IVs are a 4 bit number, so the max value is 15 (0-15 = 0b0000-0b1111 = 0x0-0xF)
// These have been broken out for legibility, but will be condensed to only 2 bytes
uint8_t attack_iv;
uint8_t defense_iv;
uint8_t speed_iv;
uint8_t special_iv;
uint8_t move1_pp;
uint8_t move2_pp;
uint8_t move3_pp;
uint8_t move4_pp;
uint16_t attack;
uint16_t defense;
uint16_t speed;
uint16_t special;
} PartyMember;
Which is serialized like so:
struct PartyMember *pPartyMember = &traderPacket.pokemon[i];
pPartyMember->pokemon = MEW;
pPartyMember->current_hp = 100;
pPartyMember->max_hp = 130;
pPartyMember->level = 40;
pPartyMember->status = NONE;
pPartyMember->type1 = PSYCHIC_TYPE;
pPartyMember->type2 = PSYCHIC_TYPE; // If only one type, copy the first
pPartyMember->catch_rate_or_held_item = 0xFF; // R/G/B/Y (catch rate), G/S/C (held item), and Stadium (held item) use this byte differently
pPartyMember->move1 = TELEPORT;
pPartyMember->move2 = PSYWAVE;
pPartyMember->move3 = PSYCHIC;
pPartyMember->move4 = FLY;
pPartyMember->original_trainer_id = 0xA455; // In decimal, these are the funny numbers
// - Experience is complicated. You must look up the Pokemon you are trying to trade
// in the following table and apply the experience points that match the level.
// EXP LVL Table for gen 1: https://pwo-wiki.info/index.php/Generation_I_Experience_Charts
// That source was the best I could find for Gen 1. If you find another, submit a PR or open an issue and I'll fix it
// - Experience is a 24bit number, we will be dropping the MSB to acheive that
pPartyMember->experience = 190148;
// Effort Values
// These are very specific to the Pokemon and who they battled in the past or what vitamins they were fed
// Luckily, these get recalculated when you level them up, or when you put them in a box and then put them back in your party
// For this example, I will take the max value and scale it to the level (65535 * 0.40) = 26214
pPartyMember->HP_ev = 26214;
pPartyMember->attack_ev = 26214;
pPartyMember->defense_ev = 26214;
pPartyMember->speed_ev = 26214;
pPartyMember->special_ev = 26214;
// IVs are a 4 bit number, so the max value is 15 (0-15 = 0b0000-0b1111 = 0x0-0xF)
// These have been broken out for legibility, but will be condensed to only 2 bytes
pPartyMember->attack_iv = 0xF;
pPartyMember->defense_iv = 0xF;
pPartyMember->speed_iv = 0xF;
pPartyMember->special_iv = 0xF;
pPartyMember->move1_pp = 20;
pPartyMember->move2_pp = 15;
pPartyMember->move3_pp = 10;
pPartyMember->move4_pp = 15;
pPartyMember->attack = 100;
pPartyMember->defense = 100;
pPartyMember->speed = 100;
pPartyMember->special = 100;
and deserialized like so:
void party_member_to_bytes(struct PartyMember *pPartyMember, uint8_t *out) {
uint8_t res[44] = {
pPartyMember->pokemon,
(uint8_t) (pPartyMember->current_hp >> 8),
(uint8_t) (pPartyMember->current_hp & 0x00FF),
pPartyMember->level,
pPartyMember->status,
pPartyMember->type1,
pPartyMember->type2,
pPartyMember->catch_rate_or_held_item,
pPartyMember->move1,
pPartyMember->move2,
pPartyMember->move3,
pPartyMember->move4,
(uint8_t) (pPartyMember->original_trainer_id >> 8),
(uint8_t) (pPartyMember->original_trainer_id & 0x00FF),
(uint8_t) ((pPartyMember->experience & 0x00FF0000) >> 16),
(uint8_t) ((pPartyMember->experience & 0x0000FF00) >> 8),
(uint8_t) (pPartyMember->experience & 0x000000FF),
(uint8_t) (pPartyMember->HP_ev >> 8),
(uint8_t) (pPartyMember->HP_ev & 0x00FF),
(uint8_t) (pPartyMember->attack_ev >> 8),
(uint8_t) (pPartyMember->attack_ev & 0x00FF),
(uint8_t) (pPartyMember->defense_ev >> 8),
(uint8_t) (pPartyMember->defense_ev & 0x00FF),
(uint8_t) (pPartyMember->speed_ev >> 8),
(uint8_t) (pPartyMember->speed_ev & 0x00FF),
(uint8_t) (pPartyMember->special_ev >> 8),
(uint8_t) (pPartyMember->special_ev & 0x00FF),
(uint8_t) (((pPartyMember->attack_iv & 0xF) << 4) | (pPartyMember->defense_iv & 0xF)),
(uint8_t) (((pPartyMember->speed_iv & 0xF) << 4) | (pPartyMember->special_iv & 0xF)),
pPartyMember->move1_pp,
pPartyMember->move2_pp,
pPartyMember->move3_pp,
pPartyMember->move4_pp,
pPartyMember->level,
(uint8_t) (pPartyMember->max_hp >> 8),
(uint8_t) (pPartyMember->max_hp & 0x00FF),
(uint8_t) (pPartyMember->attack >> 8),
(uint8_t) (pPartyMember->attack & 0x00FF),
(uint8_t) (pPartyMember->defense >> 8),
(uint8_t) (pPartyMember->defense & 0x00FF),
(uint8_t) (pPartyMember->speed >> 8),
(uint8_t) (pPartyMember->speed & 0x00FF),
(uint8_t) (pPartyMember->special >> 8),
(uint8_t) (pPartyMember->special & 0x00FF),
};
for (size_t i = 0; i < 44; i++) {
out[i] = res[i];
}
}
For those who are not sight impaired (sorry for the poor alt text), you might be noticing my comments about how this matches the save file. Well, that's because Pokémon just copies your Pokémon directly from RAM and sends all of it over the wire. [12/18]
Makes sense, right? Copying from SRAM / RAM is always going to be cheaper than restructuring data. I guess I should have guessed that.
Interesting bits: - Mons can have 2 types, but if they don't, they usually have the same type twice - catch_rate_or_held_item is a byte that is different in Gen 1 & 2. Held items didn't exist until Gen 2, and they dropped catch rate because it was redundant [13/18]
[cont] - Experience is complicated math with several variables. I just used EXP calculators - EVs are 2 bytes long, but IVs are a nibble (4 bits / ½ byte) Let's get back to the protocol. Did you forget about it? [14/18]
We have sent our mons to each other after 197 messages. Now the Primary tells us it's status with a byte, which should have bits 5 and 6 set to high indicating it is ready. If it sends an ERROR (0x6f), we start over. Otherwise, bits 0-3 indicate the index of the mon it wants to trade. [15/18]
I just skipped over SO MUCH. I must've been tired when I wrote this BSky post, because it skips a major part of the protocol.
So, that handle_byte
function I've been talking about before we derailed, it manages the state of the connection. We left off on this part:
case PKMN_TRADE_CENTER: // 0xD4
// No byte response known; just move on the next case (we'll discuss later)
connection_state = TRADE_CENTER; // enum holding the state of the connection
break;
You may notice that it doesn't send a byte back to the master. It instead moves on to the next case, which is the TRADE_CENTER
state. Instead of breaking down the code segments, which are long and illegible, I will explain the logic of the process.
- When in doubt, echo back the input to the output
- If things get really bad, start the process over from the beginning
- The Trade Center status is in its default state,
INIT
- In this case, we are waiting for a
0x00
byte from the master - We respond with
0x00
and move to the next state,READY
- In this case, we are waiting for a
- The TC status is in the
READY
state- In this case, we are waiting for a
0xFD
byte from the master - We respond with
0xFD
and move to the next state,AGREED
- In this case, we are waiting for a
- The TC status is in the
AGREED
state- In this case, we are waiting for any byte that is NOT
0xFD
from the master - We respond with
0xFD
and move to the next state,ANY_IN
- In this case, we are waiting for any byte that is NOT
- The TC status is in the
ANY_IN
state- We set a counter to 0 to count the number of bytes we receive
- In this case, we are waiting for any byte from the master
- We respond with
0xFD
and move to the next state,WAIT
- The TC status is in the
WAIT
state- If we receive a
0xFD
byte- ... we just respond with a
0x00
until we get a different response
- ... we just respond with a
- If we receive a byte that is NOT
0xFD
- NOTE: We'll be throwing the byte we receive away, but you can keep it if you care about the packet the master is sending us
- We set a counter to 0 to count the number of bytes we receive
- We respond with the first byte of our TraderPacket we constructed from the struct earlier
- We add 1 to the counter
- We move to the next state,
TRANSFER
- If we receive a
- The TC status is in the
TRANSFER
state- In this case, we are waiting for any byte from the master
- NOTE: We'll be throwing the byte we receive away, but you can keep it if you care about the packet the master is sending us
- We respond with the next byte of our TraderPacket we constructed from the struct earlier
- We add 1 to the counter
- If we have sent all 418 bytes, we move to the next state,
PATCH
- Why 418? That's the size of the TraderPacket struct when we deserialized it
- In this case, we are waiting for any byte from the master
- The TC status is in the
PATCH
state- If we receive a
0xFD
byte from the master- We set a counter to 0 to count the number of bytes we receive
- We respond with
0xFD
- If we receive a byte that is NOT
0xFD
- NOTE: We'll be throwing the byte we receive away, but you can keep it if you care about the packet the master is sending us
- We respond by echoing back whatever the master sends us
- We add 1 to the counter
- If we have sent 197 bytes, we move to the next state,
TRADE_WAIT
- Why 197? Because that's how long the trade animation is, and this is just to see if we're still alive
- If we receive a
- The TC status is in the
TRADE_WAIT
state- If we receive a byte that is
&
with0x60
to equal0x60
- If we receive a
0x6f
, we respond with0x6f
and move back to theREADY
state to start over - If we receive a
0x60
, we respond with0x60
and move to the next state,TRADE_DONE
- If we receive a
- If we receive a
0x00
byte from the master- We respond with
0x00
- We move to the next state,
TRADE_DONE
- We respond with
- If we receive a byte that is
- The TC status is in the
TRADE_DONE
state- We are waiting for a byte from the master that can be
&
with0x60
to equal0x60
- We always echo back this byte. We are just reading it to see what the status of the trade is
- If the byte is
0x61
, we go back toTRADE_WAIT
- Otherwise, we're
DONE
- We are waiting for a byte from the master that can be
- The TC status is in the
DONE
state- We are waiting for
0x00
from the master- We respond with
0x00
- We move back to the
INIT
state to start over
- We respond with
- We are waiting for
Here are all the states and what they do:
INIT
: Trade has startedREADY
: Both devices are ready to beginAGREED
: Both devices have agreed where to startANY_IN
: Waiting for the master to send us dataWAIT
: Waiting for the master to send us dataTRANSFER
: Sending the TraderPacket to the masterPATCH
: Waiting for the animation to finishTRADE_WAIT
: Waiting for the trade to finishTRADE_DONE
: Trade is doneDONE
: Connection is done
Since we are faking mons on the Secondary, we can send whatever we want, I just fill my party with Mews while we're trading info, and send index 0 (status 0x60) when it's time. Once we have responded with this status, the trade has started. [16/18]
Somewhat in correct, please refer to my above correction
The Primary sends 0x00, we respond with 0x00, and we repeat until the animation is done. After that? We're done! We *should* restart the process and have some handling for trading again, but this is where I end my explanation. [17/18]
Somewhat in correct, please refer to my above correction
Thank you to #GBDK (gbdk-2020) (and @bbbbbr.bsky.social as a major maintainer, please follow them) for enabling my project Thank you to Bulbapedia and it's maintainers for documenting the Party and Bill's PC Pokémon data structure Thank you for reading! lmk if you like this type of content [18/18]
Fun tidbits: I accidentally found a way to destroy someone's save file. A real GB virus by corrupting the mon just right. I originally did this on a Pi, but decided that the barrier for others doing this was too high. I ended up rewriting this for the GB to be run from a flash cartridge
Over a year ago, I was working on a YouTube video where I would explain how to counterfeit Pokemon through the trade protocol. Unfortunately, life happens, and I never got to share this the way I wanted to. You can follow @BreadCodes on YouTube if you want to see that kind of content!