Sprite set file: Difference between revisions
| No edit summary | |||
| Line 7: | Line 7: | ||
| <syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
| struct spriteset_header { | struct spriteset_header { | ||
|    u16  |    u16 palStart;    // palette start index in the sprite palettes file | ||
|    u16 spriteCount; // number of sprites in this sprite set |    u16 spriteCount; // number of sprites in this sprite set | ||
|    u16 unknown_0x04; |    u16 unknown_0x04; | ||
| Line 36: | Line 36: | ||
| struct spriteset_obj { | struct spriteset_obj { | ||
|    u8 flip; // bitmask; 4 = horizontal, 8 = vertical |    u8 flip; // bitmask; 4 = horizontal, 8 = vertical. TODO: other bits? | ||
|    // 00=8x8,   01=16x8,  02=8x16 |    // 00=8x8,   01=16x8,  02=8x16 | ||
| Line 48: | Line 48: | ||
|    // Bottom 12 bits: Start index for this OBJ in the raw tile data array |    // Bottom 12 bits: Start index for this OBJ in the raw tile data array | ||
|    // Top 4 bits: Palette index |    // Top 4 bits: Palette index, add to spriteset_header.palStart | ||
|    u16 tileAndPalette; |    u16 tileAndPalette; | ||
| }; | }; | ||
| struct spriteset_tile { | struct spriteset_tile { | ||
|    u8[32]  |    u8 data[32]; // Raw GBA tile data (8x8 px, 4bpp) | ||
| } | }; | ||
| </syntaxhighlight> | </syntaxhighlight> | ||
| = Palettes = | |||
| Each OBJ is linked to the palette it should use using the information in spriteset_obj.tileAndPalette and spriteset_header.palStart. However, the game engine can override this (used e.g. to animate menu cursors etc.). | |||
| Each game's master file table contains a single file that defines ''all'' palettes for each spriteset_sprite. This file is stored in the MFT directory with id_low = 0x4679 and id_high = 0x9a65. This directory will only contain a single file with the ID 0xc5e9 in every game. | |||
| The structure of this file is simple: A 4 byte header, containing the number of palettes in this file, followed by an array of that many palettes. Each palette is 0x20 bytes in size (16 entries per palette, 2 bytes per entry, see [https://problemkaputt.de/gbatek-lcd-color-palettes.htm GBATEK]). The game engine will automatically copy palettes from this file into palette RAM as neccessary. | |||
| To get the palette index for a spriteset_obj, add <code>(spriteset_obj.tileAndPalette >> 12) + spriteset_header.palStart</code>. | |||
| = Usage example = | = Usage example = | ||
| <syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
| void load_sprite(spriteset_header* spriteset, int spriteId) | void load_sprite(spriteset_header* spriteset, void* spritePalettes, int spriteId) | ||
| { | { | ||
|   // spriteset should point to the start of a spriteset file | |||
|   // spritePalettes should point to the start of the sprite palettes file (there is only one per game) | |||
|    spriteset_sprite* sprites = (u8*)spriteset + spriteset->offsetToSprites; |    spriteset_sprite* sprites = (u8*)spriteset + spriteset->offsetToSprites; | ||
|    spriteset_tile* tiles = (u8*)spriteset + spriteset->offsetToTiles; |    spriteset_tile* tiles = (u8*)spriteset + spriteset->offsetToTiles; | ||
|    spriteset_sprite* sprite = &sprites[spriteId]; |    spriteset_sprite* sprite = &sprites[spriteId]; | ||
|    // do something with sprite |    // do something with sprite | ||
|   spriteset_obj* objs = (u8*)spriteset + spriteset->offsetToOBJs + sprite->objOffset; | |||
|    for (int objIndex=0; objIndex < sprite->objCount; objIndex++) { |    for (int objIndex=0; objIndex < sprite->objCount; objIndex++) { | ||
|      spriteset_obj* obj = objs  |      spriteset_obj* obj = &objs[objIndex]; | ||
|      // do something with obj |      // do something with obj | ||
|      int  |      int firstTileIndex = obj->tileAndPalette & 0xfff; | ||
|      spriteset_tile* firstTile = &tiles[ |      spriteset_tile* firstTile = &tiles[firstTileIndex]; | ||
|     // do something with the tiles | |||
|      // number of tiles is implied by sprite->shape |      // number of tiles is implied by sprite->shape | ||
|      //  |      // for OBJs with a height of more than 1 tile, successive rows of tiles are stored 16 tiles apart. | ||
|     // example: assuming startTileIndex = 4 and shape = 8x16: tile indices are 4 and 20. | |||
|     // color lookup example: | |||
|     int pixelColor = firstTile->data[0] & 0xf; // extract color of 1st pixel in the tile | |||
|     int paletteIndex = spriteset->palStart + (obj->tileAndPalette >> 12); | |||
|     // First 4 bytes are an ignoreable header, and each palette is 0x20 bytes in size (16 colors * 2 bytes per color) | |||
|     u16* palettePtr = (u8*)spritePalettes + 4 + 0x20*paletteIndex; | |||
|     u16 paletteEntry = palettePtr[pixelColor]; | |||
|    } |    } | ||
| } | } | ||
Revision as of 15:48, 17 September 2024
Sprite sets are stored in directory id_low = 0x5f29, id_high = 0xc8e5 in the master file table. Each sprite set contains multiple distinct sprites, and each sprite may be made of multiple GBA OBJs.
File format
TODO: very incomplete
struct spriteset_header {
  u16 palStart;    // palette start index in the sprite palettes file
  u16 spriteCount; // number of sprites in this sprite set
  u16 unknown_0x04;
  u16 unknown_0x06;
  u16 unknown_0x08;
  u16 unknown_0x0a;
  u32 offsetToSprites; // byte offset from start of this struct to the spriteset_sprite[spriteCount] array
  u32 offsetToUnknown_0x10;
  u32 offsetToOBJs;    // byte offset from start of this struct to the spriteset_obj[] array
  u32 offsetToUnknown_0x18;
  u32 offsetToTiles;   // byte offset from start of this struct to the spriteset_tile[] array
};
struct spriteset_sprite {
  u16 unknown_0x00;
  u16 objCount; // Sprite is made up of this many OBJs
  u16 unknown_0x04;
  u16 unknown_0x06;
  u16 unknown_0x08;
  u16 unknown_0x0a;
  u16 unknown_0x0c;
  u16 unknown_0x0e;
  // BYTE OFFSET from start of sprite_obj[] array to the 1st OBJ of this sprite
  u32 objOffset;
};
struct spriteset_obj {
  u8 flip; // bitmask; 4 = horizontal, 8 = vertical. TODO: other bits?
  // 00=8x8,   01=16x8,  02=8x16
  // 04=16x16, 05=32x8,  06=8x32
  // 08=32x32, 09=32x16, 10=16x32
  // 12=64x64, 13=64x32, 14=32x64
  u8 shape;
  i16 xOffset;
  i16 yOffset;
  // Bottom 12 bits: Start index for this OBJ in the raw tile data array
  // Top 4 bits: Palette index, add to spriteset_header.palStart
  u16 tileAndPalette;
};
struct spriteset_tile {
  u8 data[32]; // Raw GBA tile data (8x8 px, 4bpp)
};
Palettes
Each OBJ is linked to the palette it should use using the information in spriteset_obj.tileAndPalette and spriteset_header.palStart. However, the game engine can override this (used e.g. to animate menu cursors etc.).
Each game's master file table contains a single file that defines all palettes for each spriteset_sprite. This file is stored in the MFT directory with id_low = 0x4679 and id_high = 0x9a65. This directory will only contain a single file with the ID 0xc5e9 in every game.
The structure of this file is simple: A 4 byte header, containing the number of palettes in this file, followed by an array of that many palettes. Each palette is 0x20 bytes in size (16 entries per palette, 2 bytes per entry, see GBATEK). The game engine will automatically copy palettes from this file into palette RAM as neccessary.
To get the palette index for a spriteset_obj, add (spriteset_obj.tileAndPalette >> 12) + spriteset_header.palStart.
Usage example
void load_sprite(spriteset_header* spriteset, void* spritePalettes, int spriteId)
{
  // spriteset should point to the start of a spriteset file
  // spritePalettes should point to the start of the sprite palettes file (there is only one per game)
  spriteset_sprite* sprites = (u8*)spriteset + spriteset->offsetToSprites;
  spriteset_tile* tiles = (u8*)spriteset + spriteset->offsetToTiles;
  spriteset_sprite* sprite = &sprites[spriteId];
  // do something with sprite
  spriteset_obj* objs = (u8*)spriteset + spriteset->offsetToOBJs + sprite->objOffset;
  for (int objIndex=0; objIndex < sprite->objCount; objIndex++) {
    spriteset_obj* obj = &objs[objIndex];
    // do something with obj
    int firstTileIndex = obj->tileAndPalette & 0xfff;
    spriteset_tile* firstTile = &tiles[firstTileIndex];
    // do something with the tiles
    // number of tiles is implied by sprite->shape
    // for OBJs with a height of more than 1 tile, successive rows of tiles are stored 16 tiles apart.
    // example: assuming startTileIndex = 4 and shape = 8x16: tile indices are 4 and 20.
    // color lookup example:
    int pixelColor = firstTile->data[0] & 0xf; // extract color of 1st pixel in the tile
    int paletteIndex = spriteset->palStart + (obj->tileAndPalette >> 12);
    // First 4 bytes are an ignoreable header, and each palette is 0x20 bytes in size (16 colors * 2 bytes per color)
    u16* palettePtr = (u8*)spritePalettes + 4 + 0x20*paletteIndex;
    u16 paletteEntry = palettePtr[pixelColor];
  }
}