2015-03-16 11:35:38 +01:00
|
|
|
# Scripting MAME via LUA
|
|
|
|
|
|
|
|
## Introduction
|
|
|
|
|
|
|
|
It is now possible to externally drive MAME via LUA scripts.
|
|
|
|
This feature initially appeared in version 0.148, when a minimal `luaengine`
|
|
|
|
was implemented. Nowadays, the LUA interface is rich enough
|
|
|
|
to let you inspect and manipulate devices state, access CPU
|
|
|
|
registers, read and write memory, and draw a custom HUD on screen.
|
|
|
|
|
|
|
|
Internally, MAME makes extensive use of `luabridge` to implement
|
|
|
|
this feature: the idea is to transparently expose as many of
|
|
|
|
the useful internals as possible.
|
|
|
|
|
|
|
|
Finally, a warning: LUA API is not yet declared stable and may
|
|
|
|
suddenly change without prior notice.
|
|
|
|
However, we expose methods to let you know at runtime which API
|
|
|
|
version you are running against, and you can introspect most of the
|
|
|
|
objects at runtime.
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
The API is not yet complete, but this is a partial list of capabilities
|
|
|
|
currently available to LUA scripts:
|
|
|
|
|
|
|
|
* machine metadata (app version, current rom, rom details)
|
|
|
|
* machine control (starting, pausing, resetting, stopping)
|
|
|
|
* machine hooks (on frame painting and on user events)
|
2016-02-14 23:58:51 +01:00
|
|
|
* machine options (hard reset required for options to take affect)
|
2015-03-16 11:35:38 +01:00
|
|
|
* devices introspection (device tree listing, memory and register enumeration)
|
|
|
|
* screens introspection (screens listing, screen details, frames counting)
|
2016-02-14 23:58:51 +01:00
|
|
|
* screen snaps and HUD drawing (text, lines, boxes on multiple screens)
|
2015-03-16 11:35:38 +01:00
|
|
|
* memory read/write (8/16/32/64 bits, signed and unsigned)
|
|
|
|
* registers and states control (states enumeration, get and set)
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
MAME supports external scripting via LUA (>= 5.3) scripts, either
|
|
|
|
written on the interactive console or loaded as a file.
|
|
|
|
To reach the console, just run MAME with `-console`; you will be
|
|
|
|
greeted by a naked `>` prompt where you can input your script.
|
|
|
|
|
|
|
|
To load a whole script at once, store it in a plaintext file and
|
|
|
|
pass it via the `-autoboot_script`. Please note that script
|
|
|
|
loading may be delayed (few seconds by default), but you can
|
|
|
|
override the default with the `-autoboot_delay` argument.
|
|
|
|
|
|
|
|
To control the execution of your code, you can use a loop-based or
|
|
|
|
an event-based approach. The former is not encouraged as it is
|
|
|
|
resource-intensive and makes control flow unnecessarily complex.
|
|
|
|
Instead, we suggest to register custom hooks to be invoked on specific
|
|
|
|
events (eg. at each frame rendering).
|
|
|
|
|
|
|
|
## Walktrough
|
|
|
|
|
|
|
|
Let's first run MAME in a terminal to reach the LUA console:
|
|
|
|
```
|
|
|
|
$ mame -console YOUR_ROM
|
|
|
|
M.A.M.E. v0.158 (Feb 5 2015) - Multiple Arcade Machine Emulator
|
|
|
|
Copyright Nicola Salmoria and the MAME team
|
|
|
|
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
|
|
|
|
|
|
|
|
>
|
|
|
|
```
|
|
|
|
|
|
|
|
At this point, your game is probably running in demo mode, let's pause it:
|
|
|
|
```
|
|
|
|
> emu.pause()
|
|
|
|
>
|
|
|
|
```
|
|
|
|
Even without textual feedback on the console, you'll notice the game is now paused.
|
|
|
|
In general, commands are quiet and only print back error messages.
|
|
|
|
|
|
|
|
You can check at runtime which version of MAME you are running, with:
|
|
|
|
```
|
|
|
|
> print(emu.app_name() .. " " .. emu.app_version())
|
|
|
|
mame 0.158
|
|
|
|
```
|
|
|
|
|
|
|
|
We now start exploring screen related methods. First, let's enumerate available screens:
|
|
|
|
```
|
|
|
|
> for i,v in pairs(manager:machine().screens) do print(i) end
|
|
|
|
:screen
|
|
|
|
```
|
|
|
|
|
|
|
|
`manager:machine()` is the root object of your currently running machine:
|
|
|
|
we will be using this often. `screens` is a table with all available screens;
|
|
|
|
most machines only have one main screen.
|
|
|
|
In our case, the main and only screen is tagged as `:screen`, and we can further
|
|
|
|
inspect it:
|
|
|
|
```
|
|
|
|
> -- let's define a shorthand for the main screen
|
|
|
|
> s = manager:machine().screens[":screen"]
|
|
|
|
> print(s:width() .. "x" .. s:height())
|
|
|
|
320x224
|
|
|
|
```
|
|
|
|
|
|
|
|
We have several methods to draw on the screen a HUD composed of lines, boxes and text:
|
|
|
|
```
|
|
|
|
> -- we define a HUD-drawing function, and then call it
|
|
|
|
> function draw_hud()
|
|
|
|
>> s:draw_text(40, 40, "foo"); -- (x0, y0, msg)
|
|
|
|
>> s:draw_box(20, 20, 80, 80, 0, 0xff00ffff); -- (x0, y0, x1, y1, fill-color, line-color)
|
|
|
|
>> s:draw_line(20, 20, 80, 80, 0xff00ffff); -- (x0, y0, x1, y1, line-color)
|
|
|
|
>> end
|
|
|
|
> draw_hud();
|
|
|
|
```
|
|
|
|
|
|
|
|
This will draw some useless art on the screen. However, when unpausing the game, your HUD
|
|
|
|
needs to be refreshed otherwise it will just disappear. In order to do this, you have to register
|
|
|
|
your hook to be called on every frame repaint:
|
|
|
|
```
|
|
|
|
> emu.sethook(draw_hud, "frame")
|
|
|
|
```
|
|
|
|
|
2015-04-20 17:13:38 +02:00
|
|
|
All colors are expected in ARGB format (32b unsigned), while screen origin (0,0)
|
|
|
|
normally corresponds to the top-left corner.
|
|
|
|
|
2015-03-16 11:35:38 +01:00
|
|
|
Similarly to screens, you can inspect all the devices attached to a
|
|
|
|
machine:
|
|
|
|
```
|
|
|
|
> for k,v in pairs(manager:machine().devices) do print(k) end
|
|
|
|
:audiocpu
|
|
|
|
:maincpu
|
|
|
|
:saveram
|
|
|
|
:screen
|
|
|
|
:palette
|
|
|
|
[...]
|
|
|
|
```
|
|
|
|
|
|
|
|
On some of them, you can also inspect and manipulate memory and state:
|
|
|
|
```
|
|
|
|
> cpu = manager:machine().devices[":maincpu"]
|
|
|
|
> -- enumerate, read and write state registers
|
|
|
|
> for k,v in pairs(cpu.state) do print(k) end
|
|
|
|
D5
|
|
|
|
SP
|
|
|
|
A4
|
|
|
|
A3
|
|
|
|
D0
|
|
|
|
PC
|
|
|
|
[...]
|
|
|
|
> print(cpu.state["D0"].value)
|
|
|
|
303
|
|
|
|
> cpu.state["D0"].value = 255
|
|
|
|
> print(cpu.state["D0"].value)
|
|
|
|
255
|
|
|
|
```
|
|
|
|
|
|
|
|
```
|
|
|
|
> -- inspect memory
|
|
|
|
> for k,v in pairs(cpu.spaces) do print(k) end
|
|
|
|
program
|
|
|
|
> mem = cpu.spaces["program"]
|
|
|
|
> print(mem:read_i8(0xC000))
|
|
|
|
41
|
|
|
|
```
|
|
|
|
|
2016-02-16 06:51:18 +01:00
|
|
|
manager:options()
|
|
|
|
manager:machine():options()
|
|
|
|
manager:machine():ui():options()
|
2016-02-14 23:58:51 +01:00
|
|
|
```
|
2016-02-16 06:51:18 +01:00
|
|
|
> opts = manager:machine():options()
|
|
|
|
> for k, entry in pairs(opts.entries) do print(string.format("%10s: %s\n%11s %s", k, entry:value(), "", entry:description())) end
|
2016-02-14 23:58:51 +01:00
|
|
|
diff_directory: diff
|
|
|
|
directory to save hard drive image differeVnce files
|
|
|
|
joystick_contradictory: false
|
|
|
|
enable contradictory direction digital joystick input at the same time
|
|
|
|
scalemode: none
|
|
|
|
Scale mode: none, hwblit, hwbest, yv12, yuy2, yv12x2, yuy2x2 (-video soft only)
|
|
|
|
oslog: false
|
|
|
|
output error.log data to the system debugger
|
|
|
|
[...]
|
2016-02-16 06:51:18 +01:00
|
|
|
> print(opts.entries["sleep"]:value())
|
2016-02-14 23:58:51 +01:00
|
|
|
true
|
2016-02-16 06:51:18 +01:00
|
|
|
> print(opts.entries["sleep"]:value("invalid"))
|
|
|
|
Illegal boolean value for sleep: "invalid"; reverting to 1
|
|
|
|
true
|
|
|
|
> print(opts.entries["sleep"]:value(false))
|
2016-02-14 23:58:51 +01:00
|
|
|
false
|
|
|
|
```
|
2016-02-16 06:51:18 +01:00
|
|
|
|
|
|
|
individual screen snapshots
|
|
|
|
```
|
|
|
|
> local screen = manager:machine().screens[":screen"]
|
|
|
|
> screen:snapshot()
|
|
|
|
saved snap/gridlee/0000.png
|
|
|
|
> screen:snapshot('%g.png')
|
|
|
|
saved snap/gridlee.png
|
|
|
|
```
|