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.
- Emulicious
- GitHub: pret/pokered Decompliation Project
- GitHub: pret/pokered Symbols for Debugging in Emulicious
- GitHub: kbembedded/Flipper-Zero-Game-Boy-Pokemon-Trading
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 Name
s 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:
- Red: Establish Leader/Follower
- Violet: Acknowledge Connection
- Blue: Random Seed
- Yellow: Trade Block
- Green: Patch List
- 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.