Breath of Fire 3 - Data documentation

In this documentation, you will find all the information on the data and data structures used in Breath Of Fire 3.
The "EMI" files format
Table of content:
Introduction
The EMI
files are the containers for all the game's data, apart from the videos.
These files are located in the BIN
directory on the CD, themselves organized by category in sub-folders.
They are binary files, with a header containing a table of contents
of the data they contain,
followed by the data in raw format.
Information
The same data can be found identically in several
EMI
files.The reason for this is that loading several pieces of data in different places on a disk can be slow. So, if the data to be loaded is grouped together in the same place, loading will be much faster, limiting the number of readings required.
Header
The header
structure is composed as follows:
- The number of data contained in the file,
- A magic word
MATH_TBL
, - A table of contents of the various data.
Let's take a closer look at the data in this header
:
From | To | Size | Type | Description |
---|---|---|---|---|
0x00 | 0x03 | 4 | u32 | Data entry count |
0x04 | 0x07 | 4 | ? | ??? Version ??? |
0x08 | 0x0F | 8 | u8[8] | Magic word "MATH_TBL" |
Just after these 16 bytes, we have the table of contents. This consists of several (same number as the number of data) “lines” indicating:
- The size of the data,
- Data parameters (details below),
- The first 4 bytes of the data,
- Data type (to be confirmed),
Here are the details for one line in the table of content:
From | To | Size | Type | Description |
---|---|---|---|---|
0x00 | 0x03 | 4 | u32 | Data size |
0x04 | 0x07 | 4 | u32 | Pointer in RAM |
0x08 | 0x0B | 4 | u8[4] | First 4 bytes |
0x0C | 0x0D | 2 | u16 | Data type ? |
0x0E | 0x0F | 2 | ? | Garbage data |
A list of known data types:
Type ID | Description |
---|---|
3 | Image |
6 | Header samples audio (PSX/VH) |
7 | Data samples audio (PSX/VB) |
10 | Music Sequencer/Midi (PSX/SEQ) |
A lot of data has type 0
, but it's varied data that doesn't correspond to a particular type.
Data
Note that in EMI files, the start of the data is always on a multiple of 2048 (0x800).
For example:
- The start of the first data in EMI files will always be at file position 2048 (0x800),
- The position of the second data will depend on:
- where the previous data begins,
- the size of the previous data,
- finding the next multiple of 2048 in relation to the previous data.
If we were to write it in code, it would look like this (In C++, the binary shift loses data):
unsigned int next_data_position = current_data_position + (current_data_size + 0x7FF >> 0xB) * 0x800;
/*
For example, my first data starts at 0x800 and has a size of 2100 bytes
To find the next data, I will do:
- First let's understand the complex part: "current_data_size + 0x7FF >> 0xB" -> "(2100 + 2047) >> 11" -> "4147 >> 11" -> "2".
This is the times we have to multiple 2048 from the current data to find the next one.
- Let's continue: "current_data_position + (current_data_size + 0x7FF >> 0xB) * 0x800" -> "2048 + 2 * 2048" -> 6144 (0x1800).
This is the actual position of the next data in the file
*/
Between two data, you will see mostly garbage bytes like a series of 2E
or 5F
.
Libraries and tools
You can use the Emi Extractor tool made by
Navarchos/Red Herring
to extract the data from your EMI files.
Texture and Palette
Table of content:
Introduction
Textures and palettes represent most of the visuals that will appear on screen:
- Characters, monsters, ...
- Texts and menus,
- Textures applied to 3D meshes,
- ...
In this document, we'll look at how these data are encoded and how they relate to each other.
Texture
Textures in the EMI files all seem to have the same Data Type
, which is 3
.
They have no header
, and no information about their width, height, bits per pixel and palette used.
There's only raw texture data.
Information
There are ways of finding this information relatively easily:
- Find out what platform the game is running on (Playstation), and what formats are available on it,
- Use raw image-reading software (such as TileMolester), and change the settings until you find the right format.
To compress textures size, a palette system (also known as CLUT: Color Lookup table) allows you to use only a color index found in a table:
- If an image's pixels are encoded in 4 bits, then the image can contain 16 different colors,
- If an image's pixels are encoded in 8 bits, then the image can contain 256 different colors.
However, in BoF3, two formats in particular stand out:
- For textures encoded in 4 bits per pixel, the width is 128 pixels,
- For textures encoded in 8 bits per pixel, the width is 64 pixels.
Keep in mind that there are other possible formats.
Textures can themselves be split into several tiles, with a size not specified in the data. (This information is probably hardcoded directly when the texture is used)
Now, if you try to visualize a texture with the above information, you'll see elements, but in a “broken” way. This is because the texture is composed of “strips”.
In fact, to see the texture properly, you'll need to glue the strips together correctly:
- A stripe is 256 pixels wide by 32 pixels high,
- This seems to apply to both 4bpp and 8bpp images.

Example stripped and unstripped texture
Here's an algorithm in C++ to perform this transformation from one data array to another of the same size
:
u64 ComputeUnstrippedIndex(u64 stripped_index)
{
auto stripped_x = stripped_index % STRIPPED_TEXTURE_WIDTH;
auto stripped_y = stripped_index / STRIPPED_TEXTURE_WIDTH;
auto unstripped_x = stripped_x + ((stripped_y & 0x20) >> 5) * STRIPPED_TEXTURE_WIDTH;
auto unstripped_y = (STRIPE_HEIGHT * stripped_y / 64)) + (stripped_y & 0x1F);
return unstripped_x + unstripped_y * STRIPE_WIDTH;
}
int main()
{
// Read the stripped texture data, and create another array of the same size (initialized to 0)
vector<u8> stripped_texture_data = ReadData();
vector<u8> unstripped_texture_data(input_data.size());
// We compute the index of each "unstripped" pixel, and push it to the output
for(u64 i = 0; i < stripped_texture_data.size(); ++i)
unstripped_texture_data[ComputeUnstrippedIndex(i)] = stripped_texture_data[i];
// Just write the new data in a file, the texture has now a width of 256, with the encoding
WriteData(unstripped_texture_data);
}
Palette
Palettes are used in textures to provide them colors. They are composed by multiple rows of colors. The number of max rows possible in a palette is not defined, yet.
Again, like textures, palettes does not have any header, but you can use the same tools to find their content.

Example of a palette
Most of the time, palettes are not too far from the textures they are used (in the same EMI file), but for a very few of them, they seem to be loaded once (NEED TO BE CONFIRMED WITH MORE RESEARCH).
Generally, when a palette is used:
- In a 4bpp texture: each row of this palette would be composed with 16 colors,
- In a 8bpp texture: NEED MORE INVESTIGATION.
Again, other formats are possible.
Now, each Color
of the palette is encoded in 16 bits, (more info on TIM_format):
RED | GREEN | BLUE | STP* |
---|---|---|---|
0 to 4 (5 bits) | 5 to 9 (5 bits) | 10 to 14 (5bits) | 15 |
The STP
is the "Special transparency processing": Because by default, the color Black (r0, g0, b0)
is treated as
transparent, unless the STP bit is set to 1.
Now, let's do some code! How to convert those 16 bits color to a more standard 24 bits ?
using u8 = uint8_t;
using u16 = uint16_t;
// A classic 24 bits color
struct ColorRgb
{
u8 r{};
u8 g{};
u8 b{};
};
ColorRgb Convert16BitsTo24Bits(const u16 pixel)
{
// I'm using at least C++20 here (https://en.cppreference.com/w/cpp/language/aggregate_initialization)
// but you can initialize the old fashion way too.
return
{
// ([SBBBBBGGGGGRRRRR] << 3) & 0xF8 = [BBBGGGGGRRRRR000] & [0000000011111000] = [RRRRR000]
.r = static_cast<u8>((pixel << 3) & 0xF8),
// ([SBBBBBGGGGGRRRRR] >> 2) & 0xF8 = [00SBBBBBGGGGGRRR] & [0000000011111000] = [GGGGG000]
.g = static_cast<u8>((pixel >> 2) & 0xF8),
// ([SBBBBBGGGGGRRRRR] >> 7) & 0xF8 = [0000000SBBBBBGGG] & [0000000011111000] = [BBBBB000]
.b = static_cast<u8>((pixel >> 7) & 0xF8)
};
}
You are now the master of colors!
TIPS
Palettes of the games can be found in EMI files by using their
Pointer/RAM
value of the EMI's table of content:The
Pointer/RAM
of palettes should be between 0x80033XXX and 0x8003AXXX.
How palettes are used in textures
From initial research and results, it would appear that for 4BPP textures, a row from the palette is applied
to a tile of a texture: 1 row of 16 colors == Colors of a single tile
.
Still needs a lot of investigation on this side.
Todo - Missing elements
- Find where are stored the tiles structure of a texture,
- Find a way to know which palettes are used in a texture,
- Find a way to know which rows in palette is used in a tile,
STATUS.EMI
Table of content:
Introduction
This file represent all the Status Menu
data, when you press Square
in game.
The StatusMenu textures
This texture is located at the second index of the STATUS.EMI
file, and contains tiles of:
- Characters portraits
- Menu Icons
- Menu Layout
The texture has a width of 128 pixel, and is encoded in 4bpp.
For the characters portraits, they are tiles of 40x48. They use the following palette in file like this:
Characters | Palette index | Palette Row |
---|---|---|
Adult Ryu | fourth | 01 |
Young Ryu | fourth | 11 |
Tepo | fourth | 12 |
Young Rei | fourth | 13 |
Adult Rei | fourth | 13 |
Young Nina | fourth | 15 |
Adult Nina | fourth | 15 |
Momo | fourth | 00 |
Garr | fourth | 02 |
Pico | fifth | 13 |
Dragon | fourth | 14 |
Information
Looks like the tiles are aligned in rows of 256 x 40 pixels
Two palettes for menu
They are the forth and fifth data of the STATUS.EMI
: They contain characters portraits and menu colors.
Todo
Still a lot to do in this file!