Skip to content

Lua Scripting Guide

eadmaster edited this page Mar 8, 2026 · 67 revisions

How to load a script

  • Method 1 (Content-Specific): Save the script in the same folder as your ROM with a matching filename. E.g.: Momotarou Katsugeki (Japan).pce -> Momotarou Katsugeki (Japan).lua. The script will autostart when the content is loaded with any compatible core.
  • Method 2 (Global): Save your script as $HOME/.config/retroarch/system/global.lua. This script will run automatically with any content and core.

Tip

You can combine multiple scripts with dofile()/loadfile()/require()

Lifecycle

The script keeps running and it is terminated with the associated content:

Content status Script status
Running Running
Paused Paused
Frontend menu opened Running, but not drawing shapes
Reset Running, not restarted
Closed Terminated

General structure

-- Global vars and functions definitions
local foo = "hello"
local bar = 1

function foo_bar()
   -- ...
end

-- Main loop, executed once per frame
-- without this loop the script will terminate immediately!
while true do
   -- ...
   emu.frameadvance();  -- Required
end

Functions Reference

The API is mostly compatible with Bizhawk. Use this page as reference.

Coverage:

Status Module Coverage Notes
🟑 Partial bizstring Implemented contains(), endswith(), startswith(), tolower(), toupper(), trim(), encode(), decode(). The last two requires libiconv, which could be missing on some platforms.
🟑 Partial console Missing only clear(), getluafunctionslist()
🟑 Partial emu Implemented only frameadvance(), framecount(), getsystemid()
🟑 Partial gameinfo Implemented only getromhash() and getromname(). Missing all database functions.
🟒 Full input All functions implemented.
🟒 Full memory All functions implemented.
🟒 Full mainmemory Alias to memory
🟑 Partial client Implemented ispaused(), isturbo(), screenheight(), screenwidth(), bufferheight(),bufferwidth(), getversion(), pause(), unpause(), togglepause(), exit(), reboot_core(), closerom(), screenshot(), sleep(), getconfig(), transformPoint(), get_lua_engine()
🟑 Partial comm Implemented only httpget(), httppost(), httpput(). HTTP functions are non-blocking; no response body access.
🟑 Partial gui Implemented only addmessage(), drawString(), drawPixelText(), drawRectangle(), drawBox(), clearGraphics()
🟑 Partial joypad Implemented only joypad.get()
🟒 Full savestate
πŸ”΄ None bit Not implemented (Standard Lua 5.3 bitwise operators available).
πŸ”΄ None event
πŸ”΄ None forms
πŸ”΄ None genesis
πŸ”΄ None LuaCanvas
πŸ”΄ None memorysavestate
πŸ”΄ None movie
πŸ”΄ None nds
πŸ”΄ None nes
πŸ”΄ None snes
πŸ”΄ None SQL
πŸ”΄ None tastudio
πŸ”΄ None userdata

Notable differences:

  • gameinfo.getromhash() returns the content CRC32, while Bizhawk uses SHA1 for ROM-based systems and MD5 for CD-based systems.
  • gui.draw...() functions do not support the surfacename arg. They draw in a separate buffer with an higher resolution than the emulated core.
  • client.getconfig() returns a table with different setting names
  • comm_http...() functions send non-blocking requests, and there is no way to get the response body.
  • client.screenshot() does not support the path arg

New functions not in Bizhawk:

  • gameinfo.getrompath() = returns the full path of the currently loaded rom (can be a relative path)
  • rom.readbyte(int address) = Get an unsigned byte from the actual ROM file at the given address.
  • emu.getscreenpixel(int x, int y, bool getemuscreen) = Returns the separate RGB components of the given screen pixel, and the palette.
  • gui.drawStringO(...), gui.pixelTextO(...). gui.drawRectangleO(...) = Draws outside the emulator screen space
  • memory.dump(filename, [string domain = nil], [ long start_address = 0 ], [long stop_address = memsize]) = Dump a memory region into a file.

Tip

Check the source code as additional reference of missing functions/features.

Memory domains

Unlike Bizhawk, all cores share the same memory domain names: "Battery RAM", "RTC", "RAM", "VRAM", "ROM".

However, availability varies by core. Use this check to prevent errors:

-- check if the VRAM memory domain is available
if(memory.usememorydomain("VRAM")) then
    -- safe to access VRAM here
    print(memory.readbyte(0x0, "VRAM"))
else
    print("err: VRAM domain NOT available")
end

-- this will fall back to default domain if VRAM is not available
print(memory.readbyte(0x0, "VRAM"))

To list all available domains for the current core:

console.write(memory.getmemorydomainlist())

-- alt. iterative way
for i, name in ipairs(memory.getmemorydomainlist()) do
    print(i, name, "  size=" .. memory.getmemorydomainsize(name))
end

Sandbox mode

The full Lua 5.3 stdlib is also available.

Some stdlib functions can be disabled via the user setting lua_scripts_sandboxed, to prevent the execution of malicious scripts:

  • os.execute()
  • os.remove()
  • os.rename()
  • io.open() is restricted to read-only access files in the same folder of the current content.

Debugging

To debug a script, run RetroArch from a terminal to view stdout and stderr messages.

Since the API is mostly compatible, you can also load your scripts in BizHawk and use its Lua Interactive Console as a playground for testing and practice.

Examples

Clone this wiki locally