This is the API from DeHackEd's version of Lua in ZSnes. At the time of writing, FCUE's Lua was based on this API.
Basics
Your code will be run alongside the emulator's main loop. You code should
probably look roughly like this:
-- initialization goes here
while condition do
-- Code executed once per frame
snes9x.frameadvance()
end
-- Cleanup goes here
When Lua execution starts, the emulator will be automatically unpaused if it
is currently paused. If so, it will automatically be paused when the script
exits, voluntarily or otherwise. This allows you to have a script execute
some work on your behalf and then when it exits the emulator will be paused,
ready for the player to continue use.
Base library
Handy little things that are not put into a class. Mostly binary operations right now.
int AND(int arg1, int arg2, ..., int argn)
Since Lua lacks binary operators and since binary will come up with memory manipulation,
I offer this function. Output is the binary AND of all its parameters together.
Minimum 1 argument, all integers.
At a binary level, the AND of two binary bits is 1 if both inputs are 1, and the output
is 0 in any other case. Commonly used to test if a bit is set by ANDing with a number
with only the desired position set to 1.
int OR(int arg1, int arg2, ..., int argn)
The OR of two bits is 1 if either of the inputs is 1, and 0 if both inputs are 0.
Typically used to force a single bit to 1, regardless of its current state.
int XOR(int arg1, int arg2, ..., int argn)
XOR flips bits. An even number of 1s yields a zero and an odd number of 1s yields a 1.
Commonly used to toggle a bit by XORing.
int BIT(int which)
Returns a number with only the given bit set. which is in the range from 0 to 15 since the
SNES is a 16 bit system. BIT(15) == 32768
... Actually this system will accept a range of 0 to 30, but none of the memory access functions will
accept it, so you're on your own for those. 31 is not allowed for now due to signedness risking wreaking havoc.
Basic master emulator control.
snes9x.speedmode(string mode)
Selects the speed mode snes9x should run at while Lua is in control of frame advance. It must be set to one of the following:
In modes other than normal, pause will have no effect.
snes9x.frameadvance()
Snes9x executes one frame. This function pauses until the execution finishes. General system slowdown
when running at normal speed (ie. sleeping for 1/60 seconds) also occurs here when not in high speed mode.
Warning: Due to the way the code is written, the times this function may be called is restricted. Norably,
it must not be called within a coroutine or under a [x]pcall(). You can use coroutines for
your own purposes, but they must not call this function themselves. Furthermore, this function cannot be called from any
"registered" callback function. An error will occur if you do.
snes9x.message(string msg)
Displays the indicated string on the user's screen. snes9x.speedmode("normal") is probably the only way this is of any use,
lest the message not be displayed at all
snes9x.pause()
v0.05+ only
Pauses the emulator. This function blocks until the user unpauses.
This function is allowed to be called from outside a frame boundary (ie. when it is not allowed to call
snes9x.frameadvance). In this case, the function does not wait for the pause because you can't pause
midway through a frame. Your code will continue to execute and the emulator will be paused at the end
of the current frame. If you are at a frame boundary, this function acts a lot like snes9x.frameadvance()
plus the whole pause thing.
It might be smart to reset the speed mode to "normal" if it is not already so.
snes9x.wait()
v0.06+ only
Skips emulation of the next frame. If your script needs to wait for something to happen before proceeding (eg.
input from another application) then you should call this. Otherwise the GUI might jam up and your
application will not appear to be responding and need termination. It is expected that this function
will pause the script for 1/60 of a second without actually running the emulator itself, though it tends to be
OS-dependent right now.
If you're not sufficiently confused yet, think of this as pausing for one frame.
If you need to do a large amount of calculations -- so much that you risk setting off the rampant script
warning, just call this function every once in a while.
Might want to avoid using this if you don't need to. If the emulator is running at normal speed, paused
and the user presses frame-advance, they might be confused when nothing happens.
Memory access and manipulation.
int memory.readbyte(int address)
int memory.readword(int address)
Reads a number of bits (8 or 16) and returns the memory contents. The address must be a fully qualified
memory address. The RAM range is 0x7e0000 through 0x7fffff, but you may use any memory address, including
the ROM data itself.
int memory.readbytesigned(int address)
int memory.readwordsigned(int address)
v0.04+ only
Same as its counterparts, except numbers will be treated as signed. Numbers larger than 127 for bytes and
32767 for words will be translated into the correct negative numbers. For reference, an alternate formula
is to subtract 256 for bytes and 65536 for words from any number equal to or larger than half that number.
For example, a byte at 250 becomes 250-256 = -6.
memory.writebyte(int address, int value)
memory.writebyte(int address, int value)
Writes a number of bits (8 or 16) to the indicated memory address. The address MUST be in the range of
0x7e0000 through 0x7fffff.
memory.register(int address, function func)
When the given memory address is written to (range must be 0x7e0000 to 0x7fffff), the given function will be
called. The execution of the CPU will be paused mid-frame to call the given function.
Only one function can be registered with a memory address. 16 bit writes will only trigger the lower address
listener. There is no distinction between 8 and 16 bit writes. func may be nil in order to
delete a function from listening.
Code called may not call snes9x.frameadvance() or any savestate save/load functions, and any button
manipulation results are undefined. Those actions are only meaningful at frame boundaries.
Access to the gamepads. Note that Lua makes some joysticks do strange things.
Setting joypad inputs causes the user input for that frame to be ignored, but
only for that one frame.
Joypads are numbered 1 to 5.
Joypad buttons are selected by use of a table with special keys. The table
has keys start, select, up, down, left, right, A, B, X, Y, L, R. Note the
case is sensetive. Buttons that are pressed are set to a non-nil value
(use of the integer 1 is just a convention). Note that "false" is valid,
but discouraged as testing for logical true will fail.
Currently reading input from a movie file is not possible, but
a movie will record button presses from Lua.
table joypad.read(int which)
Returns a table indicating which buttons are pressed by the user.
This is probably the only way to get input to the script by the user.
This is always user input, even if the joypads have been set by joypad.set.
joypad.set(int which, table buttons)
Sets the buttons to be pressed. These choices will be made in place of
what the user is pressing during the next frame advance; they are then
discarded, so this must be called once every frame, even if you just want to
keep the same buttons pressed for several frames.
Control over the savestate process. Savestate objects are opaque structures
that represent non-player accessible states (except for the functions that
return "official" savesates). Such an object is garbage collectable, in which
case the savestate is no longer usable. Recycling of existing savestate objects
is highly recommended for disk space concerns lest the garbage collector
grow lazy.
Each object is basically a savestate file. Anonymous savestates are saved to
your temp directory.
object savestate.create(int userslot=nil)
Creates a savestate object for use. If the userslot argument
is given, the state will be accessible via the associated
F-key (F1 through F12 are 1 through 12). If not specified or
nil, an anonymous savestate is created that only Lua can access.
Each call to savestate.create() (without parameters) returns
a unique savestate. As such, if you discard the contents of a variable
containing an important savestate, you've just shot yourself in the foot.
An object may be used freely once created, saved over and loaded whenever.
It is an error to load an anonymous (non-player accessbile) state that
has not been saved yet, since it's empty.
Each savestate uses about 120 KB of disk space and the random filename generator
has its limits with regards to how many filenames it can generate. Don't go too
overboard. If you need more than 1000 savestates, maybe you should rethink
your tehcnique. (The actual windows limit is about 32768, Linux is higher).
savestate.save(object state)
Saves the current state to the given object. Causes an error if something goes horribly
wrong, or if called within any "registered" callback function.
savestate.load(object state)
Loads the given state. Throws an error for all the same bad things that might happen.
function savestate.registersave(function save)
v0.06+ only
Registers a function to be called on a savestate event. This includes both calls to
savestate.save() and users pressing buttons. The function will be called without
parameters.
The function called is permitted to return any number of string and number values.
Lua lets you do this by simply writing return 1, 2, 3, "four and five", 6.2, integerVar
These variables must be numeric or string. They will be saved into the savestate itself
and returned back to the application via savestate.registerload() should the state ever be loaded
again later.
Only one function can be registered. Registering a second function will cause the first function
to be returned back by savestate.registersave() before being discarded.
Savestates created with this mechanism are likely to break some savestate features in other emulators.
Don't be surprised if savestates from this version don't work on others if you enable all those
fancy features. Compatible savestates are created if there is no registered save function, or if
the save function returns no parameters at all.
function savestate.registerload(function load)
v0.06+ only
The companion to savestate.registersave, this function registers a function to be called during
a load. The function will be passed parameters -- exactly those returned by the function
registered for a save. If the savestate contains no saved data for your script, the function
will be called without parameters.
Concept code:
function saveState() .... end
function loadState(arg1, arg2, ...) ... end
savestate.registersave(saveState)
savestate.registerload(loadState)
-- Behind the scenes
local saved_variables
-- User presses savestate
saved_variables = { saveState() } -- All return values saved
-- Time passes
-- ...
-- User presses loadstate
loadState(unpack(saved_variables))
Access to movie information.
int movie.framecount()
Returns the current frame count, or nil if no movie is running.
string movie.mode()
Returns "record", "playback", or nil, depending on the current movie.
movie.rerecordcounting(boolean counting)
Select whether rerecording should be counted. If set to false, you can do
all the brute force work you want without inflating the rerecord count.
This will automatically be set to true after a script finishes running, so
don't worry about messing up the rerecord count for the player.
movie.stop()
Stops movie recording/playback. I'm not sure why you'd want to do that, but you can.
0.03+ only
The ability to draw on the surface of the screen is a long sought feature. The surface is 256x239 pixels
(256x224 most of the time though) with (0,0) being in the top-left corner of the screen.
The SNES uses a 16 bit colour system. Red and blue both use 5 bits (0 through 31) while green uses
6 bits (0 through 63), in place of the usual 0 to 255 range. If you want to construct your own exact colours,
multiply your red value by 2048, your green value by 32 and leave your blue value untouched. Add these all
together to get a valid colour. Bright red would be 31*2048 = 63488, for example.
Some strings are accepted. HTML style encoding such as "#00ff00" for green is accepted. Some simple strings such
as "red", "green", "blue", "white" and "black" are also accepted.
The transparent colour is 1 (a VERY dark blue, which is probably not worth using in place of black) or the string
"clear". Remove drawn elements using this colour.
Output is delayed by a frame. The graphics are drawn on a separate buffer and then overlayed on the image
during the next refresh, which means allowing for a frame to execute. Also, the buffer is cleared after drawing,
so if you want to keep something on screen, you must keep drawing it on each frame.
It is an error to draw outside the drawing area. gdoverlay is the only exception to this rule - images will
be clipped to the visible area.
r,g,b = gui.getpixel(int x, int y)
Returns the pixel on the indicated coordinate. (0,0) is the top-left corner and (255, 223) is the typical bottom-right corner,
though (255,238) is allowed. The return value range is (0,0,0) to (31,63,31). You get the actual screen surface before
any damage is done by your drawing. Well, unless you call snes9x.wait() in which case your damage is applied and the SNES
hardware doesn't get a chance to draw a new frame. :)
gui.drawpixel(int x, int y, type colour)
Draw a single pixel on the screen.
gui.drawline(int x1, int y1, int x2, int y2, type colour)
Draw a line between the two indicated positions.
gui.drawbox(int x1, int y1, int x2, int y2, type colour)
Draw a box going through the indicated opposite corners.
gui.text(int x, int y, string message)
Write text on the screen at the indicated position.
The coordinates determine the top-left corner of the box that the text fits in.
The font is the same one as the snes9x messages, and you can't control colours or anything. :(
The minimum y value is 9 for the font's height and each letter will take around 8 pixels of width.
Text that exceeds the viewing area will be cut short, so ensuring your text will fit would be wise.
string gui.gdscreenshot()
0.04+ only
Takes a screen shot of the image and returns it in the form of a string which can be imported by
the gd library using the gd.createFromGdStr() function.
This function is provided so as to allow snes9x to not carry a copy of the gd library itself. If you
want raw RGB32 access, skip the first 11 bytes (header) and then read pixels as Alpha (always 0), Red,
Green, Blue, left to right then top to bottom, range is 0-255 for all colours.
Warning: Storing screen shots in memory is not recommended. Memory usage will blow up pretty quick.
One screen shot string eats around 230 KB of RAM.
gui.gdoverlay(int x=0, int y=0, string gdimage)
0.04+ only
Overlays the given image on top of the screen with the top-left corner in the given screen location.
Transparency is not fully supported -- a pixel must be 100% transparent to actually leave
a hole in the overlayed image or else it will be treated as opaque.
Naturally, the format for input is the gd file format, version 1. The image MUST be truecolour.
The image will be clipped to fit into the screen area.
gui.transparency(int strength)
0.04+ only
Transparency mode. A value of 0 means opaque; a value of 4 means invisible (useful option that one).
As for 1 through 3, I'll let you figure those out.
All image drawing (including gui.gdoverlay) will have the given transparency level applied from that point
on. Note that drawing on the same point over and over will NOT build up to a higher opacity level.
function gui.register(function func)
0.04+ only
Register a function to be called between a frame being prepared for displaying on your screen and
it actually happening. Used when that 1 frame delay for rendering is a pain in the butt.
This function is not necessarily complicated to use, but it's not recommended to users
new to the whole scripting thing.
You may pass nil as the parameter to kill off a registered function. The old function (if any) will be
returned.
string function gui.popup(string message, [string type = "ok"])
v0.05+ only
Pops up a dialog to the user with a message, and returns after the user acknowledges the dialog.
type may be any of "ok", "yesno", "yesnocancel". The return value will be "yes", "no" or "cancel"
as the case may be. "ok" is a waste of effort.
Linux users might want to install xmessage to perform the work. Otherwise the dialog will
appear on the shell and that's less noticable.