Tile set file

From Boktai Hacking Wiki

Tile set files contain the GBA tiles (8x8 px, 4 bpp) used by tile maps. Boktai 1 only has a few of these files (11), while Boktai 2 and 3 only have one each. Tile set files contain multiple "parts", and each part can be loaded individually instead of having to load the entire file at once. Tile sets are loaded by engine call 0x11d4 by specifying the file ID and part ID(s). The requested parts are directly copied from the tile set file into the GBA's VRAM.

File format

There are two versions of the tile set file format. Boktai 2 and 3 use a different version than Boktai 1: starting with Boktai 2, the tileRefCount field has been expanded from a 16-bit to a 32-bit integer.

// Boktai 1:
struct TileSetFile_B1 {
  /* offset 0x00 */ u16 partCount;    // Number of elements in the parts[] array
  /* offset 0x02 */ u16 tileRefCount; // Number of elements in the tileReferences[] array
  /* offset 0x04 */ u16 tileCount;    // Number of elements in the tiles[] array
  /* offset 0x06 */ u16 unknown_0x06;

  // Byte offset from start of this file to the parts array
  /* offset 0x08 */ u32 offsetToPartList;

  // Byte offset from start of this file to the tileReferences array
  /* offset 0x0c */ u32 offsetToTileReferences;

  // Byte offset from start of this file to the tileData array
  /* offset 0x10 */ u32 offsetToTileData;

  TileSetPart parts[partCount];
  u16 tileReferences[tileRefCount];
  GBA_Tile tileData[tileCount]; // 32 bytes per tile
};

// Boktai 2 and 3:
struct TileSetFile_B2 {
  /* offset 0x00 */ u16 partCount;    // Number of elements in the parts[] array
  /* offset 0x02 */ u16 unknown_0x02;
  /* offset 0x04 */ u32 tileRefCount; // Number of elements in the tileReferences[] array
  /* offset 0x08 */ u32 tileCount;    // Number of elements in the tiles[] array

  /* offset 0x0c */ u32 offsetToPartList;
  /* offset 0x10 */ u32 offsetToTileReferences;
  /* offset 0x14 */ u32 offsetToTileData;

  TileSetPart parts[partCount];
  u16 tileReferences[tileRefCount];
  GBA_Tile tileData[tileCount]; // 32 bytes per tile
};

struct TileSetPart {
  u16 id;
  u16 tileCount;
  u32 startIndex;
};

Loading parts

To load a part by its ID, search the parts array for the entry with the matching TileSetPart.id. Then use the TileSetPart.startIndex field as an index into the tileReferences array. For each of the TileSetPart.tileCount tile references, copy the tileData element with that index into VRAM:

void LoadTileSetPart(TileSetFile_B1* tileSet, u16 partId)
{
  TileSetPart* partList = (char*)tileSet + tileSet->offsetToParts;
  u16* tileReferences = (char*)tileSet + tileSet->offsetToTileReferences;
  GBA_Tile* tileData = (char*)tileSet + tileSet->offsetToTileData;

  TileSetPart* part;
  for (int i=0; i<tileSet->partCount; i++) {
    part = partList[i];
    if (part->id == partId) break;
  }
  // Assume we found the matching part

  for (int i=0; i<part->tileCount; i++) {
    int tileIndex = tileReferences[part->startIndex + i];
    GBA_Tile tile = tileData[tileIndex];
    VRAM[i] = tile;
  }
}