Font
The font stores all characters the game uses for drawing text. There are two types of characters: Narrow characters are 8x16 px in size (two tiles), and wide characters are 16x16 px in size (four tiles).
The font is stored in the directory with id_low=0x6d24, id_high=0xa705 in the master file table. That directory will contain a single font file with id=0x3f51.
Warning: In Boktai 1, wide characters are structured differently than described here. In Boktai 1, each wide character is 0x48 bytes in size, whereas in Boktai 2 and 3 each wide character is 0x80 bytes in size. The format of Boktai 1 wide characters still needs to be reverse engineered.
File format
struct FontHeader {
/* offset 0x0 */ u16 narrowCharCount;
/* offset 0x2 */ u16 wideCharCount;
/* offset 0x4 */ u32 offsetToNarrowChars;
/* offset 0x8 */ u32 offsetToWideChars;
// Array of 8x8 px 4bpp tiles follows (32 bytes per tile)
};
Narrow characters
The font should provide one 8x16 px character for each ASCII byte between 0x20 and 0xff inclusive (= 224 characters in total). If a byte is less than 0x20, the game will not render a character for that byte:
void DrawNarrowCharacter(int x, int y, char ch, FontHeader* font) {
// The 1st 0x20 ASCII characters are not stored in the font, and cannot be rendered
int charIndex = ch - 0x20;
// Each tile is 0x20 bytes in size, a narrow character is 1x2 tiles in size,
// giving a total size of 0x40 bytes per character
u8* tileStart = (u8*)font + font->offsetToNarrowChars + charIndex*0x40;
// Draw two tiles, stacked vertically
DrawTile(x, y, tileStart);
DrawTile(x, y+1, tileStart + 0x20);
}
Wide characters
The font can also provide arbitrarily many wide 16x16 px characters. These are referenced from strings using a two-byte sequence, stored in big-endian byte order, with the top bit of the first byte set. For example, to reference the wide character 0x123, the string should contain the two bytes 0x81 0x23
. The first wide character has index 0:
void DrawWideCharacter(int x, int y, char* text, FontHeader* font) {
char high = text[0];
char low = text[1];
// Combine the two bytes, and clear the top bit
int charIndex = ((high & 0x7f) << 8) | low
// Each tile is 0x80 bytes in size, a wide character is 2x2 tiles in size,
// giving a total size of 0x80 bytes per character
u8* tileStart = (u8*)font + font->offsetToWideChars + charIndex*0x80;
// Draw four tiles
DrawTile(x, y, tileStart);
DrawTile(x+1, y, tileStart + 0x20);
DrawTile(x, y+1, tileStart + 0x40);
DrawTile(x+1, y+1, tileStart + 0x60);
}