subscribe

bread.codes

code stuff

Linkin-Park-Numb.mp3
--:--
--:--

Pokemon Trading Protocol

December 31, 2024

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]

A C code block showing the packet structure of a trade

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]

A C code block showing the structure of Pokemon in Red, Blue, Green, Yellow. Note that there are comments in the code, which I will refer to, but will also explainC code showing how the previous structure is converted to an array of bytes
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
  • 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
  • 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
  • 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
    • 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
  • 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
  • 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
  • The TC status is in the TRADE_WAIT state
    • If we receive a byte that is & with 0x60 to equal 0x60
      • If we receive a 0x6f, we respond with 0x6f and move back to the READY state to start over
      • If we receive a 0x60, we respond with 0x60 and move to the next state, TRADE_DONE
    • If we receive a 0x00 byte from the master
      • We respond with 0x00
      • We move to the next state, TRADE_DONE
  • The TC status is in the TRADE_DONE state
    • We are waiting for a byte from the master that can be & with 0x60 to equal 0x60
      • 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 to TRADE_WAIT
      • Otherwise, we're DONE
  • 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

Here are all the states and what they do:

  • INIT: Trade has started
  • READY: Both devices are ready to begin
  • AGREED: Both devices have agreed where to start
  • ANY_IN: Waiting for the master to send us data
  • WAIT: Waiting for the master to send us data
  • TRANSFER: Sending the TraderPacket to the master
  • PATCH: Waiting for the animation to finish
  • TRADE_WAIT: Waiting for the trade to finish
  • TRADE_DONE: Trade is done
  • DONE: 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!