How to Write Lua Scripts for Video Games with the BizHawk Emulator
Time to read: 5 minutes
BizHawk is a multi-system emulator beloved by the Tool Assisted Speedrun community for its recording/playback and debugging tools, as well as Lua scripting functionality that can be used for a variety of purposes.
While there is basic documentation describing some of the functions available in these scripts, the lack of working code samples might make it difficult for some to get started. Let's walk through some of the Lua scripting features the BizHawk emulator provides, and have some fun with real examples.
Setting up the BizHawk emulator
BizHawk runs on multiple operating systems, but Lua scripting is only available on the Windows versions. If you are on Mac or Linux, you can use Wine, although setup might be a bit of an involved process depending on which version of which operating system you're using.
There are installation instructions in the README of the project's repository, including an installer that takes care of the prerequisites. Download, unzip, run this installation tool, and then download the corresponding version of the emulator from TASVideos. Now you can run the EmuHawk executable, and load up your favorite video game ROM!
The original Super Mario Bros for the Nintendo Entertainment System is a widely popular classic game so we will be referencing it throughout the post for our code examples.
Getting started with Lua scripting
Now that you have the emulator running, open up the Lua scripting window via Tools -> Lua Console. Here you can open scripts, control their execution, and view their output. In the text editor of your choice, create a file called hello.lua
and add the following line of code to it:
Run this code by clicking Script -> Open Script in the Lua Console and navigating to where you saved the file. You should see "Hello World!" printed in the “Outputs” tab.
For most emulator scripts, it's desirable to have a main execution loop that runs continuously until it's stopped, executing code before each frame of the game is rendered. There are two ways you can do this. One is to write an infinite loop using emu.frameadvance
at the end of each iteration, and the other is to register a function to event.onframestart
. Let's take a look at both of these.
Replace the code in hello.lua
with the following, which will write "Hello World!" to the screen for every frame of the game, rather than the console:
You can double click your script in the Lua console to toggle it on and off, and each time you restart it, your new code will load if you have made changes and saved the file. Do this and you should see "Hello World!" pop onto the screen and stay there because it is being printed on every frame. Personally, I am excited to write an infinite loop that's actually useful, as an act of vindication for all of the ones I've written by mistake.
If you don't like the way infinite loops look, let's try doing it more functionally using event.onframestart
with the following code that has the same behavior:
With that out of the way, let's move onto writing code to interact with the game itself.
Reading from and writing to memory
BizHawk provides many useful developer tools to gain insight into the games you are playing. These utilities come in handy when doing speedruns or hacking old games. One of them is a “hex editor”, a tool that allows you to view and edit the game's RAM in real time. The RAM is displayed in the form of 4 digit (for NES games) hexadecimal addresses and 2 digit hexadecimal values.
Open BizHawk's hex editor by clicking Tools -> Hex Editor. All of the two digit hex numbers you see represent a value in the game’s RAM at a specific location. You can change any value to see what happens in-game. You can also search for specific values using Ctrl-F or clicking Edit -> Find. For example, the hex editor can be used to find the bytes corresponding to the timer in Super Mario Bros and to change the time to zero to kill Mario:
Cleverly editing memory addresses can result in some interesting gameplay modifications. In this case, we killed Mario by editing the values in the memory addresses, 07F8-07FA to zero. But this can also be done programmatically in a Lua script.
Create a new script with the following code, which will log the values of the timer to the console, and then change them to zero to kill Mario:
In Lua, 0x
before a number means that you are referring to a hexadecimal value, and ..
is an operator for string concatenation. As you can see in this code you can read the values of hex addresses in the game's memory with memory.readbyte
and write to them with memory.writebyte
.
The idea of digging around a game's memory may seem daunting at first, but it turns out that for many games, people on the internet have already done this for you! Here is a RAM map for Super Mario Bros. Try playing around with these memory addresses and values for yourself and see what happens.
Controller input - playing the game with code
Aside from manipulating a game's memory, you can also programmatically enter button presses with joypad.set
and read input with joypad.get
. The button names are different depending on each video game console, so here is a useful chart with string values of button names for each system.
Controller inputs are represented by a table in Lua, which is similar to a dictionary in Python or an object in JavaScript. For instance, joypad.set({A=true}, 1)
would press the A button on player 1's controller.
Using Mario as an example again, run a Lua script with the following code to make Mario run to the right indefinitely at full speed:
This is great and while running full speed into a Goomba might be funny, it is not a good recipe for success. What if we combined all of the things we learned to write some code that can run through the entire first level of Super Mario Bros? With the information from the RAM map for the game, we can write code to read values from the game's memory to make decisions on when to jump.
Create a file called level1.lua
with the following code:
I left comments for each section to explain the code, but the basic strategy is to continuously move to the right while jumping in these scenarios:
- Whenever an enemy gets too close
- When there is a pit nearby
- If Mario is colliding with an obstacle
jumping whenever an enemy gets too close, whenever there is a pit nearby, or whenever Mario is colliding with an obstacle. There are values in memory that represent all of these things, at least accurately enough for this example.
Start the game at World 1-1, and run this code to see Mario complete the level! (If you let it keep running he will likely get to World 1-2 and lose to the second Goomba).
It's not the most sophisticated or efficient way to beat the level, but it gets the job done. This was only the first level, so now the rest is up to you. Try improving on the script to beat the next level!
Level Complete!
We've covered some decent ground here to get you started writing with Lua scripts for video games, but BizHawk's Lua API has some other features as well. With the event
module you can register functions to events, such as saving or loading save states, or writing to certain memory locations. There is also a comm
module that provides some communications functionality such as socket connections and HTTP requests. These are all described in the BizHawk Lua Functions documentation on TASVideos.
I cannot wait to see what kind emulator extensions you build. Drop me a line if you have any questions or if you just want to show off your hack.
- Email: sagnew@twilio.com
- Twitter: @Sagnewshreds
- Github: Sagnew
- Twitch (streaming live code): Sagnewshreds
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.