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: