Agent Skills: mGBA Scripting

Use when scripting mGBA emulator automation in Lua - console API, ROM and save state operations, frame callbacks, input handling, memory reading for cheats and RAM watches, sockets, or TAS and speedrunner tooling

UncategorizedID: CodeAtCode/oss-ai-skills/mgba

Install this agent skill to your local

pnpm dlx add-skill https://github.com/CodeAtCode/oss-ai-skills/tree/HEAD/extend/mgba

Skill Files

Browse the full folder contents for mgba.

Download Skill

Loading file tree…

extend/mgba/SKILL.md

Skill Metadata

Name
mgba
Description
Use when scripting mGBA emulator automation in Lua - console API, ROM and save state operations, frame callbacks, input handling, memory reading for cheats and RAM watches, sockets, or TAS and speedrunner tooling

mGBA Scripting

Lua scripting for mGBA emulator.

Overview

Starting with version 0.10, mGBA has built-in scripting capabilities. To use scripting, click "Scripting..." from the Tools menu. Currently, only Lua scripting is supported.

Key Features:

  • Full memory access (ROM, RAM, MMIO)
  • Input manipulation (button presses)
  • Save state management
  • Callbacks for frame/events
  • TCP socket networking
  • Console output
  • Screenshot capture

Opening Scripting Console

Tools → Scripting...

This opens a console where you can load and run Lua scripts.

Top-Level Objects

Available Objects

-- emu: CoreAdapter instance (available when game loaded)
-- C: Exported constants
-- callbacks: CallbackManager instance
-- console: Console instance
-- util: Basic utility library
-- socket: TCP socket library

Console Output

console.log("Info message")
console.warn("Warning message")
console.error("Error message")

-- Create text buffer
buffer = console.createBuffer("My Buffer")
buffer:print("Text in buffer")
buffer:clear()

Utility Functions

-- Expand bitmask to list
bits = util.expandBitmask(0xFF)  -- {0,1,2,3,4,5,6,7}

-- Make bitmask from list
mask = util.makeBitmask({0, 3, 5})  -- 0x29

Core API

ROM Operations

-- Load ROM file
success = emu.loadFile("path/to/rom.gba")

-- Get game info
title = emu.getGameTitle()  -- "POKEMON FIRE"
code = emu.getGameCode()   -- "AGB-P-FE"
size = emu.romSize()       -- ROM size in bytes
platform = emu.platform()  -- 0=GBA, 1=GB
checksum = emu.checksum()  -- CRC32

Save States

-- Save to file
emu.saveStateFile("path/to/state.state")

-- Load from file
emu.loadStateFile("path/to/state.state")

-- Save to slot (0-9)
emu.saveStateSlot(0)
emu.loadStateSlot(0)

-- Save/load buffer
buffer = emu.saveStateBuffer()
emu.loadStateBuffer(buffer)

-- Flags: SCREENSHOT=1, SAVEDATA=2, CHEATS=4, RTC=8, METADATA=16
-- ALL = 31
emu.saveStateSlot(0, 31)  -- Save everything

Frame Control

-- Run one frame
emu.runFrame()

-- Run one instruction
emu.step()

-- Get current frame number
frame = emu.currentFrame()

-- Get cycle info
cycles = emu.frameCycles()    -- Cycles per frame
freq = emu.frequency()         -- Cycles per second

Input Handling

-- Set all keys at once
emu.setKeys(0x0000)  -- No keys

-- Add keys (OR with existing)
emu.addKeys(C.GBA_KEY.A + C.GBA_KEY.R)

-- Clear keys
emu.clearKeys(C.GBA_KEY.B)

-- Check key state
if emu.getKey(C.GBA_KEY.UP) == 1 then
    print("UP is pressed")
end

-- Get all pressed keys
keys = emu.getKeys()

GBA Key Constants

C.GBA_KEY.A      = 0
C.GBA_KEY.B      = 1
C.GBA_KEY.SELECT = 2
C.GBA_KEY.START  = 3
C.GBA_KEY.RIGHT  = 4
C.GBA_KEY.LEFT   = 5
C.GBA_KEY.UP     = 6
C.GBA_KEY.DOWN   = 7
C.GBA_KEY.R      = 8
C.GBA_KEY.L      = 9

Game Boy Key Constants

C.GB_KEY.A       = 0
C.GB_KEY.B       = 1
C.GB_KEY.SELECT  = 2
C.GB_KEY.START   = 3
C.GB_KEY.RIGHT   = 4
C.GB_KEY.LEFT    = 5
C.GB_KEY.UP      = 6
C.GB_KEY.DOWN    = 7

Memory Access

Reading Memory

-- Read 8/16/32 bit values
value8 = emu.read8(address)
value16 = emu.read16(address)
value32 = emu.read32(address)

-- Read range
data = emu.readRange(address, length)

-- Read from memory domain
rom = emu.memory["cart0"]
value = rom:read8(offset)
data = rom:readRange(offset, length)

Writing Memory

-- Write 8/16/32 bit values
emu.write8(address, value)
emu.write16(address, value)
emu.write32(address, value)

Memory Domains (GBA)

-- Available memory domains
emu.memory["bios"]    -- BIOS (0x00000000)
emu.memory["wram"]     -- EWRAM (0x02000000)
emu.memory["iwram"]    -- IWRAM (0x03000000)
emu.memory["io"]       -- MMIO (0x04000000)
emu.memory["palette"]  -- Palette (0x05000000)
emu.memory["vram"]     -- VRAM (0x06000000)
emu.memory["oam"]      -- OAM (0x07000000)
emu.memory["cart0"]    -- ROM (0x08000000)
emu.memory["cart1"]    -- ROM WS1 (0x0a000000)
emu.memory["cart2"]    -- ROM WS2 (0x0c000000)

Memory Domains (GB)

emu.memory["cart0"]  -- ROM Bank ($0000)
emu.memory["vram"]   -- VRAM ($8000)
emu.memory["sram"]   -- SRAM ($a000)
emu.memory["wram"]   -- WRAM ($c000)
emu.memory["oam"]    -- OAM ($fe00)
emu.memory["io"]     -- MMIO ($ff00)
emu.memory["hram"]   -- HRAM ($ff80)

Callbacks

Adding Callbacks

-- Add callback (returns callback ID)
id = callbacks.add("frame", function()
    -- Called every frame
end)

id = callbacks.add("start", function()
    -- Called when emulation starts
end)

id = callbacks.add("reset", function()
    -- Called when emulation resets
end)

id = callbacks.add("shutdown", function()
    -- Called when emulation stops
end)

-- Remove callback
callbacks.remove(id)

Available Callbacks

-- alarm     - In-game alarm went off
-- crashed   - Emulation crashed
-- frame     - Frame finished
-- keysRead  - About to read key input
-- reset     - Emulation reset
-- savedataUpdated - Save data modified
-- sleep     - Entered low-power mode
-- shutdown  - Powered off
-- start     - Started
-- stop      - Voluntarily shut down

Frame Callback Example

-- Auto-press A every 10 frames
local counter = 0
callbacks.add("frame", function()
    counter = counter + 1
    if counter >= 10 then
        emu.addKeys(C.GBA_KEY.A)
        counter = 0
    else
        emu.clearKeys(C.GBA_KEY.A)
    end
end)

Deep Dives

Detailed reference material is available in the references/ directory. Load these on demand for specific topics:

  • Classes & Registers — MemoryDomain class, TextBuffer class, GBA/GB register details
    • references/classes-registers.md
  • Networking — Socket API details and constants
    • references/networking.md
  • Examples — Complete working scripts (cheats, TAS tools, networked scripts)
    • references/examples.md

Best Practices

1. Always Clear Keys

-- Bad: Keys stay pressed
callbacks.add("frame", function()
    emu.addKeys(C.GBA_KEY.A)
end)

-- Good: Clear after use
callbacks.add("frame", function()
    emu.addKeys(C.GBA_KEY.A)
    emu.clearKeys(C.GBA_KEY.A)
end)

2. Use Frame Callback for Input

-- Input should be handled in frame callback
callbacks.add("frame", function()
    if btnp(6) then  -- UP pressed this frame
        -- Handle input
    end
end)

3. Watch for Crashes

callbacks.add("crashed", function()
    console.error("Emulation crashed!")
    -- Save state before exit
    emu.saveStateFile("crash.state")
end)

4. Reset State on Script Load

-- Clear any previous state when loading
emu.clearKeys(0xFFFF)
callbacks.remove(cbid)  -- Remove old callbacks

Common Issues

Address Not Found

-- Some games use different RAM locations
-- Use mGBA's cheat search or memory viewer to find correct addresses

Keys Not Working

-- Some games poll keys differently
-- Try using addKeys instead of setKeys
emu.addKeys(C.GBA_KEY.A)  -- OR with existing

Socket Connection Timeout

-- Socket connect is blocking!
-- Use connect with timeout or run in separate thread
-- For async, use callbacks and poll()

Complete API Reference

Core Methods Summary

| Method | Description | |--------|-------------| | loadFile(path) | Load ROM file | | getGameTitle() | Get ROM title | | getGameCode() | Get ROM code | | romSize() | Get ROM size | | platform() | Get platform (GBA=0, GB=1) | | checksum(type) | Get ROM checksum | | reset() | Reset emulation | | runFrame() | Run one frame | | step() | Run one instruction | | currentFrame() | Get frame number | | frameCycles() | Cycles per frame | | frequency() | Cycles per second | | screenshot(filename) | Save screenshot |

Memory Methods Summary

| Method | Description | |--------|-------------| | read8(addr) | Read 8-bit value | | read16(addr) | Read 16-bit value | | read32(addr) | Read 32-bit value | | readRange(addr, len) | Read byte range | | write8(addr, val) | Write 8-bit value | | write16(addr, val) | Write 16-bit value | | write32(addr, val) | Write 32-bit value | | readRegister(name) | Read CPU register | | writeRegister(name, val) | Write CPU register |

Input Methods Summary

| Method | Description | |--------|-------------| | setKeys(mask) | Set key bitmask | | addKeys(mask) | Add keys to current | | clearKeys(mask) | Remove keys from current | | addKey(key) | Add single key | | clearKey(key) | Clear single key | | getKey(key) | Get key state | | getKeys() | Get all keys as mask |

Save State Methods Summary

| Method | Description | |--------|-------------| | saveStateFile(path, flags) | Save to file | | loadStateFile(path, flags) | Load from file | | saveStateSlot(slot, flags) | Save to slot | | loadStateSlot(slot, flags) | Load from slot | | saveStateBuffer(flags) | Save to buffer | | loadStateBuffer(buf, flags) | Load from buffer | | autoloadSave() | Load associated save |

References

  • Official Documentation: https://mgba.io/docs/scripting.html
  • mGBA GitHub: https://github.com/mgba-emu/mgba
  • Forums: https://forums.mgba.io/
  • Discord: https://discord.gg/em2M2sG
  • Scripting API Reference: https://mgba.io/docs/scripting.html