Feb 12, 2021
7 mins read
I’ve been doing weekly chaos engineering projects for a while now, so I decided to start a blog. A sort of dumping ground for all the things I do. If you’re interested in more projects like this, give me a follow on Twitter @_mattata. I’m always working on something fun.
Kicking it off with the first blog post. I’m going to compile this blog post (yes, the very one you’re reading) into a valid Gameboy Advance ROM.
Full source code can be viewed here: https://github.com/xen0bit/gbablog
You can download the ROM here: blog.gba
Feel free to try it out below running in a Gameboy Advance emulator written in javascript:
Controls: Click inside the above box change focus to the emulator. Keyboard UP/DOWN to scroll
Keyboard | GBA |
---|---|
Z | A |
X | B |
UP | UP |
DOWN | DOWN |
LEFT | LEFT |
RIGHT | RIGHT |
ENTER | START |
/ | SELECT |
I got this idea while browsing around and heard about TinyGo.
TinyGo is a project to bring the Go programming language to microcontrollers and modern web browsers by creating a new compiler based on LLVM.
One of the compiler targets for TinyGo is a Gameboy Advance, but the documentation shows a bunch of “?” in terms of supporting it. Naturally, I decided this seemed stable enough and decided to give it a shot.
The Gameboy Advance is a handheld videogame paltform based on the ARM7TDMI microcontroller and there was only a single example piece of code written in Tinygo for reference seen below:
(Images after this point tend to look quite fuzzy in the ROM due to color palette limitations)
package main
// Draw a red square on the GameBoy Advance screen.
import (
"image/color"
"machine"
)
var display = machine.Display
func main() {
display.Configure()
for x := int16(30); x < 50; x++ {
for y := int16(80); y < 100; y++ {
display.SetPixel(x, y, color.RGBA{255, 0, 0, 255})
}
}
display.Display()
}
Perfect. I compiled the example and ran it in the VisualboyAdvance emulator and it ran without issue. I needed a bit more though. I need to read gamepad input to allow scrolling through the blog post as well as drawing the blog post to the screen.
The first step was learning how to draw an image to the screen. Since display.SetPixel takes in a color.RGBA I needed to convert my image into something easily readable by the game that can store x, y, Red, Green, Blue, Alpha for each pixel.
For this task, I started by cheating a bit. A byte can hold any value 0-255 and the resolution of the gameboy advance LCD is 240x160. Conviently, if I want to create a really inefficient file format I can fit each value needed into a single byte. Then each block of 6 bytes represents a pixel.
I used the Python PIL package to read an image and export a binary file with this format. Next, I used go-bindata to convert the file into a []byte slice that can be embedded entirely within my TinyGo program. This looks something like this:
func myfile() []byte {
//This byte slice would contain the contents of the image,
//allowing me to embed the file in the binary
return []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a}
}
Surprisingly, this actually worked (somewhat).
After tweaking some things and generally getting a feel for how stuff worked internally, I switched to my next task: figuring out how to read gamepad input.
In retrospect, I probably could’ve used CGO which enables the creation of Go packages that call C code. This would’ve been a lot easier if I’d just used the well defined developer SDK’s already written in C, but I’d had a few beers by the time I encountered this problem. Here’s how to do things THE HARD WAY.
Problem Statement: We want to scroll through an image by a set number of pixels when the user presses up or down on the D-Pad.
Okay, how do we read input from the D-Pad?
Let me point you to GBA Development: Button Input Handling
you might assume that when a button is pressed that the bit in the IO register that it relates to is turned on. This is infact the opposite of what happens. The REG_KEYINPUT 0x04000130 has all bits turned on when buttons are not pressed. This isn’t really a massive issue, it just means that we need to look for when a button’s bit is in the off state to indicate a press.
Well that sounds complicated and hard, so I’m gonna basically ignore pretty much all of that. I do need to create a pointer to the IO register for the keypad though:
regKEYPAD = (*volatile.Register16)(unsafe.Pointer(uintptr(0x04000130)))
Alright, so we try to get the value of the register and… nothing. We wrap getting the value of the register in a while loop and… nothing. Why?
Ah, maybe my timing is off?
So let’s create a timer that checks the value of the register at a regular interval!
import "time"
After countless hours, you will come to the conclusion that “time” actually doesn’t work despite compiling without error.
We now have 2 problems:
Super fun stuff.
After doing some reading, I found GBATek (Warning, HUGE page).
V-Blanking 68 lines, 4.994 ms, 83776 cycles - 30% of v-time
Cool, so I can use IRQ_VBLANK interrupt as a timer AND sync my actions with the screen refresh. Makes sense.
After some fiddling around, we end up with an IRQ interrupt that calls a function “update”. “Update” reads the value of the register regKEYPAD and compares it against the defined values for each button on the Gameboy Advance.
var (
//KeyCodes
keyDOWN = uint16(895)
keyUP = uint16(959)
keyLEFT = uint16(991)
keyRIGHT = uint16(1007)
keyLSHOULDER = uint16(511)
keyRSHOULDER = uint16(767)
keyA = uint16(1022)
keyB = uint16(1021)
keySTART = uint16(1015)
keySELECT = uint16(1019)
)
func update(interrupt.Interrupt) {
//Read uint16 from register that represents the state of current buttons pressed
switch keyValue := regKEYPAD.Get(); keyValue {
case keyDOWN:
//Increase where we draw from in image
lineCursor += 106
//Draw image
drawBlog()
case keyUP:
//Decrease
lineCursor -= 106
//Draw
drawBlog()
func main(){
//Register interrupt using VBLANK and timer, calls update function
interrupt.New(machine.IRQ_VBLANK, update).Enable()
}
Finally, We write a function “drawBlog” which handles drawing the scrolled image on the screen based on “lineCursor” which is controlled by the D-Pad, write a blog about it, screenshot it, and stick it into the game running on the blog about the blog.
Closing Statements: At the end, I’ve certainly learned a lot. Way more than I originally intended to, especailly considering I’d never properly written any Golang code when I started this project.
I think the work that the TinyGo team has put into this is absolutely phenomenal and truly believe that it can be the basis for creating a high level Gameboy Advance SDK in the future. Functionality such as text/font rendering and basic drawing is already supported and works on GBA.
Sharing is caring!