MAME Lua Scripting: Inspecting Emulated Hardware Without Editing the Driver
Use MAME's Lua console, autoboot scripts, and plugins for repeatable inspection while accounting for versioned APIs and mutable emulation state.
MAME’s Lua interface exposes selected emulator state without requiring a driver change or a custom MAME build. Scripts can inspect the current machine, enumerate devices, read memory and registers, react to frame events, and draw overlays. The interface is valuable for debugging and preservation research, but MAME explicitly does not declare its Lua API stable. Scripts should identify the MAME version they support and fail visibly when an expected object or method is absent.
The documentation describes three entry points: an interactive Lua console, a script loaded at startup with -autoboot_script, and Lua plugins. Choose the smallest that fits the work. Use the console for exploration, an autoboot script for repeatable one-session experiments, and a plugin when the tool needs to be reusable through MAME’s plugin system.
Start with a read-only inspection
Enable the console for a system and verify which build is running:
mame -console -window YOUR_SYSTEM
At the MAME prompt, inspect the version and device tree:
print(emu.app_name() .. " " .. emu.app_version())
for tag, device in pairs(manager.machine.devices) do
print(tag)
end
manager.machine represents the current running emulation session. A system may have multiple CPU, screen, sound, and other devices, and the tag names are driver-specific. Discover the actual device tree before writing a script that expects a tag such as :maincpu; do not assume that every machine uses the same topology.
To inspect a CPU’s address spaces without changing emulated state:
local cpu = manager.machine.devices[":maincpu"]
if cpu == nil then
error("expected CPU device is not present")
end
for name, space in pairs(cpu.spaces) do
print(name)
end
The Lua reference exposes methods for memory reads and writes and for reading or setting register state. Use read operations first when the goal is observation. A write changes the emulated machine and can invalidate a reproduction, alter a save state, or make a preservation test non-deterministic.
Automate a startup experiment
Save a script such as inspect.lua, then pass it to MAME:
mame YOUR_SYSTEM \
-autoboot_delay 2 \
-autoboot_script "$PWD/inspect.lua"
The delay is a startup timing control, not a guarantee that every device has reached the state your script expects. Check for required devices and handle absent objects explicitly. Keep scripts read-only when collecting evidence, and record the MAME build, system short name, ROM or software-list set, command line, script revision, and configuration alongside the output.
For screen overlays, MAME’s documentation demonstrates a frame callback such as emu.register_frame_done and methods on a screen device. An overlay must be redrawn on the relevant updates; drawing once during startup does not make it persistent. Coordinates, screen tags, and device capabilities vary by driver, so validate them against the specific emulated system.
Keep version drift observable
MAME documents runtime version and API introspection because the Lua surface can change. Start a script with a version banner, check that required methods exist, and write a clear diagnostic before stopping if they do not. Avoid depending on undocumented internals simply because they happen to be visible through a particular build.
If a script manipulates memory, input, or registers, document the exact purpose and restoration path. Keep an untouched baseline save state or test image, and compare the state before and after the experiment. A Lua script is capable of changing the emulation; its presence does not make an experiment observational.
Reproducibility and safe use
An experiment is reproducible only when the environment is specified. Record the exact MAME version, machine short name, ROM set hashes, device options, Lua script hash, and relevant input configuration. Do not distribute copyrighted ROM data with the script; the Lua tool and the media it inspects are separate artifacts with different rights and provenance.
Use the interactive console to discover APIs, then convert verified steps into a minimal script and rerun it from a clean launch. Check that the result is stable across repeated runs and that the script does not silently change emulated state. For long-term preservation work, keep the script alongside a README describing the supported MAME version and expected output rather than treating an old console transcript as an enduring API contract.
Inspect ROM regions without changing machine state
The Lua memory API exposes memory regions separately from CPU address spaces. A region is often the right object when you want to verify what the loader populated; a CPU address space is the right object when you want to inspect what a processor can read at a mapped address. They are not interchangeable. A banked CPU read can return a different backing region depending on the selected bank, and a region offset is not automatically a CPU address.
This read-only inventory is useful as a starting point for a preservation or driver investigation:
for tag, region in pairs(manager.machine.memory.regions) do
print(string.format("region=%s bytes=%d width=%d endian=%s",
tag, region.size, region.bitwidth, region.endianness))
end
The API documents region.size in bytes and provides region:read(offset, length) for reading a bounded byte range. Keep the requested length small and confirm that it is within the region before comparing data. Do not print or publish copyrighted ROM contents; offsets, byte counts, hashes, and a privately stored test report are normally enough. A Lua read can validate that an in-memory region differs from expectation, but it cannot establish that the source image was legally obtained or historically authentic.
If you need processor-visible data, first enumerate the target CPU’s spaces and check the space’s address mask, width, and endianness. A read through space:read_u8(address) is a bus-level observation, while a region read bypasses the CPU map. If the target driver uses a bank, record the active bank entry along with the address. This distinction often turns a confusing “ROM bytes are right but CPU sees the wrong byte” report into a concrete address-map or bank-selection issue.
Instrument callbacks with bounded work
Frame callbacks are useful for counting events or sampling state over time, but they execute inside the emulator’s event flow. Keep them deterministic and cheap: increment counters, store a small number of values, and print or serialize a summary at a lower rate. Logging every memory access or vector segment can overwhelm the frontend, alter perceived performance, or produce so much output that the useful sequence is lost.
For a memory experiment, a read tap or write tap can observe a bounded address range. MAME’s reference notes that pass-through callbacks are not coroutines and can modify data if they return an integer. Begin with an observational callback that returns nothing, use the narrowest possible range, and remove the handler when the test ends. A callback that returns a modified value is an intervention; label it as such and do not compare its output with an unmodified gameplay trace as if both were observations of the same machine.
Similarly, an input field override changes the emulated system. Keep the saved script explicit about whether it is read-only or mutating. A small harness should print its MAME version, short system name, device tags it found, and any missing expected tag. If the script cannot find an object, stop with a clear diagnostic rather than silently producing an empty report that looks like a successful measurement.
Make experiments repeatable across MAME releases
The Lua API is documented as unstable, so a script should treat an API change as a compatibility event. Record the exact MAME version and source revision, not just “current MAME.” Before collecting data, check required methods and values exist. A feature can be present in a newer documentation page but absent from the binary the user launched, particularly when multiple packaged builds are installed.
Use a fresh launch and a known machine configuration for each run. Avoid depending on state left behind by a previous script, plugin, save state, or input override. Store output in a separate experiment directory with the script hash and command line. If a result depends on a particular frame, record the start condition and frame number; two attract-mode sessions can be at different animation phases and produce different memory or rendering data even when the code is unchanged.
When publishing the diagnostic, include the minimum facts another maintainer needs to reproduce it: system short name, MAME version, content set identifier or private hash, relevant options, script revision, expected behavior, observed result, and whether the script altered machine state. Do not include ROM data, encryption keys, or other material that the rights holder or distributor has not authorized. This method keeps a useful technical trace while separating code, configuration, and copyrighted media.
Related:
- MAME Input Recordings: Deterministic Playback, Desyncs, and Reproducible Evidence
- Preservation-Grade Game Images: Dumps, Hashes, DATs, and Disc Formats Explained
Sources: