GBFS is an archive format created by Damian Yerrick in April 2002.
In the early days of the GBA homebrew scene, there was no efficient way to include a binary file as a read-only data array in a GBA program.
The alternative at the time was to translate each binary file to a C source code file containing a large char
array.
This technique was inefficient in GCC, the compiler used by both commercial and homebrew GBA game developers, especially around the turn of the millennium when new PCs were still shipped with 64 MB of RAM.
The DJGPP FAQ explains:
Some innocent-looking programs are known to cause GCC to gobble preposterous amounts of memory, which could cause it to crash or abort after printing "Virtual memory exhausted". One particular case of such programs is when you initialize very large arrays. For example, to compile a source which initializes a char array of 300,000 elements requires more than 60MB(!) of memory. You should avoid such constructs in your programs.
As knowledge of assembly language became more widespread in the GBA scene, this technique was eventually replaced with conversion to a constant array in assembly language, greatly reducing RAM use at build time. This shifted the limiting factor: as a ROM got bigger with more assets, the command line to the linker got longer and longer, eventually surpassing what an operating system would allow for a new process.
The design of GBFS was inspired by that of datafiles in the Allegro library versions 3 and 4. GBFS is designed to be appended to a GBA executable in the same way that an Allegro datafile was appended to a PC game executable. This avoids pathological compiler behavior, plus it lets artists work on assets without having to request a recompile. The UNIX tape archive (tar) format was rejected in favor of a custom format for two reasons: tar had far too much directory overhead (500 bytes per file), and linear search through a large archive was inefficient.
All values in the GBFS header and directory are little-endian because the GBA CPU is a little-endian ARM7. (It has been suggested, but not yet implemented, to make a big-endian variant of the format called "MDFS" for targeting Sega Genesis, Nintendo GameCube, and Wii.)
Each GBFS file begins with a header at least 24 bytes.
type somefile.gbfs
in the DOS prompt do something sane.uint32_t
): Total length of archive, including header, directory, and file data. Used to find the next archive if more than one are appended.uint16_t
): Offset from start of file to start of directory, or length of header. Was intended for determining format version.uint16_t
): Number of files in archive.Each entry in the directory is 32 bytes, and files are sorted with names in memcmp() order.
uint32_t
): Length of file in bytesuint32_t
): Offset in bytes from start of GBFS file to start of file dataGBFS comes with a library that can locate an appended archive, search directory for a filename, and enumerate files in an archive. Locating an appended archive requires the executable to have been padded to a multiple of 256 bytes before appending archives. This way, the library can efficiently perform a linear search through the 32 MiB of GBA ROM address space to find the header. The archiver tool pads individual files to start on 16-byte boundaries so that memory copy can work efficiently.
In 2004, it became apparent that source-level debugging was incompatible with use of appended archives.
Also by this time, developers had become more comfortable with assembly language files.
So alternate methods of inserting archives into an executable were proposed.
One involved converting an archive to an array in an assembly language file (the bin2s
method), which was not quite as inefficient as doing so in C.
The other involved generating a large zero-filled assembly language file and later overwriting that space with archives.
The GBFS tools ended up included with devkitARM.