Pokémon Fire Red Cheats: 13 Steps, 45 Min Setup [2026]

Most Pokémon Fire Red cheats fail for one dull reason: the code list was written for a different ROM revision than the one you loaded. The codes themselves are rarely the problem. Delta 2.0 is now out as a free download on the App Store, so a new wave of iPhone players is typing GameShark lines into a cheat editor for the first time. That makes this the right week to fix the process instead of hunting for more codes.

This tutorial builds that process as a small Python project. In 13 steps and about 45 minutes you will read your ROM’s header, validate codes before any emulator sees them, generate a RetroArch cheat file plus a paste list, and snapshot your save at every launch. Then you will load the same codes in mGBA, RetroArch and Delta. If you already followed our guide to Pokémon Emerald cheats on mGBA, the menus will feel familiar. The safety net is different, because FireRed ships in two revisions and only the revision byte tells them apart.

A note on scope. This guide does not link to ROMs and does not publish code lists. Use a backup dumped from a cartridge you own, and take codes from a list that states which revision it was tested on. Nintendo has also re-released both games for the Switch family. That official version is a separate product, and nothing here touches it, which is one more reason to keep the two worlds apart.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

Why Pokémon Fire Red Cheats Break on Emulators

A GameShark or Action Replay line is a tiny instruction: write this value to that memory address. The address is fixed inside one specific build of the game. Pokémon FireRed originally launched on Game Boy Advance, and different builds of the game can place data at different addresses. A code written for one build and run on another pokes the wrong byte. Sometimes you see nothing. Sometimes the game freezes, or your party fills with Bad Eggs.

How to read a code line

Take any two-group line and split it in half. In a raw GameShark Advance line, the first group packs the kind of write and the target address, and the second group holds the value to store. An encrypted line scrambles both halves, so you cannot read it by eye. That is why guessing is pointless and why a source note beats memory. Once you know a line is a plain write to one address, the failure modes become obvious: right value, wrong address, and the game reads garbage.

Popular community lists already know this, which is why the better ones split their entries into a Version 1.0 section and a Version 1.1 section. The trouble starts when a player copies a line from the wrong half, or from a forum post that never mentions a version at all. Emulators cannot warn you, because they treat every line as a valid instruction.

Three more traps sit behind the revision problem. First, code types differ. Raw GameShark lines and encrypted Action Replay v3 lines both look like two groups of eight hex digits, but an emulator needs to be told which decoder to use. CodeBreaker lines use a shorter second group. Second, many lists publish a master code that must stay enabled for the other lines to work, and the rule changes between emulators. Third, each front end has its own cheat screen, so a workflow that succeeds in mGBA does not carry over to RetroArch or Delta without translation.

The project below removes those traps one by one. It reads the revision byte from your ROM and refuses to build cheat files for the wrong one. It checks every line against the format you declared. It marks codes as untested until you prove them on a throwaway save. And it copies your save before each session, so a bad code costs you minutes rather than a long playthrough.

Prerequisites and Versions for This Tutorial

You need a computer for the toolkit and at least one of the three emulators. The project uses only Python’s standard library, so there is nothing to pip install. It does rely on tomllib, which arrived in Python 3.11, so older interpreters will fail at import time.

ItemVersion usedPriceWhere cheats live
Python3.11 or newerFreeRuns the toolkit
mGBA0.10.5 (stable line)Free, open source (MPL 2.0)Tools, then Cheats
RetroArch1.22.2 with the mGBA coreFree, open sourceQuick Menu, then Cheats
Delta2.0 (released October 22, 2026)Free download, optional patron purchasesIn-game menu, then Cheats
iOS or iPadOSDelta’s App Store listing asked for iOS 17.4 or later when we checkedFree updateNeeded only for Delta
FireRed or LeafGreen ROM backupDumped from your own cartridgeYour cartridgeSingle .gba file
Save fileCreated by your emulatorFreeA .sav file beside the ROM, by default

Three version notes matter. mGBA’s own news page describes 0.10.5 as a bugfix release and says it may be the last to support Windows 7 through 8.1 and macOS 10.9 through 10.12. A secondary source reports 0.11 development builds, but we tested on the stable line. On the RetroArch side, Libretro’s documentation was still describing 1.22.2 when it was updated on October 24, 2026, so that is the build this guide uses. If you have not set RetroArch up for Pokémon yet, our walkthrough on setting up RetroArch for Pokémon covers cores and controller mapping first.

Check your interpreter before you start:

python3 --version

Expected output on a current Linux or macOS machine:

Python 3.12.3

On Windows, run py -3 --version instead and substitute py -3 wherever this guide types python3.

Step 1: Read Your ROM’s Revision Before Touching Any Code

Every GBA ROM begins with a 192-byte header. Three fields matter here. The four-byte game code sits at offset 0xAC, the software version byte sits at offset 0xBC, and a checksum byte at 0xBD lets you detect a damaged dump. FireRed’s game code starts with BPR and LeafGreen’s starts with BPG. The fourth letter marks the region, and E means the US English release.

On Linux or macOS you can read those bytes with xxd:

xxd -s 0xAC -l 4 firered.gba
xxd -s 0xBC -l 1 firered.gba

A v1.0 US dump prints this:

000000ac: 4250 5245                                BPRE
000000bc: 00                                       .

The final byte is the revision: 00 means v1.0 and 01 means v1.1. If you see a game code that does not start with BPR or BPG, you have a different game, a different region, or a ROM hack. Hacks reuse the FireRed engine but move data around, so a standard list will not fit them. Our guide to setting up mGBA for Pokémon games explains how to confirm a clean dump before you start patching or cheating.

Write the result on a sticky note or in a text file named revision.txt. Everything later in this tutorial branches on it. Windows users can skip xxd, because Step 3 builds a header reader that works anywhere Python runs.

One more check costs ten seconds. Compare the file size on disk with the size your dumping tool reported when you made the backup. A trimmed or truncated dump sometimes breaks save handling, and no cheat list can fix that. Replace it with a fresh dump before you blame the cheats.

Steps 2 and 3: Create the Project and the Header Reader

Make a folder and keep your ROM, the toolkit and your codes together. The toolkit is a single file, so structure stays simple.

mkdir firered-cheat-kit
cd firered-cheat-kit
mkdir tests
cp /path/to/your/firered.gba .

Now create cheatkit.py. This tutorial builds it in five pieces, and each piece goes at the bottom of the same file, with a blank line or two between pieces. Start with the imports, constants and the header reader.

#!/usr/bin/env python3
"""cheatkit: check ROM revisions, validate codes, build cheat files, protect saves."""
from __future__ import annotations

import argparse
import re
import shutil
import subprocess
import sys
import tomllib
import zlib
from datetime import datetime
from pathlib import Path

GAMES = {"BPR": "FireRed", "BPG": "LeafGreen"}
FORMATS = {
    "gsa": re.compile(r"^[0-9A-F]{8} [0-9A-F]{8}$"),
    "cb": re.compile(r"^[0-9A-F]{8} [0-9A-F]{4}$"),
}


def read_header(rom: Path) -> dict:
    data = rom.read_bytes()
    if len(data) < 0xC0:
        raise SystemExit(f"{rom} is too small to be a GBA ROM")
    check = (-sum(data[0xA0:0xBD]) - 0x19) & 0xFF
    return {
        "title": data[0xA0:0xAC].rstrip(b"\0").decode("ascii", "replace"),
        "code": data[0xAC:0xB0].decode("ascii", "replace"),
        "revision": data[0xBC],
        "header_ok": check == data[0xBD],
        "size": len(data),
        "crc32": f"{zlib.crc32(data):08X}",
    }


def describe(h: dict) -> str:
    game = GAMES.get(h["code"][:3], "not FireRed or LeafGreen")
    lines = [
        f"title     : {h['title']}",
        f"game code : {h['code']} ({game})",
        f"revision  : {h['revision']} (0 = v1.0, 1 = v1.1)",
        f"crc32     : {h['crc32']}",
        f"header    : {'checksum OK' if h['header_ok'] else 'CHECKSUM MISMATCH'}",
    ]
    return "\n".join(lines)

The checksum line deserves a sentence. The GBA header stores a complement byte so the BIOS can verify it, and the formula subtracts every byte from 0xA0 through 0xBC plus 0x19. When your dump is intact, the computed value equals the byte at 0xBD. A mismatch tells you the file was edited, truncated or patched, which is useful to know before you stack cheats on top.

Test the reader with a one-liner. This works even before the command-line piece exists, because it calls the functions directly:

python3 -c "import cheatkit, pathlib; print(cheatkit.describe(cheatkit.read_header(pathlib.Path('firered.gba'))))"

Expected output for a clean US v1.0 dump (your CRC32 will differ by dump):

title     : POKEMON FIRE
game code : BPRE (FireRed)
revision  : 0 (0 = v1.0, 1 = v1.1)
crc32     : XXXXXXXX
header    : checksum OK

If the last line says CHECKSUM MISMATCH, stop and get a clean dump. If the revision line disagrees with your xxd reading, you typed an offset wrong. Redo Step 1.

Steps 4 and 5: Describe Your Codes in cheats.toml and Validate Them

Codes live in a TOML file, not in the emulator. That gives you a single source of truth, a place to record where each code came from, and an easy way to rebuild cheat files for any front end. Create cheats.toml next to the toolkit:

[target]
game_code = "BPRE"   # BPRE = FireRed (USA), BPGE = LeafGreen (USA)
revision  = 0        # read this from `cheatkit.py inspect`, never guess

[[cheat]]
name   = "REPLACE ME: master code for this revision"
fmt    = "gsa"
lines  = ["DEADBEEF 0000000A"]
master = true
tested = false
source = "paste the list name and version you copied this from"

[[cheat]]
name   = "REPLACE ME: any single effect"
fmt    = "gsa"
lines  = ["DEADBEEF 0000000B", "DEADBEEF 0000000C"]
tested = false
source = "paste the list name and version you copied this from"

The two entries use DEADBEEF filler on purpose. They are format examples, not real FireRed codes, and the toolkit will not build them into a cheat file until you flip tested to true. Replace them with lines you copied from a list that names your exact revision, and paste the list name into the source field. That habit pays off six months later, when a code misbehaves and you need to know where it came from.

Each field has one job. The target table pins the game code and revision, so a file written for v1.0 can never be applied to a v1.1 ROM. The fmt field says which line shape to expect. The master flag marks the code that must load first. The tested flag keeps unproven lines out of your daily cheat file. Here is how the common formats map to those labels:

FormatLine shapetoolkit fmtNotes
GameShark Advance (raw)8 hex digits, space, 8 hex digitsgsaMost FireRed lists use this shape
Action Replay v3 (encrypted)8 hex digits, space, 8 hex digitsgsaLooks random, the emulator decodes it when you pick the right type
CodeBreaker8 hex digits, space, 4 hex digitscbShorter second group
Game GenieNot used on GBAnoneBelongs to older Nintendo and Sega systems
RetroArch .cht entryOne code string, lines joined with a plus signgeneratedBuilt by Step 6

Because raw and encrypted lines share a shape, the validator cannot tell them apart. Emulators also differ in which types they decode, which is part of the story in our comparison of mGBA, VBA-M and SkyEmu, so always test in the emulator you will actually play in. It catches the mistakes that do happen constantly: a missing digit, the letter O typed in place of zero, a stray quote pasted from a web page, or the same line listed twice under different names. Add the validation piece below the header reader.

def load_config(path: Path) -> dict:
    with path.open("rb") as handle:
        return tomllib.load(handle)


def normalize(line: str) -> str:
    return " ".join(line.upper().split())


def validate(cfg: dict) -> list[str]:
    problems: list[str] = []
    target = cfg.get("target", {})
    if str(target.get("game_code", ""))[:3] not in GAMES:
        problems.append("target.game_code must start with BPR (FireRed) or BPG (LeafGreen)")
    if target.get("revision") not in (0, 1):
        problems.append("target.revision must be 0 or 1")
    seen: dict[str, str] = {}
    masters = 0
    for index, cheat in enumerate(cfg.get("cheat", []), start=1):
        label = f"cheat #{index} ({cheat.get('name', 'unnamed')})"
        fmt = cheat.get("fmt")
        if fmt not in FORMATS:
            problems.append(f"{label}: fmt must be one of {sorted(FORMATS)}")
            continue
        lines = cheat.get("lines") or []
        if not lines:
            problems.append(f"{label}: no code lines")
        for raw in lines:
            line = normalize(raw)
            if not FORMATS[fmt].match(line):
                problems.append(f"{label}: '{raw}' does not match format {fmt}")
            elif line in seen:
                problems.append(f"{label}: line {line} already used by {seen[line]}")
            else:
                seen[line] = label
        if not cheat.get("source"):
            problems.append(f"{label}: add a source note so you can retest it later")
        masters += bool(cheat.get("master"))
    if masters > 1:
        problems.append("only one entry may be flagged master = true")
    return problems

Run the checker through the command-line piece in Step 8, or test it now in a Python shell. Feed it a deliberately broken entry and you should see this:

PROBLEM: cheat #1 (bad): '1234 5678' does not match format gsa
PROBLEM: cheat #1 (bad): add a source note so you can retest it later

A clean file prints config OK and returns exit code 0, so you can drop the check into a pre-launch script later.

Step 6: Build RetroArch Cheat Files and Paste Lists

The builder does the work that people usually do by hand with a text editor. It compares your ROM’s header with the target table, drops every code that is not marked tested, sorts the master code to the top and writes two files. One is a .cht file that RetroArch can load directly. The other is a plain paste list for mGBA and Delta, where you enter codes through a form.

def build(cfg: dict, header: dict, rom: Path, out_dir: Path, include_untested: bool) -> list[Path]:
    target = cfg["target"]
    if header["code"] != target["game_code"] or header["revision"] != target["revision"]:
        raise SystemExit(
            f"Refusing to build: ROM is {header['code']} rev {header['revision']}, "
            f"codes are for {target['game_code']} rev {target['revision']}"
        )
    chosen = [c for c in cfg.get("cheat", []) if c.get("tested") or include_untested]
    skipped = len(cfg.get("cheat", [])) - len(chosen)
    chosen.sort(key=lambda c: not c.get("master", False))
    out_dir.mkdir(parents=True, exist_ok=True)

    cht = [f"cheats = {len(chosen)}", ""]
    paste: list[str] = []
    for i, cheat in enumerate(chosen):
        code = "+".join(normalize(x) for x in cheat["lines"])
        name = cheat["name"].replace('"', "'")
        cht += [
            f'cheat{i}_desc = "{name}"',
            f'cheat{i}_code = "{code}"',
            f"cheat{i}_enable = {'true' if cheat.get('master') else 'false'}",
            "",
        ]
        paste += [f"# {name}", *[normalize(x) for x in cheat["lines"]], ""]
    cht_path = out_dir / f"{rom.stem}.cht"
    paste_path = out_dir / f"{rom.stem}.paste.txt"
    cht_path.write_text("\n".join(cht), encoding="utf-8")
    paste_path.write_text("\n".join(paste), encoding="utf-8")
    print(f"built {len(chosen)} cheat(s), skipped {skipped} untested")
    return [cht_path, paste_path]

Three decisions in that function are worth defending. The revision gate raises an error instead of printing a warning, because a warning scrolls past and a wrong-revision cheat file can wreck a save. The builder joins multi-line codes with a plus sign, which is the convention RetroArch’s cheat format uses for codes that span several lines. And the master code starts enabled, while every other entry starts disabled, so you opt in to each effect during play rather than discovering five active codes at once.

The .cht file name comes from the ROM name, because RetroArch pairs cheat files with content by name. Keep the ROM file name stable and the pairing keeps working.

Steps 7 and 8: Snapshot Your Save, Then Run the Toolkit

The last two pieces add save protection and the command-line wrapper. The snapshot function copies your .sav into a save-snapshots folder with a timestamp and prunes the folder so only the newest copies survive. The restore function puts the newest snapshot back and keeps the current file as a .before-restore copy, so even a restore is reversible.

def snapshot(save: Path, keep: int) -> Path:
    if not save.is_file():
        raise SystemExit(f"No save file at {save}")
    folder = save.parent / "save-snapshots"
    folder.mkdir(exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    dest = folder / f"{save.stem}-{stamp}{save.suffix}"
    shutil.copy2(save, dest)
    for old in sorted(folder.glob(f"{save.stem}-*{save.suffix}"))[:-keep]:
        old.unlink()
    return dest


def restore(save: Path) -> Path:
    folder = save.parent / "save-snapshots"
    snaps = sorted(folder.glob(f"{save.stem}-*{save.suffix}"))
    if not snaps:
        raise SystemExit("No snapshots to restore")
    if save.exists():
        shutil.copy2(save, save.with_suffix(save.suffix + ".before-restore"))
    shutil.copy2(snaps[-1], save)
    return snaps[-1]

If you later want an outside program to drive mGBA instead of a person, our mGBA MCP setup shows one way to script the emulator, and the launch wrapper below still gives you the save safety net first.

The wrapper ties everything into six commands: inspect, check, build, snapshot, restore and launch. The launch command is the one you will use daily. It takes a snapshot first and then starts your emulator.

def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(prog="cheatkit")
    sub = parser.add_subparsers(dest="cmd", required=True)
    p = sub.add_parser("inspect"); p.add_argument("rom", type=Path)
    p = sub.add_parser("check"); p.add_argument("config", type=Path)
    p = sub.add_parser("build")
    p.add_argument("config", type=Path); p.add_argument("--rom", type=Path, required=True)
    p.add_argument("--out", type=Path, default=Path("dist")); p.add_argument("--include-untested", action="store_true")
    p = sub.add_parser("snapshot"); p.add_argument("save", type=Path); p.add_argument("--keep", type=int, default=10)
    p = sub.add_parser("restore"); p.add_argument("save", type=Path)
    p = sub.add_parser("launch")
    p.add_argument("--save", type=Path, required=True); p.add_argument("--keep", type=int, default=10)
    p.add_argument("command", nargs=argparse.REMAINDER)
    args = parser.parse_args(argv)

    if args.cmd == "inspect":
        print(describe(read_header(args.rom)))
    elif args.cmd == "check":
        problems = validate(load_config(args.config))
        print("\n".join(f"PROBLEM: {x}" for x in problems) or "config OK")
        return 1 if problems else 0
    elif args.cmd == "build":
        cfg = load_config(args.config)
        problems = validate(cfg)
        if problems:
            print("\n".join(f"PROBLEM: {x}" for x in problems))
            return 1
        for path in build(cfg, read_header(args.rom), args.rom, args.out, args.include_untested):
            print("wrote", path)
    elif args.cmd == "snapshot":
        print("saved", snapshot(args.save, args.keep))
    elif args.cmd == "restore":
        print("restored from", restore(args.save))
    elif args.cmd == "launch":
        command = [x for x in args.command if x != "--"]
        if not command:
            raise SystemExit("Add the emulator command after --")
        print("snapshot:", snapshot(args.save, args.keep))
        return subprocess.call(command)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Now run the full sequence once. The first build skips the placeholder codes because they are untested, which proves the gate works. The second build forces them in so you can inspect the files.

python3 cheatkit.py inspect firered.gba
python3 cheatkit.py check cheats.toml
python3 cheatkit.py build cheats.toml --rom firered.gba
python3 cheatkit.py build cheats.toml --rom firered.gba --include-untested

Expected output of the last two commands:

built 0 cheat(s), skipped 2 untested
wrote dist/firered.cht
wrote dist/firered.paste.txt
built 2 cheat(s), skipped 0 untested
wrote dist/firered.cht
wrote dist/firered.paste.txt

Open dist/firered.cht and you will find this structure, shown here with the filler lines:

cheats = 2

cheat0_desc = "REPLACE ME: master code for this revision"
cheat0_code = "DEADBEEF 0000000A"
cheat0_enable = true

cheat1_desc = "REPLACE ME: any single effect"
cheat1_code = "DEADBEEF 0000000B+DEADBEEF 0000000C"
cheat1_enable = false

Finally, try the launch command with a harmless stand-in for the emulator. It confirms the snapshot happens before anything starts:

python3 cheatkit.py launch --save firered.sav -- echo emulator-would-start
snapshot: save-snapshots/firered-20261025-180921.sav
emulator-would-start

If your save does not exist yet, the command exits with a message saying so. That is expected on a brand-new setup. Create the save by playing to your first in-game save point, then rerun it.

Step 9: Load Pokémon Fire Red Cheats in mGBA

mGBA is the simplest place to start because its cheat window accepts pasted lines directly. mGBA covers Game Boy Advance along with Game Boy and Game Boy Color, and the mGBA FAQ is the place to check platform questions. Launch it through the toolkit so a snapshot happens first. On Linux the Qt binary is usually called mgba-qt, on Windows it is mGBA.exe:

python3 cheatkit.py launch --save firered.sav -- mgba-qt firered.gba

With the game running, open Tools and choose Cheats. The exact menu wording can shift slightly between releases, but the flow stays the same:

  1. Click Add New Set and name it after the effect, for example the same name you used in cheats.toml.
  2. Add the lines from dist/firered.paste.txt under that set. mGBA accepts GameShark, Pro Action Replay and CodeBreaker formats, and its autodetect handles most pasted lists. If the dialog offers a type selector, match it to the label on your source list.
  3. Tick the checkbox beside the set to enable it. Untick it to disable it.
  4. Leave the window open while you test, then close it. mGBA remembers sets per ROM, so they return next time.

Enable the master code first if your list publishes one, then enable one effect at a time. Some emulators need the master code and others ignore it, so test both ways once and write the result in the source note. A thirty-second test now beats a corrupted party later.

Pay attention to the difference between a battery save and a save state. The in-game save writes the .sav file, which is the file the toolkit snapshots. A save state freezes the whole console, including whatever your active cheats already wrote into memory. Load a state made while a risky code was running and the damage is back, even if you disabled the code since. Treat states as temporary bookmarks, never as backups, and make your real saves through the game’s own menu with effects switched off.

Save location matters for the toolkit. By default mGBA writes the .sav next to the ROM with the same base name, which is why the launch command can find it. If you changed the save directory in the settings, point –save at the new path.

Step 10: Load Cheats in RetroArch 1.22.2

RetroArch does not emulate the console itself. It loads cores, and for Game Boy Advance the mGBA core does the work. If you have not installed it, open Online Updater, choose Core Downloader and pick the Nintendo Game Boy Advance mGBA core. The RetroArch documentation lists the current menu labels, and our RetroArch cheat menu walkthrough for PC shows the general cheat screen in more detail.

You have two ways to get codes in. The first is RetroArch’s own cheat database, which you can fetch from the Online Updater. Its files are named after the game and region, so look for the entry that matches your revision. The second is the .cht file your toolkit just built. Use the second route when you want only the codes you have proven.

  1. Start the game with the mGBA core.
  2. Open the Quick Menu and choose Cheats.
  3. Choose Load Cheat File (Replace) and browse to dist/firered.cht.
  4. Toggle the entries you want, then choose Apply Changes if your build does not apply them on toggle.
  5. Optionally turn on automatic cheat loading at game start, so the master code is active before the title screen.

A few details save time. The database files are organized by system, and the names often carry region and revision tags, so a close match is not enough. Open the entry, read the code lines, and compare them with the list you trust before you toggle anything. When two sources disagree, believe the one that names your revision. Also remember that RetroArch applies cheats to whichever core is running, so a cheat file loaded under a different core, such as an older Game Boy Advance core, may decode the same lines differently.

RetroArch pairs cheat files with content by file name. If your ROM is called firered.gba, the file should be firered.cht. Rename one to match the other when the pairing fails. Menu labels shift between RetroArch releases, so trust the structure more than the exact wording.

Step 11: Use Pokémon Fire Red Cheats in Delta 2.0 on iPhone

Delta is the quickest route for iPhone and iPad players, and it just became more visible. The Delta 2.0 release added Sega Genesis support, RetroAchievements, a Liquid Glass redesign, animated controller skins and optimization for the iPhone Duo. The Delta and AltStore announcement also said the app passed 20 million downloads after two years. It is available through the App Store and, in supported regions, AltStore PAL. For a longer setup walkthrough, see our guide to Delta for Pokémon on iPhone.

Delta 2.0 added Action Replay and Game Genie cheat support for Genesis games, which tells you cheat entry is a mature part of the app. FireRed is a Game Boy Advance title, so that Genesis change does not affect it, but the existing GBA cheat editor works as before. The Delta user guide documents the current screens.

The flow on your phone looks like this:

  1. Open FireRed in Delta and pause to reach the in-game menu.
  2. Choose the cheats entry, then add a new cheat.
  3. Pick the code type that matches your list, enter a name, and paste the lines from dist/firered.paste.txt.
  4. Save the cheat and switch it on. Resume play and check the effect.

The toolkit cannot run on the phone itself, so you will copy codes across by hand, using AirDrop, Notes or a shared file. Snapshots work differently here. Delta’s settings include sync options, and you can also export saves through the Files app. Check the Delta user guide for the current export path before you rely on either one, and make a manual copy before your first cheat session.

One caution for achievement hunters. RetroAchievements runs a hardcore mode whose rules ban cheats, so keep that mode off while you experiment. And the iPhone Duo optimization is aimed at dual-screen systems like the Nintendo DS. FireRed is single-screen, so it changes nothing for this tutorial.

Steps 12 and 13: Test on a Throwaway Save, Then Promote the Code

Never test a new code on your real save. Make a copy of the ROM under a new name, so the emulator creates a separate save file:

cp firered.gba firered-test.gba
python3 cheatkit.py build cheats.toml --rom firered-test.gba --include-untested

Start a new game on the test copy and work through the same short routine for each code. Do one code at a time, with the master code on if the list requires it.

  1. Enable the code and play for a minute. Walk, open the menu, enter a building.
  2. Check the effect. Did the value change, and did anything unrelated change with it?
  3. Save in the game, close the emulator, and reopen it. A code that survives a save and reload is much safer than one that only looks right on screen.
  4. Disable the code, play another minute, and confirm the game behaves normally.
  5. Record the outcome in a small log with the date, emulator, revision and result.

Keep the log boring and consistent. One line per test is enough, and a CSV file opens anywhere:

echo "date,emulator,revision,code,result" > testlog.csv
echo "2026-10-25,mGBA 0.10.5,rev0,master+effect-1,pass" >> testlog.csv
echo "2026-10-25,Delta 2.0,rev0,master+effect-1,pass" >> testlog.csv

A code earns its tested flag only when every emulator you use has a pass line for it. That rule sounds strict, and it saves arguments with your future self. Different front ends decode types differently, so a pass in mGBA says little about Delta, and a pass on the desktop says nothing about the phone.

For Step 13, edit cheats.toml and set tested = true on every code that passed on every emulator you care about. Rebuild without the include flag:

python3 cheatkit.py build cheats.toml --rom firered.gba

The output now lists only proven codes, and that is the file you load in your real game. If a promoted code misbehaves later, set tested back to false and rebuild. The history stays in the file, which is the point of keeping codes in TOML rather than inside three different emulators.

To roll back after a bad session, close the emulator and run the restore command:

python3 cheatkit.py restore firered.sav
restored from save-snapshots/firered-20261025-180921.sav

Your damaged save sits beside the original as firered.sav.before-restore, so you can still inspect it if you want to understand what went wrong.

Choosing where to play

After the tests, pick one home for daily play. mGBA gives you the cleanest cheat window and the easiest save access, which makes it the best desk setup. RetroArch earns its place when you already run it for other consoles and want cheats, shaders and controller profiles in one frontend. Delta wins on a phone or tablet because nothing else matches its touch controls and skins. Whichever you choose, keep the toolkit on a computer and treat the phone as a consumer of its output.

7 Common Pitfalls With Pokémon Fire Red Cheats

These mistakes cause most of the bug reports that surround cheat lists. Each one is cheap to avoid once you know it exists.

  1. Using the wrong revision. A v1.0 line on a v1.1 ROM is the number one cause of freezes. Read the revision byte, then pick the matching list. The toolkit refuses mismatches for exactly this reason.
  2. Stacking many codes at once. Ten active codes make it impossible to tell which one broke the game. Enable one, test, then add the next.
  3. Saving while an unusual code is active. Codes that change levels, encounters or items can write values the game never expects. If you save in that state, the damage becomes permanent. Turn the code off before you save in-game.
  4. Skipping the master code test. Some lists require it and some emulators ignore it. Check both ways once and note the result.
  5. Copying from web pages without cleaning. Smart quotes, non-breaking spaces and the letter O in place of the digit zero all produce lines that look right and fail. The validator catches these.
  6. Applying standard codes to a ROM hack. Hacks built on the FireRed engine move data, so base-game lines misfire. Our guides to Pokémon Unbound with mGBA and Radical Red cover hack-specific setup, and you should look for hack-specific codes.
  7. Trusting a code because it worked elsewhere. Older emulators such as VBA-M handle some code types differently from mGBA. A line that passed in one program can fail in another, so repeat your throwaway-save test in every emulator you plan to use.

12 Troubleshooting Fixes

Start with the symptom you see. Most entries resolve in under two minutes. If none fits, the gaming section has emulator-specific guides you can compare against.

SymptomLikely causeFix
Game freezes after enabling a codeRevision mismatch or missing master codeRun inspect, confirm the revision, switch to the matching list, retest with the master code on
Code does nothingCheat set not enabled, or RetroArch changes not appliedTick the mGBA checkbox, use Apply Changes in RetroArch, flip the toggle in Delta
Cheat vanishes after a resetEmulator reloaded without re-enablingTurn on RetroArch auto-apply, or re-tick the set in mGBA after each reset
PROBLEM: does not match formatTypo, letter O for zero, stray characterRetype the line by hand and run check again
Refusing to buildROM revision differs from the target tableFix revision in cheats.toml or use the list for your ROM
Black screen with master code onMaster code not needed or not valid in this emulatorDisable it, test the other codes without it, record the result
Garbled item names or Bad EggsA code wrote values the game cannot parseClose without saving, run restore, remove the offending code
RetroArch ignores the .cht fileFile name does not match the ROM nameRename it to match, or load it with Load Cheat File
Delta rejects a codeWrong code type or wrong digit countRe-pick the type and compare length against the format table
Save missing after launchSave directory differs from the ROM folderPoint –save at the real path shown in the emulator settings
ModuleNotFoundError for tomllibPython older than 3.11Upgrade Python and rerun
Achievements stop unlocking in DeltaHardcore mode rules conflict with cheatsTurn off hardcore mode while testing cheats

When a problem does not match any row, gather three facts before you change anything. Run the inspect command and note the revision. Run the check command and confirm the config is clean. Then rebuild with the include flag off and see whether the code in question was even in the file. Those three answers eliminate wrong ROMs, bad lines and untested entries in under a minute, and what remains is usually an emulator setting.

Two habits prevent most of this table. Keep the snapshot folder on a different drive from your emulator if you can, and never delete the before-restore copy until you are sure the restored save is healthy.

Advanced Tips and the Finished Project

Advanced tips

Once the basic loop works, a few upgrades turn it into a proper cheat workshop. Search memory yourself when a list has no code for what you want. mGBA ships a memory viewer, memory search tools and scripting support, so you can hunt for values yourself. Be aware that Generation III games encrypt some values, including money and item quantities, with a key stored in the save data. A naive search for your cash total will fail for that reason, so look for unencrypted values such as your party’s current HP first.

Track cheats.toml in git. Every change to a tested flag is then a commit, and a bad promotion is one revert away. Add a crc32 field per ROM if you manage several dumps, and have the builder compare it. The header reader already prints the value, so the check is a few lines.

Wrap the daily routine in a shell script so you never skip a step. Save this as play.sh, make it executable, and call it instead of the emulator:

#!/bin/sh
set -eu
python3 cheatkit.py check cheats.toml
python3 cheatkit.py build cheats.toml --rom firered.gba
exec python3 cheatkit.py launch --save firered.sav -- mgba-qt firered.gba

The set -eu line makes the script stop at the first failure, so a config error blocks the launch instead of scrolling past. On Windows, a short batch file with the same three commands does the job.

Know when a cheat is the wrong tool. A RAM cheat rewrites memory while the game runs, which is fragile by design. If you want a permanent change, such as a different starting item or a rebalanced game, a ROM patch applies the edit once at load time and leaves nothing running in the background. Patches use their own formats and tools, and they change the file’s checksum, so run the inspect command again afterward and update your notes. The same idea explains why ROM hacks feel more stable than a pile of live codes.

Mirror the snapshot folder somewhere that is not your emulator’s drive. A cloud-synced folder or a second disk is enough. The toolkit keeps ten copies by default, which covers a typical stretch of daily sessions, and you can raise the number with the keep option.

Keep one cheats file per revision and per game, for example cheats-bpre-rev0.toml and cheats-bpge-rev0.toml. LeafGreen shares the engine but not every address, so never reuse a FireRed list for it without testing. If you play challenge runs, remember that cheats usually break the spirit of the rules. Our Nuzlocke emulator setup shows how to keep a run honest while still using save states for safety.

Final layout and run

Your finished folder looks like this:

firered-cheat-kit/
  cheatkit.py          (the five pieces from Steps 3 to 8)
  cheats.toml          (your codes, with source notes)
  firered.gba          (your own dump)
  firered.sav          (created by the emulator)
  save-snapshots/      (timestamped copies, newest 10 kept)
  dist/                (generated .cht and paste list)
  tests/test_cheatkit.py

The tests cover the header reader, the validator, the revision gate and snapshot rotation. Save this as tests/test_cheatkit.py:

import tempfile
import unittest
from pathlib import Path

import cheatkit


def fake_rom(code=b"BPRE", revision=0, size=0x100):
    data = bytearray(size)
    data[0xA0:0xAC] = b"POKEMON FIRE"
    data[0xAC:0xB0] = code
    data[0xBC] = revision
    data[0xBD] = (-sum(data[0xA0:0xBD]) - 0x19) & 0xFF
    return bytes(data)


class CheatKitTests(unittest.TestCase):
    def test_header_and_checksum(self):
        with tempfile.TemporaryDirectory() as tmp:
            rom = Path(tmp) / "fake.gba"
            rom.write_bytes(fake_rom(revision=1))
            header = cheatkit.read_header(rom)
            self.assertEqual(header["code"], "BPRE")
            self.assertEqual(header["revision"], 1)
            self.assertTrue(header["header_ok"])

    def test_validate_catches_bad_lines(self):
        cfg = {"target": {"game_code": "BPRE", "revision": 0},
               "cheat": [{"name": "x", "fmt": "gsa", "lines": ["1234 5678"], "source": "s"}]}
        self.assertTrue(any("does not match" in p for p in cheatkit.validate(cfg)))

    def test_revision_mismatch_blocks_build(self):
        with tempfile.TemporaryDirectory() as tmp:
            rom = Path(tmp) / "fake.gba"
            rom.write_bytes(fake_rom(revision=1))
            cfg = {"target": {"game_code": "BPRE", "revision": 0}, "cheat": []}
            with self.assertRaises(SystemExit):
                cheatkit.build(cfg, cheatkit.read_header(rom), rom, Path(tmp) / "out", False)

    def test_snapshot_rotation(self):
        with tempfile.TemporaryDirectory() as tmp:
            save = Path(tmp) / "game.sav"
            save.write_bytes(b"abc")
            for n in range(4):
                (save.parent / "save-snapshots").mkdir(exist_ok=True)
                (save.parent / "save-snapshots" / f"game-0000000{n}-000000.sav").write_bytes(b"x")
            cheatkit.snapshot(save, keep=3)
            self.assertEqual(len(list((save.parent / "save-snapshots").glob("game-*.sav"))), 3)


if __name__ == "__main__":
    unittest.main()

Run them from the project folder:

python3 -m unittest tests.test_cheatkit -v
test_header_and_checksum ... ok
test_revision_mismatch_blocks_build ... ok
test_snapshot_rotation ... ok
test_validate_catches_bad_lines ... ok

Ran 4 tests in 0.002s

OK

Your daily routine is now three lines. Rebuild after any edit with the build command, launch through the wrapper so a snapshot happens first, and promote codes only after they pass on the throwaway copy. That loop is the whole product, and it works the same way for every Game Boy Advance game you add later.

FAQ

Do Pokémon Fire Red cheats work on LeafGreen?

Not safely. The two games share an engine and many addresses, but they are separate builds. Use a list written for LeafGreen, check that your game code starts with BPG, and set the target table accordingly. Treat every code as untested until it passes on a throwaway save.

What is the difference between FireRed v1.0 and v1.1?

They are two releases of the same game with different internal layouts. Both report the same game code, so the revision byte at offset 0xBC is what separates them. Cheat lines are tied to one layout, which is why quality lists split their entries by version.

Can I use these cheats on the official Switch version?

No. The Switch release is a separate product, and these methods apply to Game Boy Advance ROM backups running in an emulator. Code lines target memory addresses in the original GBA builds, and the official release gives you no way to enter them.

Do I need a master code?

It depends on the list and the emulator. Some encrypted code sets require it, and some emulators work without it. Test your codes with and without the master code once, then record the result in the source field of cheats.toml.

Which emulator is best for Pokémon FireRed cheats?

mGBA is the most direct, because its cheat window accepts pasted lines and keeps sets per ROM. RetroArch is better when you want one cheat file across devices. Delta is the practical choice on iPhone and iPad. All three accept the same codes once you pick the right type.

Will cheats corrupt my save file?

They can, which is why the toolkit snapshots your save before each launch. Wrong-revision codes and codes that change levels or items cause most damage. Test on a throwaway copy, turn effects off before saving in-game, and keep the before-restore file until you are sure.

Is it legal to use a FireRed ROM in an emulator?

Laws differ by country, and this guide does not give legal advice. It does not link to ROMs. The safer path is a backup dumped from a cartridge you own, or the official Switch release if you want to play without emulation.

Marcus Chen

Marcus Chen

Gaming & Consumer Tech Editor

Marcus Chen is a senior editor at Tech Insider, where he leads coverage of the US online gaming market, including sweepstakes and social casinos, alongside consumer technology. He evaluates operators on their published terms, licensing and RNG certifications, stated redemption policies, and corroborating independent reporting, and writes plainly about what the evidence supports. Tech Insider does not run first-party money tests and does not gamble with reader funds. Marcus has reported on the technology and online-gaming industries for more than a decade.

View all articles