In the BinaryGolf competition, specific file related problems have to be solved with the least amount of bytes. This was the challenge for the 2023 edition:
Me and some other guy decided to go for a GameBoy ROM with an embedded bash script, since old file formats like game ROMs are quite interesting. In the end, we came up with such a polyglot file that met all criteria of the challenge and was 397 bytes in size. This is a short demo of it:
As you can see, the file is a valid bash script that launches a GameBoy emulator with its own file as input to display a 4
after replicating.
The first obvious idea to create this file was to use a GameBoy ROM creator like GB Studio. However, even a ROM with minimal content was still huge in size. It seems that being lazy was not the way to go :)
. In the end, we used a simple Hello World example in pure ASM as a template. Of course we didn’t understand all aspects of this template but we still managed to reduce the resulting file size a bit by removing parts using trial and error. An important thing to do is to strip all zero bytes at the end of the resulting ROM file, since they seem to be irrelevant.
Our initial plan was to use an example that just printed some text on the screen so that we could replace the text with a 4
. However, it seems that font files have to be included when printing a pre-defined string to the screen. Since most fonts are huge, we decided to try something else. In the old days of the GameBoy, disk space was a limiting factor, so clever developers used various tricks to save some of that. One of them was a technique called Tile Map. This basically allows to define (and to design) a small number of visual tiles once and referencing them multiple times using tile maps, which are ultimately used to draw content on the screen. To display a 4
, you could define a white tile once and re-use it a number of times to draw all pixels required for the desired result.
We found out that this does not only save space but also allows embedding a shell script inside of the ROM file because tile data is placed at the start of the ROM. Therefore, we can overwrite this tile data partially using a hex editor after compiling the ROM. The hardest part was defining the tiles and tile maps because low-level GameBoy tooling is nuts.
You can find our source code here (which is based on this Hello World example):
INCLUDE "hardware.inc"
SECTION "Header", ROM0[$100]
jp EntryPoint
ds $150 - @, 0 ; Make room for the header
EntryPoint:
; Shut down audio circuitry
ld a, 0
ld [rNR52], a
ld a, 4
; Do not turn the LCD off outside of VBlank
WaitVBlank:
ld a, [rLY]
cp 144
jp c, WaitVBlank
; Turn the LCD off
ld a, 0
ld [rLCDC], a
; Copy the tile data
ld de, Tiles
ld hl, $9000
ld bc, TilesEnd - Tiles
CopyTiles:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTiles
; Copy the tilemap
ld de, Tilemap
ld hl, $9800
ld bc, TilemapEnd - Tilemap
CopyTilemap:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTilemap
; Turn the LCD on
ld a, LCDCF_ON | LCDCF_BGON
ld [rLCDC], a
; During the first (blank) frame, initialize display registers
ld a, %11100100
ld [rBGP], a
Done:
jp Done
SECTION "Tile data", ROM0
Tiles:
db $00,$00,$20,$00,$20,$00,$20,$00,$28,$00,$3C,$00,$08,$00,$08,$00
TilesEnd:
SECTION "Tilemap", ROM0
Tilemap:
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, 0,0,0,0,0,0,0,0,0,0,0,0
TilemapEnd:
This is the resulting ROM in Base64:
IyEvYmluL3NoCmNwICQwIDQ7Z2VhcmJveSAkMCAjAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wD/AP8A/wD/AP8A/wD/AIAAgACAAIAAgACAAIAAgMNQAQDO7WZmzA0ACwNzAIMADAANAAgRH4iJAA7czG7m3d3Zmbu7Z2NuDuzM3dyZn7u5Mz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5/22PgDqJv8+BPpE//6Q2lcBPgDqQP8R4AAhAJABIAAaIhMLeLHCbQERAAAhAJgB4AAaIhMLeLHCfwE+gepA/w==