ワンクリックで
mgba
Lua scripting for mGBA emulator - game automation, memory hacking, cheats, callbacks, and ROM manipulation
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Lua scripting for mGBA emulator - game automation, memory hacking, cheats, callbacks, and ROM manipulation
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Develop C++ plugins for Kate text editor using KTextEditor Framework, CMake with ECM, Qt threading, and KDE plugin architecture
Develop plugins, tools, and extensions for OpenCode AI coding agent with MCP, LSP integration, custom tools, and SDK usage
Django security - CSRF protection, authentication, sessions, login/logout, password handling, middleware, protected views
Python testing framework with powerful fixtures, parametrization, extensive plugin ecosystem, and support for async, Django, Flask testing
Python SQL toolkit and ORM with expressive query API, relationship mapping, async support, and Alembic migrations
Common Rust development pitfalls: frequent compiler errors, struct constructor patterns, test organization, and coverage enforcement for reliable codebases.
| name | mgba |
| description | Lua scripting for mGBA emulator - game automation, memory hacking, cheats, callbacks, and ROM manipulation |
| metadata | {"author":"mte90","version":"1.0.0","tags":["lua","emulator","gba","gameboy-advance","scripting","memory-hacking"]} |
Lua scripting for mGBA emulator.
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:
Tools → Scripting...
This opens a console where you can load and run Lua scripts.
-- 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.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()
-- 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
-- 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 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
-- 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
-- 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()
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
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
-- 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)
-- Write 8/16/32 bit values
emu.write8(address, value)
emu.write16(address, value)
emu.write32(address, value)
-- 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)
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)
-- Read register
value = emu.readRegister("r0")
emu.readRegister("pc")
emu.readRegister("sp")
emu.readRegister("lr")
-- Write register
emu.writeRegister("r0", 0x12345678)
emu.writeRegister("pc", 0x08000000)
-- General purpose: r0-r12
-- Special: sp (r13), lr (r14), pc (r15), cpsr
-- 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)
-- 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
-- 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)
-- Create socket
sock = socket.tcp()
-- Connect (blocking!)
err = sock:connect("192.168.1.100", 1234)
if err then
print("Error: " .. socket.ERRORS[err])
end
-- Bind for server
server = socket.tcp()
server:bind(nil, 8080)
server:listen(1)
client = server:accept()
-- Send data
sock:send("Hello, world!")
-- Receive data
data = sock:receive(1024)
-- Check if data available
if sock:hasdata() then
data = sock:receive(256)
end
-- Close
sock:close()
-- Add event callback
id = sock:add("received", function(data)
print("Received: " .. data)
end)
id = sock:add("error", function(err)
print("Error: " .. err)
end)
-- Poll manually
sock:poll()
-- Remove callback
sock:remove(id)
C.PLATFORM.NONE = -1
C.PLATFORM.GBA = 0
C.PLATFORM.GB = 1
C.SAVESTATE.SCREENSHOT = 1
C.SAVESTATE.SAVEDATA = 2
C.SAVESTATE.CHEATS = 4
C.SAVESTATE.RTC = 8
C.SAVESTATE.METADATA = 16
C.SAVESTATE.ALL = 31
C.SOCKERR.OK = 0
C.SOCKERR.AGAIN = 1
C.SOCKERR.ADDR_IN_USE = 2
C.SOCKERR.CONN_REFUSED = 3
C.SOCKERR.DENIED = 4
C.SOCKERR.FAILED = 5
C.SOCKERR.NETWORK_UNREACHABLE = 6
C.SOCKERR.NOT_FOUND = 7
C.SOCKERR.NO_DATA = 8
C.SOCKERR.OUT_OF_MEMORY = 9
C.SOCKERR.TIMEOUT = 10
C.SOCKERR.UNSUPPORTED = 11
-- Infinite health (example address)
local healthAddr = 0x02001234
callbacks.add("frame", function()
-- Always write max health
emu.write16(healthAddr, 999)
end)
-- Press A every 60 frames
callbacks.add("frame", function()
local frame = emu.currentFrame()
if frame % 60 == 0 then
emu.addKeys(C.GBA_KEY.A)
else
emu.clearKeys(C.GBA_KEY.A)
end
end)
-- Watch specific RAM addresses
callbacks.add("frame", function()
local hp = emu.read16(0x02001234)
local mp = emu.read16(0x02001236)
console.log(string.format("HP: %d, MP: %d", hp, mp))
end)
-- Auto-save every 5 seconds (300 frames at 60fps)
local frameCount = 0
local saveSlot = 0
callbacks.add("frame", function()
frameCount = frameCount + 1
if frameCount >= 300 then
emu.saveStateSlot(saveSlot)
console.log("Auto-saved to slot " .. saveSlot)
frameCount = 0
end
end)
-- Mash A button as fast as possible
callbacks.add("frame", function()
emu.addKeys(C.GBA_KEY.A)
emu.clearKeys(C.GBA_KEY.A)
end)
-- Advance RNG for shiny hunting
-- Example: GBA games often use LCG at specific address
local rngAddr = 0x02001234 -- Replace with actual address
callbacks.add("frame", function()
local rng = emu.read32(rngAddr)
-- Simple LCG: new = (old * 0x41C64E6D + 0x6073) & 0xFFFFFFFF
local newRng = (rng * 0x41C64E6D + 0x6073) & 0xFFFFFFFF
emu.write32(rngAddr, newRng)
end)
-- Capture screenshot every 1000 frames
callbacks.add("frame", function()
if emu.currentFrame() % 1000 == 0 then
local filename = string.format("screenshot_%04d.png", emu.currentFrame())
emu.screenshot(filename)
end
end)
-- 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)
-- Input should be handled in frame callback
callbacks.add("frame", function()
if btnp(6) then -- UP pressed this frame
-- Handle input
end
end)
callbacks.add("crashed", function()
console.error("Emulation crashed!")
-- Save state before exit
emu.saveStateFile("crash.state")
end)
-- Clear any previous state when loading
emu.clearKeys(0xFFFF)
callbacks.remove(cbid) -- Remove old callbacks
-- Some games use different RAM locations
-- Use mGBA's cheat search or memory viewer to find correct addresses
-- Some games poll keys differently
-- Try using addKeys instead of setKeys
emu.addKeys(C.GBA_KEY.A) -- OR with existing
-- Socket connect is blocking!
-- Use connect with timeout or run in separate thread
-- For async, use callbacks and poll()
Memory domains provide direct access to specific memory regions (ROM, RAM, etc.).
-- Get domain info
local rom = emu.memory["cart0"]
local baseAddr = rom:base() -- Base address
local boundAddr = rom:bound() -- End address (exclusive)
local size = rom:size() -- Size in bytes
local name = rom:name() -- Human-readable name
-- Read from domain
value8 = rom:read8(offset)
value16 = rom:read16(offset)
value32 = rom:read32(offset)
data = rom:readRange(offset, length)
-- Write to domain (RAM only, not ROM)
local wram = emu.memory["wram"]
wram:write8(offset, 0xFF)
wram:write16(offset, 0xFFFF)
wram:write32(offset, 0xFFFFFFFF)
-- Read ROM header
local rom = emu.memory["cart0"]
-- Nintendo logo starts at 0x04
local logo = rom:readRange(0x04, 156)
-- Game title at 0xA0 (12 bytes)
local title = rom:readRange(0xA0, 12)
console.log("Game: " .. title)
-- Game code at 0xAC (4 bytes)
local code = rom:readRange(0xAC, 4)
console.log("Code: " .. code)
-- Maker code at 0xB0 (2 bytes)
local maker = rom:readRange(0xB0, 2)
console.log("Maker: " .. maker)
Create custom text buffers for displaying information.
-- Create buffer
local buf = console.createBuffer("My Buffer")
-- Set size
buf:setSize(80, 25) -- 80 columns, 25 rows
-- Get dimensions
local cols = buf:cols()
local rows = buf:rows()
-- Print text
buf:print("Hello, World!")
-- Move cursor
buf:moveCursor(10, 5) -- x=10, y=5
-- Get cursor position
local x = buf:getX()
local y = buf:getY()
-- Advance cursor
buf:advance(5) -- Move 5 columns right
-- Clear buffer
buf:clear()
-- Set visible name
buf:setName("Stats Display")
local statsBuf = console.createBuffer("Game Stats")
statsBuf:setSize(40, 10)
statsBuf:setName("Live Stats")
callbacks.add("frame", function()
statsBuf:clear()
local hp = emu.read16(0x02001234)
local mp = emu.read16(0x02001236)
local gold = emu.read32(0x02001238)
statsBuf:moveCursor(0, 0)
statsBuf:print("=== GAME STATS ===\n")
statsBuf:print(string.format("HP: %5d\n", hp))
statsBuf:print(string.format("MP: %5d\n", mp))
statsBuf:print(string.format("Gold: %5d\n", gold))
statsBuf:print("==================")
end)
For Game Boy (DMG/CGB) emulation.
-- 8-bit registers
emu.readRegister("a") -- Accumulator
emu.readRegister("f") -- Flags
emu.readRegister("b")
emu.readRegister("c")
emu.readRegister("d")
emu.readRegister("e")
emu.readRegister("h")
emu.readRegister("l")
-- 16-bit register pairs
emu.readRegister("bc")
emu.readRegister("de")
emu.readRegister("hl")
emu.readRegister("af")
emu.readRegister("pc") -- Program counter
emu.readRegister("sp") -- Stack pointer
callbacks.add("frame", function()
-- Watch GB registers
local a = emu.readRegister("a")
local hl = emu.readRegister("hl")
local pc = emu.readRegister("pc")
console.log(string.format("A=%02X HL=%04X PC=%04X", a, hl, pc))
end)
-- Find all occurrences of a value in RAM
function searchMemory(value, size)
local wram = emu.memory["wram"]
local results = {}
for i = 0, wram:size() - size, size do
local v
if size == 1 then
v = wram:read8(i)
elseif size == 2 then
v = wram:read16(i)
else
v = wram:read32(i)
end
if v == value then
table.insert(results, i)
end
end
return results
end
-- Usage
local addresses = searchMemory(100, 2) -- Find 100 as 16-bit
for _, addr in ipairs(addresses) do
console.log(string.format("Found at 0x%08X", addr))
end
-- Simple cheat engine with multiple cheats
local cheats = {
{ name = "Infinite HP", addr = 0x02001234, value = 999, size = 2, enabled = true },
{ name = "Max Gold", addr = 0x02001238, value = 999999, size = 4, enabled = true },
{ name = "All Items", addr = 0x02002000, value = 0xFF, size = 1, enabled = false },
}
callbacks.add("frame", function()
for _, cheat in ipairs(cheats) do
if cheat.enabled then
if cheat.size == 1 then
emu.write8(cheat.addr, cheat.value)
elseif cheat.size == 2 then
emu.write16(cheat.addr, cheat.value)
else
emu.write32(cheat.addr, cheat.value)
end
end
end
end)
-- Toggle cheat
function toggleCheat(name)
for _, cheat in ipairs(cheats) do
if cheat.name == name then
cheat.enabled = not cheat.enabled
console.log(name .. ": " .. (cheat.enabled and "ON" or "OFF"))
end
end
end
-- Frame counter and IGT (In-Game Time) tracker
local startFrame = nil
local lastSplit = nil
local splits = {}
callbacks.add("start", function()
startFrame = emu.currentFrame()
splits = {}
end)
function split(name)
local currentFrame = emu.currentFrame()
local frameTime = currentFrame - (lastSplit or startFrame)
lastSplit = currentFrame
table.insert(splits, {
name = name,
frame = frameTime,
total = currentFrame - startFrame
})
local seconds = frameTime / 60
local totalSeconds = (currentFrame - startFrame) / 60
console.log(string.format("%s: %.2fs (Total: %.2fs)", name, seconds, totalSeconds))
end
function printSplits()
console.log("=== SPLITS ===")
for _, s in ipairs(splits) do
console.log(string.format("%s: %.2fs", s.name, s.frame / 60))
end
console.log("==============")
end
-- Record and playback inputs
local recording = {}
local isRecording = false
local isPlaying = false
local playbackFrame = 0
function startRecording()
recording = {}
isRecording = true
console.log("Recording started...")
end
function stopRecording()
isRecording = false
console.log("Recording stopped. " .. #recording .. " frames recorded.")
end
function startPlayback()
isPlaying = true
playbackFrame = 0
console.log("Playback started...")
end
function stopPlayback()
isPlaying = false
console.log("Playback stopped.")
end
callbacks.add("frame", function()
if isRecording then
table.insert(recording, emu.getKeys())
elseif isPlaying then
playbackFrame = playbackFrame + 1
if playbackFrame <= #recording then
emu.setKeys(recording[playbackFrame])
else
isPlaying = false
console.log("Playback complete.")
end
end
end)
-- Sync game state over network
local server = nil
local clients = {}
function startSyncServer(port)
server = socket.tcp()
server:bind(nil, port or 8080)
server:listen(5)
server:add("received", function()
local client = server:accept()
table.insert(clients, client)
console.log("Client connected!")
end)
console.log("Sync server started on port " .. (port or 8080))
end
function broadcastState()
if not server then return end
local state = {
frame = emu.currentFrame(),
keys = emu.getKeys(),
-- Add more state as needed
}
local data = string.format("%d,%d\n", state.frame, state.keys)
for i, client in ipairs(clients) do
local err = client:send(data)
if err then
table.remove(clients, i)
end
end
end
callbacks.add("frame", broadcastState)
| 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 |
| 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 |
| 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 |
| 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 |