I love the Pokémon games. When I was 9 years old, I got Pokémon Red and from there on I was hooked. I played every free minute on my Game Boy Advance.
Years later, one feature I still find intriguing: Trading via Serial Cable. How does it work and could you spoof the response, so you can trade yourself any Pokémon you’d like?

The answer, of course, is yes! This is how it can be done. This post only covers Pokémon Red.

You can view the code for this here: GitHub: nitwhiz/pokemon-red-trade.

The setup

I don’t own an original Game Boy and wanted to focus on the software side of things. Thus, I decided to go with emulation.

I wrote a Game Boy emulator once (in Go), and it actually succeeded a decent number of acceptance tests, but I felt like it wasn’t accurate enough for this. So I literally googled “most accurate gameboy emulator” and it landed me on GitHub: SameBoy. I already heard of SameBoy during my emulator coding adventures and took a look at it.

The “problem”: I don’t really like coding in C. I really wanted to do this in Go. To make this possible, I’m using a unix socket to “get out and in” the SameBoy emulator with low effort in the emulator itself.

How does the Game Boy handle serial data?

The Game Boy sends and receives data on the serial port at the same time. During a transfer, the byte to be sent is shifted out of the SB register and the response is shifted into it. (Read more on that here: PanDocs: Serial Data Transfer)

How does SameBoy handle serial data?

As I scouted the source code, I stumbled upon GB_set_serial_transfer_bit_start_callback and GB_set_serial_transfer_bit_end_callback in printer.c (which emulates the Game Boy printer). That’s exactly what I was searching for!
These methods are used to handle the transferred data from and to the emulator bit by bit.
To make things simple, I implemented two callbacks which send/receive the data in bytes and source/feed it from/into the emulator in bits.

This approach works because the Pokémon trading protocol is pretty forgiving, and it’s all right if we send our response for the first byte a bit late.

These are the methods I implemented:

// Core/gb.c

void GB_set_serial_sock(GB_gameboy_t *gb, int sock)
{
    // this is a new property in the GB_gameboy_t struct: int serial_sock
    gb->serial_sock = sock;
}

ssize_t GB_send_serial_byte(const GB_gameboy_t *gb, const uint8_t byte)
{
    return send(gb->serial_sock, &byte, 1, 0);
}

ssize_t GB_recv_serial_byte(const GB_gameboy_t *gb, uint8_t *byte)
{
    return recv(gb->serial_sock, byte, 1, 0);
}
// SDL/main.c

static void serial_init_socket(GB_gameboy_t *gb)
{
    struct sockaddr_un address;
    int sock;

    if ((sock = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
    {
        CON_printf("serial_init_socket: socket(): failed\n");
        return;
    }

    struct timeval timeout;

    timeout.tv_sec = 0;
    timeout.tv_usec = 10000;

    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof timeout);

    address.sun_family = AF_LOCAL;
    strcpy(address.sun_path, "/tmp/gb-serial.sock");

    if (connect(sock, (struct sockaddr *)&address, sizeof(address)) == -1)
    {
        CON_printf("serial_init_socket: connect(): failed\n");
        return;
    }

    GB_set_serial_sock(gb, sock);

    CON_printf("serial_init_socket(): serial socket connected!\n");
}

static void serial_start(GB_gameboy_t *gb, bool bit_received)
{
    static uint8_t buf_in = 0;
    static uint8_t buf_pos = 0;

    buf_in <<= 1;
    buf_in |= bit_received;
    ++buf_pos;

    if (buf_pos == 8)
    {
        if (GB_send_serial_byte(gb, buf_in) < 1)
        {
            CON_printf("seral_start(): unable to send serial data\n");
        }

        buf_pos = 0;
    }
}

static bool serial_end(GB_gameboy_t *gb)
{
    static uint8_t buf_out = 0;
    static uint8_t buf_pos = 8;

    if (buf_pos == 8)
    {
        if (GB_recv_serial_byte(gb, &buf_out) < 1)
        {
            CON_printf("serial_end(): unable to receive serial data\n");
        }

        buf_pos = 0;
    }

    bool ret = buf_out & 0x80;
    buf_out <<= 1;
    buf_pos++;

    return ret;
}

Furthermore, I used the Pokémon Red decompilation project by pret together with Emulicious to see what Pokémon Red was doing. Another huge help was a Flipper Zero implementation of this I found online.

Now everything is ready so we can talk to the emulators’ serial port from Go.

The protocol

(And how we handle it to spoof the trade.)

Note: To actually start a trade in-game, you need to visit Pokémon Center and talk to the person in the back of the Pokémon Center. This is possible as soon as you finished Oak’s Parcel quest.

Overview of the steps done by both Game Boys:

Player talks to Cable Club NPC

To start a trade, both players need to start talking to the Cable Club NPC.

Establish Follower/Leader

The game waits for 90 frames (1.5 seconds) to receive 0x01 or 0x02. If there is nothing received in these 90 frames, the game sends 0x01.
This is done to decide which Game Boy is the leader (sends 0x01) and which one is the follower (sends 0x02).

To spoof the trade, we’re always sending 0x02 right away, and let the Game Boy handle the clocking.

After receiving 0x02, the two Game Boys exchange a bunch of 0x00, followed by some 0x60. This is done to acknowledge the connection.

Both Game Boys are now sending which item in the Cable Club menu is currently selected over and over again. We just echo this data.

Exchange Random Seed

The leader sends seeds for random number generation. This data starts with 10x 0xDF (also called preamble) and is terminated with 9x 0xDF preamble bytes. The follower Game Boy reads them back.

This data isn’t of any use for the spoofing.

Right after this block, the Trade Blocks are exchanged.

Exchange Trading Blocks

Now the fun begins. The leader Game Boy sends 415 bytes of data, which contains the players’ party. The structure is the same as seen in Bulbapedia: Pokémon Data Structure (Generation I).

I’m using the following Go structs for them:

type StatusCondition uint8

const (
	StatusNone      = StatusCondition(0x00)
	StatusAsleep    = StatusCondition(0x04)
	StatusPoisoned  = StatusCondition(0x08)
	StatusBurned    = StatusCondition(0x10)
	StatusFrozen    = StatusCondition(0x20)
	StatusParalyzed = StatusCondition(0x40)
)

type SpeciesType uint8

const (
	TypeNormal   = SpeciesType(0x00)
	TypeFighting = SpeciesType(0x01)
	TypeFlying   = SpeciesType(0x02)
	TypePoison   = SpeciesType(0x03)
	TypeGround   = SpeciesType(0x04)
	TypeRock     = SpeciesType(0x05)
	TypeBird     = SpeciesType(0x06)
	TypeBug      = SpeciesType(0x07)
	TypeGhost    = SpeciesType(0x08)
	TypeFire     = SpeciesType(0x14)
	TypeWater    = SpeciesType(0x15)
	TypeGrass    = SpeciesType(0x16)
	TypeElectric = SpeciesType(0x17)
	TypePsychic  = SpeciesType(0x18)
	TypeIce      = SpeciesType(0x19)
	TypeDragon   = SpeciesType(0x1A)
)

type PartyData struct {
	Index             uint8
	HP                uint16
	Level             uint8
	StatusCondition   StatusCondition
	Type1             SpeciesType
	Type2             SpeciesType
	CatchRate         uint8
	Moves             [4]uint8
	OriginalTrainerID uint16
	Experience        [3]uint8
	EffortValues      EffortValues
	IndividualValues  uint16
	MovesPowerPoints  [4]uint8
	// Level is repeated here
	Level2 uint8
	Stats  Stats
}

type Name [11]uint8

type TradeBlock struct {
	TrainerName [11]uint8
	PartySize   uint8
	// PartyMembers is terminated with 0xFF
	PartyMembers         [7]uint8
	Party                [6]PartyData
	OriginalTrainerNames [6]Name
	Nicknames            [6]Name
}

The data received by the Game Boy uses big endian.

Why the Name type declaration?

Names in the Pokémon Generation I games use a custom encoding for text. See Bulbapedia: Character Encoding (Generation I) for more info.

This the table and the method I use to map Names to Strings
var textTable = map[uint8]string{
	0x4F: "  ",
	0x57: "#",
	0x51: "*",
	0x52: "A1",
	0x53: "A2",
	0x54: "POKé",
	0x55: "+",
	0x58: "$",
	0x75: "…",
	0x7F: " ",
	0x80: "A",
	0x81: "B",
	0x82: "C",
	0x83: "D",
	0x84: "E",
	0x85: "F",
	0x86: "G",
	0x87: "H",
	0x88: "I",
	0x89: "J",
	0x8A: "K",
	0x8B: "L",
	0x8C: "M",
	0x8D: "N",
	0x8E: "O",
	0x8F: "P",
	0x90: "Q",
	0x91: "R",
	0x92: "S",
	0x93: "T",
	0x94: "U",
	0x95: "V",
	0x96: "W",
	0x97: "X",
	0x98: "Y",
	0x99: "Z",
	0x9A: "(",
	0x9B: ")",
	0x9C: ":",
	0x9D: ";",
	0x9E: "[",
	0x9F: "]",
	0xA0: "a",
	0xA1: "b",
	0xA2: "c",
	0xA3: "d",
	0xA4: "e",
	0xA5: "f",
	0xA6: "g",
	0xA7: "h",
	0xA8: "i",
	0xA9: "j",
	0xAA: "k",
	0xAB: "l",
	0xAC: "m",
	0xAD: "n",
	0xAE: "o",
	0xAF: "p",
	0xB0: "q",
	0xB1: "r",
	0xB2: "s",
	0xB3: "t",
	0xB4: "u",
	0xB5: "v",
	0xB6: "w",
	0xB7: "x",
	0xB8: "y",
	0xB9: "z",
	0xBA: "é",
	0xBB: "'d",
	0xBC: "'l",
	0xBD: "'s",
	0xBE: "'t",
	0xBF: "'v",
	0xE0: "'",
	0xE1: "PK",
	0xE2: "MN",
	0xE3: "-",
	0xE4: "'r",
	0xE5: "'m",
	0xE6: "?",
	0xE7: "!",
	0xE8: ".",
	0xED: "?",
	0xEE: "?",
	0xEF: "?",
	0xF0: "¥",
	0xF1: "×",
	0xF3: "/",
	0xF4: ",",
	0xF5: "?",
	0xF6: "0",
	0xF7: "1",
	0xF8: "2",
	0xF9: "3",
	0xFA: "4",
	0xFB: "5",
	0xFC: "6",
	0xFD: "7",
	0xFE: "8",
	0xFF: "9",
}

func DecodeText(b uint8) string {
	s, ok := textTable[b]

	if !ok {
		return "?"
	}

	return s
}

Exchange Patch Lists

To understand the next part, we need an important detail about the Game Boy serial port. You shouldn’t send 0xFE. If there is no serial cable connected, the port tends to read 0xFE, so many games use this as an indicator of an unplugged serial cable.
Still, there might be some 0xFE necessary to transmit the Trade Blocks.

To circumvent this, two Patch Lists are sent over the wire.
A Patch List is a list of indexes inside the Trade Block data, where 0xFE is expected. In the received data, these indexes will be 0xFF.
Both Patch Lists are terminated with 0xFF and the data is padded with 0x00. The size of both Patch Lists is 190 bytes.

Why 2 Lists?
Because 0xFF < 415. The first Patch List contains the indexes 0x00 to 0xFC, the second Path List contains the indexes 0xFD to 0x19E.
All indexes are incremented by 1 when they’re put into the Patch List.

Why 0xFC?
If the index 0xFD needs to be in the Patch List, it would be incremented and inserted as 0xFE.

To handle Patch Lists, I created the following type declaration and a method to create an empty Patch List:

type PatchListData [190]uint8

func NewPatchListData() *PatchListData {
	return &PatchListData{0xFF, 0xFF}
}

To actually keep track of the indexes to be patched, I’m using a linked list and wrote a simple parser:

type PatchIndex struct {
	index int
	next  *PatchIndex
}

func (p PatchListData) Parse() *PatchIndex {
	var root *PatchIndex
	var current *PatchIndex

	base := 0x00

	for _, relativePatchIndex := range p {
		if relativePatchIndex == 0xFF {
			if base == 0x00 {
				base = 0xFC
				continue
			} else {
				// end of patch list
				break
			}
		}

		pi := base + int(relativePatchIndex) - 1

		if root == nil {
			root = &PatchIndex{
				index: pi,
			}

			current = root
		} else {
			next := &PatchIndex{
				index: pi,
			}

			current.next = next
			current = next
		}
	}

	return root
}

As we want to send a spoofed Pokémon over the wire, we need a method to marshal Patch Lists, too:

func (p *PatchIndex) Marshal() *PatchListData {
	res := NewPatchListData()
	maxResLen := uint8(len(res))

	current := p
	base := 0x00

	for idx := uint8(0); idx < maxResLen; idx++ {
		if current == nil {
			res[idx] = 0xFF
			idx++

			if base == 0x00 {
				// still in part zero, write the final terminator, too
				res[idx] = 0xFF
			}

			break
		}

		if base == 0x00 && current.index >= 0xFC {
			base = 0xFC
			res[idx] = 0xFF
			idx++
		}

		res[idx] = uint8(current.index - base)
		current = current.next
	}

	return res
}

And of course, a method to actually patch our trade blocks:

func PatchTradeBlock(tb []uint8) *PatchIndex {
	var root *PatchIndex
	var current *PatchIndex

	for idx, v := range tb {
		if v == 0xFE {
			tb[idx] = 0xFF

			patchedIdx := idx + 1

			if root == nil {
				root = &PatchIndex{
					index: patchedIdx,
				}

				current = root
			} else {
				next := &PatchIndex{
					index: patchedIdx,
				}

				current.next = next
				current = next
			}
		}
	}

	return root
}

func (t *TradeBlock) MarshalPatched() ([]uint8, *PatchListData, error) {
	data, err := Marshal(t)

	if err != nil {
		return nil, nil, err
	}

	var pld *PatchListData

	pi := PatchTradeBlock(data)

	if pi == nil {
		pld = NewPatchListData()
	} else {
		pld = pi.Marshal()
	}

	return data, pld, nil
}

To get the data from the bytes and into the bytes used by the Game Boy, I just use binary.Read and binary.Write:

func Unmarshal(data []uint8, v any) error {
	return binary.Read(bytes.NewReader(data), binary.BigEndian, v)
}

func Marshal(v any) ([]uint8, error) {
	buf := bytes.Buffer{}

	if err := binary.Write(&buf, binary.BigEndian, v); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

Select Pokémon to trade

When the players select a Pokémon to trade, the Game Boy sends 0x60 + <pokemon party index> over the wire. Right after that, a 0x00 follows.

As soon as the player does that, we respond with 0x60, to show that we want to trade the first Pokémon in our party.

Wait for Accept/Reject

The player has now the opportunity to accept or reject this trade.
If it’s rejected, the Game Boy sends 0x61 and the player can select a Pokémon again.
If it’s accepted, the Game Boy sends 0x62 and the trade starts, the trading movie plays.

After that, the random seed, Trading Blocks and Patch Lists are exchanged again, and another trade can be done.

Protocol Dump

This is a dump of the incoming data from the Game Boy:

In order:

  1. Red: Establish Leader/Follower
  2. Violet: Acknowledge Connection
  3. Blue: Random Seed
  4. Yellow: Trade Block
  5. Green: Patch List
  6. Pink: Pokémon #2 of the party is selected and the trade is accepted.

I’m not 100% sure about the data in between 4. and 5.
The spoofer just echos this data. The preamble bytes (0xFD) seem off.

The result