A bridge between mGBA and a Large Language Model that gives every NPC in Pokémon FireRed a unique personality. When you talk to an NPC, the original dialog is intercepted, sent to an LLM (Gemini), and replaced in real-time with a fresh, character-consistent response - so no two conversations are ever the same.
In this version the LLM takes the original NPC message and paraphrases it into a new line of dialog, keeping the meaning but varying the wording each time you talk to that character.
The next milestone is to tag every player interaction with a fingerprint so the system can distinguish between NPCs and objects (signs, bookshelves, item balls, etc.). Once that classification is in place, only NPCs will receive an LLM-generated personality - objects will keep their original text unchanged.
| Tool | Version |
|---|---|
| mGBA | 0.10+ (with scripting support) |
| Python | 3.10+ |
| Pokémon FireRed ROM | US version (NOT Rev 1) |
# 1. Clone the repo
git clone https://github.com/Code-Hdez/pokemon-firered-llm-mod.git
cd firered_llm
# 2. Create a virtual environment and activate it
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Set up your environment variables
cp .env.example .env
# Open .env and paste your Gemini API keyFollow these steps in order every time you play.
- Open mGBA and load your Pokémon FireRed ROM.
- On the title screen (before pressing Start), open the scripting console: Tools → Scripting.
- In the Scripting window click File → Load Script… and pick the Lua script for the mode you want:
Mode Lua script Memory Scan lua/memory_scan_bridge.luaInject Test lua/dialog_injector.luaFingerprint Collector lua/fingerprint_collector.luaLLM Inject lua/dialog_injector.lua - Run the Python side in a terminal (with the virtual environment activated):
python main.py
- Select the matching option from the menu (e.g.
4for LLM Inject). - Press Start on the title screen and play normally - every NPC conversation will now be rewritten by the LLM.
This project was inspired by josh and his project animal-crossing-llm-mod.
Special thanks to the pret team for their incredible work decompiling Pokémon FireRed. Without their reverse engineering efforts, this project would not have been possible.
firered_llm/
├── main.py # Top-level entry point
├── requirements.txt
├── .gitignore
├── LICENSE
│
├── lua/ # mGBA Lua scripts (one per mode)
│ ├── fingerprint_collector.lua # Read-only dialog event collector
│ ├── dialog_injector.lua # Dialog detection + text injection
│ ├── memory_scan_bridge.lua # Minimal READ/FIND memory scanner
│ └── lib/ # Shared Lua library modules
│ ├── commands.lua # Command definitions
│ ├── config.lua # Configuration constants
│ ├── dialog.lua # Dialog detection helpers
│ ├── injector.lua # Text injection logic
│ ├── ipc.lua # IPC / socket layer
│ └── utils.lua # General utilities
│
├── python/ # Python package root
│ ├── main.py # CLI entry point (3-mode menu)
│ ├── config.py # Global configuration
│ ├── protocol.py # IPC message protocol definitions
│ ├── exceptions.py # Custom exception types
│ ├── pokemon_text/ # Pokemon character encoding
│ │ ├── char_table.py # DECODE/ENCODE dicts, encode/decode fns
│ │ └── text_formatter.py # Word-wrap + pagination for dialog boxes
│ ├── classifier/ # Dialog classification engine
│ │ └── dialog_classifier.py # Two-tier classifier (fingerprint DB + EB8 hints)
│ ├── ipc/ # IPC server
│ │ └── server.py # TCP socket server (listens for Lua events)
│ └── apps/ # Application modules
│ ├── memory_scan_app.py # Memory scan server + interactive CLI
│ ├── inject_test_app.py # Text injection test server
│ └── fingerprint_collector_app.py # Fingerprint collection + per-city storage
│
├── data/ # All persistent data
│ ├── fingerprints/ # Per-city fingerprint JSON files (generated)
│ ├── info/ # Documentation & analysis
│ │ ├── technical_analysis.md # Reverse engineering notes
│ │ ├── mGBA_debug_logs_day_1.txt # Session logs
│ │ ├── oak_sequence.txt # Professor Oak dialog sequence
│ │ └── PalletTown_Script.txt # Pallet Town script dump
│ └── reference/ # Reference files
│ └── characters.h # C header with character encoding table
│
└── README.md
- mGBA 0.10+ with scripting support
- Python 3.10+
- Gemini API key - needed for LLM Inject mode (option 4). See
.env.examplefor setup. - Modes 1-3 use the standard library only; mode 4 additionally requires
google-genaiandpython-dotenv(listed inrequirements.txt).
python main.py
# Select option 1 - Memory ScanLua script: lua/memory_scan_bridge.lua
python main.py
# Select option 2 - Inject TestLua script: lua/dialog_injector.lua
python main.py
# Select option 3 - Fingerprint CollectorLua script: lua/fingerprint_collector.lua
python main.py
# Select option 4 - LLM InjectLua script: lua/dialog_injector.lua
Requires a valid
GEMINI_API_KEYin your.envfile.
- Transport: TCP on
127.0.0.1:35600 - Direction: Lua (client) → Python (server)
- Format: Line-delimited JSON (
\nseparator) - Commands (Python → Lua):
PING,READ,FIND,INJECT,STREAM,WATCH - Events (Lua → Python):
hello,pong,dialog_open,dialog_close,dialog_page_wait,dialog_page_advance,map_change,read,find,ack,err
| Address | Size | Description |
|---|---|---|
0x02021D18 |
256B | Primary text display buffer (EWRAM) |
0x03000EB0 |
1B | Script engine state (0/1/2) |
0x03000EB8 |
4B | Script command pointer (ROM) |
0x03000EBC |
4B | NPC/event script pointer (ROM) |
0x03005008 |
4B | gSaveBlock1Ptr (map detection) |
Dialog is detected when all conditions hold simultaneously:
engine_state >= 1(script engine active)- Buffer content changed (snapshot of first 32 bytes differs)
0xFF(EOS) found within first 256 bytes- Text length ≥ 2 bytes
Pokemon FireRed uses a custom encoding (NOT ASCII/UTF-8). Special bytes:
0xFF- End of String (EOS)0xFE- Newline0xFA- Scroll0xFB- Page break0xFC- Extended control code prefix0xFD- Placeholder prefix (StringVar1-4 etc.)
See python/pokemon_text/char_table.py for the full encoding table.