FS-UAE began adding an experimental Lua scripting layer around 2013, although it is incomplete, disabled by default, and almost completely undocumented. Fortunately, it’s possible to re-enable it and use it to write scripts to aid in the real-time analyis of Amiga software… or just to cheat at games.
Building FS-UAE with Lua
You will need a small amount of technical skill here, but nothing unusual for someone who is already analyzing Amiga game code.
Grab the source code of the latest stable release of FS-UAE from GitHub - I tested this as working on version 3.1.66. Next, grab fs-uae-lua.diff, a patch based on cnvogelg’s pull request from 2015 which adds a Lua shell. Put it in the directory with the source code and apply it using git:
git apply fs-uae-lua.diff
Next, follow the FS-UAE documentation on compiling for your platform, using the
--enable-lua
directive. It should go something like this:
./bootstrap
./configure --enable-lua
./make
If all goes well with no errors, this will produce an executable fs-uae
.
Using the Lua shell
In your fs-uae configuration, set this option:
lua_shell = 1
Run the patched fs-uae
as normal. Open a telnet window and connect to
localhost in port 6800:
telnet 127.0.0.1 6800
This places you in a sort of Lua interpreter window. If you have now discovered that you need to learn Lua, please consult the Lua Reference Manual.
Loading scripts at startup
If FS-UAE was compiled with --enable-lua
, it will load an run a file named
default.lua
from the current working directory at startup.
It appears that the startup script and the shell are separate; i.e. functions
defined in default.lua
aren’t available in the shell, and the lua require()
function is also missing. However, you can still define scripts at startup which
trigger callbacks.
FS-UAE Lua API documentation
The following function names assume you have installed the Lua shell patch.
Functions
- fsemu.load_shader()
- fsemu.log()
- fsemu.set_frame_position_and_size()
- fsemu.set_scale()
- fsemu.set_shader()
- fsuae.cdrom.get_file(filename)
- fsuae.cdrom.get_num_drives()
- fsuae.cdrom.set_file(filename)
- fsuae.floppy.get_file(filename)
- fsuae.floppy.get_num_drives()
- fsuae.floppy.set_file(filename)
- fsuae.get_input_event()
- fsuae.get_rand_checksum()
- fsuae.get_save_state_number()
- fsuae.get_state_checksum()
- fsuae.send_input_event()
- fsuae.set_input_event()
- uae.exe()
- UAE remote CLI.
- uae.log()
- uae.peek_u16(addr)
- Read 16-bit value from memory location without any side-effects.
- uae.read_config()
- uae.read_u16(addr)
- Read 16-bit value from memory location.
- uae.read_u8(addr)
- Read 8-bit value from memory location.
- uae.write_config()
- uae.write_u16(addr, value)
- Write 16-bit value to a memory location.
- uae.write_u8(addr, value)
- Write 8-bit value to a memory location.
- quit()
- Quit the Lua shell.
Tables
- uae.custom
- A table of the custom chip registers and their memory locations. For example:
print(string.format(“$%x”,uae.peek_u16(uae.custom[‘COLOR01’])))
Callbacks
Define a function by this name and and it will be called whenever the relevant event triggers.
- on_fs_emu_render_frame()
- on_fs_uae_input_event
- on_fs_uae_load_state()
on_fs_uae_save_state()
on_fs_uae_load_state_done()
on_fs_uae_save_state_done() - Called whenever you load or save a save state.
- on_fs_uae_read_input
- Only triggers for the first input handler run per frame.
- on_uae_config_changed()
- on_uae_vsync()
- Called on vsync; i.e. once per frame.
Uses for the scripting interface
One use is to show data in memory or custom registers. Here is a one-liner which will dump the current palette:
for n=0,31 do print(string.format("COLOR%02d: $%03x",n,uae.peek_u16(uae.custom['COLOR00']+n*2))) end
Unlike the real Lua interpreter, it doesn’t allow you to spread over multiple
lines. You will have to use semicolons to divide statements. It’s also less
fully featured than actual Lua; e.g. you can’t require()
external modules, and
the bitwise shift operators <<
and >>
are missing.
Another use of the Lua interface is to track variables which aren’t normally
displayed on screen. For example, put this in default.lua
to show whenever the
first character gains XP to the Adventurer class in Knightmare:
xp = 0
function on_uae_vsync()
t = uae.peek_u16(0x18e1a)
if not(t==xp) then
print(string.format("Ratt gained %d XP.",t-xp))
xp = t
end
end
A script like this prints to stdout (i.e. the terminal you launched fs-uae from), but you could also display data by writing it to some on-screen value, such as a character name or player 2 score readout.
The following useful snippet can be pasted into the telnet to report ongoing
changes within a certain memory area, as defined by start
and finish
. It
also defines some handy shortcut functions: W(addr, value)
to write a two-byte
value, and A(addr,value)
to add a number to a value.
start = 0x18dd0 ; finish = 0x18fa2
d = {} ; for n=start,finish,2 do d[n]=0 end
function hex(n) return (string.format("$%04x",n)) end
function W(addr,value) uae.write_u16(addr,value) end
function A(addr,value) uae.write_u16(addr,uae.peek_u16(addr)+value) end
function u() for addr,v in pairs(d) do c = uae.peek_u16(addr) ; if not (v==c) then print(hex(addr), hex(v), hex(c), pl(c-v)) ; d[addr] = c ; end end end
u()
function on_uae_vsync() u() end
Using the terminal interface, there’s a limit to how long you can make one line, and you can’t enter a function over multiple lines. Another limitation is that you can only read memory and custom registers, not data or address registers. For more specific abilities, including search, reading and setting registers, and setting watchpoints, use the FS-UAE debugger.
See also cnvogelg’s tools, cnvogelg’s fsuaetools, and sonnenscheinchen’s emu-scripts for some tools that mainly use the interface to create a GUI to switch disks. These tools take the versatile approach of writing Python scripts which interface with the Lua shell, which opens up a lot of options.
« Back to index page