Maps/Mesh
The structure of a mesh file consists of a header of 196 bytes followed by a series of chunks of different kinds of data.
Contents
Header
The header serves as a table of contents for the data chunks. It is a series of 32-bit unsigned little-endian integers, each of which is either 0 (meaning that a given chunk is not present in the file) or an intra-file pointer to the byte where the chunk begins. Here's a table of what each pointer points to.
address of pointer | type of chunk it points to |
---|---|
0x40 | Primary mesh |
0x44 | Texture palettes (color) |
0x4c | Unknown (this pointer is only non-zero in MAP000.5) |
0x64 | Light colors and positions, background gradient colors |
0x68 | Terrain (tile heights, slopes, and surface types) |
0x6c | Texture animation instructions |
0x70 | Palette animation instructions |
0x7c | Texture palettes (grayscale) |
0x8c | Mesh animation instructions |
0x90 | Animated mesh 1 |
0x94 | Animated mesh 2 |
0x98 | Animated mesh 3 |
0x9c | Animated mesh 4 |
0xa0 | Animated mesh 5 |
0xa4 | Animated mesh 6 |
0xa8 | Animated mesh 7 |
0xac | Animated mesh 8 |
0xb0 | Polygon visibility angles |
Notice that this table skips some pointers. The omitted pointers are 0 in every mesh file: they never point at anything.
Primary mesh
This chunk contains most of the polygons that make up the map. It contains XYZ coordinates for each vertex, normal vectors for each vertex, UV coordinates for each vertex, and the ID of the palette to use for each polygon's texture.
The polygons are divided into four groups: Textured triangles, textured quadrilaterals, untextured triangles, and untextured quadrilaterals. The untextured polygons are always black. They're used mostly around the edges of the map to make the map look like a solid cross-section.
Header
The mesh chunk begins with a header of 4 16-bit unsigned integers specifying how many of each type of polygon the mesh contains:
Width (bits) | Data type | Purpose | Maximum Value |
---|---|---|---|
16 | uint | Number of textured triangles (N) | 512 |
16 | uint | Number of textured quadrilaterals (P) | 768 |
16 | uint | Number of untextured triangles (Q) | 64 |
16 | uint | Number of untextured quadrilaterals (R) | 256 |
XYZ coordinates
The next block of data consists of N sets of 3 XYZ coordinates (one XYZ coordinate for each of the triangle's points):
Width (bits) | Data type | Purpose |
---|---|---|
16 | int | Point A, X coordinate |
16 | int | Point A, Y coordinate |
16 | int | Point A, Z coordinate |
16 | int | Point B, X coordinate |
16 | int | Point B, Y coordinate |
16 | int | Point B, Z coordinate |
16 | int | Point C, X coordinate |
16 | int | Point C, Y coordinate |
16 | int | Point C, Z coordinate |
Then P sets of 4 XYZ coordinates:
Width (bits) | Data type | Purpose |
---|---|---|
16 | int | Point A, X coordinate |
16 | int | Point A, Y coordinate |
16 | int | Point A, Z coordinate |
16 | int | Point B, X coordinate |
16 | int | Point B, Y coordinate |
16 | int | Point B, Z coordinate |
16 | int | Point C, X coordinate |
16 | int | Point C, Y coordinate |
16 | int | Point C, Z coordinate |
16 | int | Point D, X coordinate |
16 | int | Point D, Y coordinate |
16 | int | Point D, Z coordinate |
Then Q sets of 3 XYZ coordinates (same format as triangles, above), then R sets of 4 XYZ coordinate (same format as quadrilaterals, above).
Normal vectors
Next come the normal vectors, which follow the same pattern. Instead of being integers, however, the values are stored as fixed-point numbers. They have a sign bit, then a 3-bit whole part and a 12-bit fractional part. This sounds complicated, but basically it just means you read them as signed 16-bit integers, then convert them to floating-point numbers and divide by 4096.0.
Also, there are no normal vectors for the untextured polygons, since those are always completely black.
First, there are N sets of 3 normal vectors:
Width (bits) | Data type | Purpose |
---|---|---|
16 | fixed 1,3,12 | Point A, nomal vector X |
16 | fixed 1,3,12 | Point A, nomal vector Y |
16 | fixed 1,3,12 | Point A, nomal vector Z |
16 | fixed 1,3,12 | Point B, nomal vector X |
16 | fixed 1,3,12 | Point B, nomal vector Y |
16 | fixed 1,3,12 | Point B, nomal vector Z |
16 | fixed 1,3,12 | Point C, nomal vector X |
16 | fixed 1,3,12 | Point C, nomal vector Y |
16 | fixed 1,3,12 | Point C, nomal vector Z |
Then P sets of 4 normal vectors:
Width (bits) | Data type | Purpose |
---|---|---|
16 | fixed 1,3,12 | Point A, nomal vector X |
16 | fixed 1,3,12 | Point A, nomal vector Y |
16 | fixed 1,3,12 | Point A, nomal vector Z |
16 | fixed 1,3,12 | Point B, nomal vector X |
16 | fixed 1,3,12 | Point B, nomal vector Y |
16 | fixed 1,3,12 | Point B, nomal vector Z |
16 | fixed 1,3,12 | Point C, nomal vector X |
16 | fixed 1,3,12 | Point C, nomal vector Y |
16 | fixed 1,3,12 | Point C, nomal vector Z |
16 | fixed 1,3,12 | Point D, nomal vector X |
16 | fixed 1,3,12 | Point D, nomal vector Y |
16 | fixed 1,3,12 | Point D, nomal vector Z |
Polygon texture data
This block of data is a little bit different. It has UV coordinates for each point on the textured polygons, but it also has a texture page number and palette number for the polygon as a whole. The texture page number is multiplied by 256 and then added to the V coordinate for each point on the polygon. This is because the UV coordinates are stored as bytes, but the textures are 256x1024 pixels, so effectively there are 4 texture pages. The palette number indicates which palette to apply to the polygon's texture while rendering it.
There are N triangles:
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Point A, U coordinate |
8 | uint | Point A, V coordinate |
8 | uint | Palette number |
8 | N/A | Padding/unknown |
8 | uint | Point B, U coordinate |
8 | uint | Point B, V coordinate |
6 | N/A | Padding/unknown |
2 | uint | Page number |
8 | N/A | Padding/unknown |
8 | uint | Point C, U coordinate |
8 | uint | Point C, V coordinate |
Followed by P quadrilaterals:
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Point A, U coordinate |
8 | uint | Point A, V coordinate |
8 | uint | Palette number |
8 | N/A | Padding/unknown |
8 | uint | Point B, U coordinate |
8 | uint | Point B, V coordinate |
6 | N/A | Padding/unknown |
2 | uint | Page number |
8 | N/A | Padding/unknown |
8 | uint | Point C, U coordinate |
8 | uint | Point C, V coordinate |
8 | uint | Point D, U coordinate |
8 | uint | Point D, V coordinate |
Unknown
After the polygon texture data, there's a block of data whose purpose I don't understand. Its length is equal to 4 * Q + 4 * R.
Polygon tile locations
Next comes a block that assigns a terrain coordinate to each textured polygon. Its length is equal to 2 * N + 2 * P. This data is used when the game highlights all the tiles you can move to: It looks here to figure out which polygons to turn blue.
Width (bits) | Data type | Purpose |
---|---|---|
7 | uint | Z coordinate |
1 | N/A | Height Level |
8 | uint | X coordinate |
Sometimes, there are 2 bytes of unknown data or padding at the end of this block.
Texture palettes (color)
A set of 16 palettes, each containing 16 colors. Each color in the palette is 16 bits: 5 bits each for R, G, and B, and 1 bit for A. The bits are organized like this:
Width (bits) | Data type | Purpose |
---|---|---|
1 | uint | Alpha |
5 | uint | Blue |
5 | uint | Green |
5 | uint | Red |
If R == G == B == A == 0, then the color is transparent.
Light colors and positions, background gradient colors
Each map can have 3 directional lights and ambient light. The lighting data is stored like this:
Width (bits) | Data type | Purpose |
---|---|---|
16 | fixed 1,3,12 | Light 1, red |
16 | fixed 1,3,12 | Light 2, red |
16 | fixed 1,3,12 | Light 3, red |
16 | fixed 1,3,12 | Light 1, green |
16 | fixed 1,3,12 | Light 2, green |
16 | fixed 1,3,12 | Light 3, green |
16 | fixed 1,3,12 | Light 1, blue |
16 | fixed 1,3,12 | Light 2, blue |
16 | fixed 1,3,12 | Light 3, blue |
Width (bits) | Data type | Purpose |
---|---|---|
16 | int | Light 1, X coordinate |
16 | int | Light 1, Y coordinate |
16 | int | Light 1, Z coordinate |
16 | int | Light 2, X coordinate |
16 | int | Light 2, Y coordinate |
16 | int | Light 2, Z coordinate |
16 | int | Light 3, X coordinate |
16 | int | Light 3, Y coordinate |
16 | int | Light 3, Z coordinate |
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Red |
8 | uint | Green |
8 | uint | Blue |
This is immediately followed by the background gradient colors:
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Top color, red |
8 | uint | Top color, green |
8 | uint | Top color, blue |
8 | uint | Bottom color, red |
8 | uint | Bottom color, green |
8 | uint | Bottom color, blue |
Sometimes, there are 3 bytes of unknown data or padding at the end of this chunk.
Terrain
This chunk contains data about the height, depth, slope, and surface type (grass, water, stone, etc) of each tile, as well as flags for whether you can walk on a tile or select it with the cursor.
Header
There is a two-byte header indicating the size of the X and Z dimensions of the map, in tiles. The product of X and Z must be <= 256.
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Number of tiles of terrain, X dimension |
8 | uint | Number of tiles of terrain, Z dimension |
Main Block
The main block of the terrain data is divided into 2 levels. For a map with a bridge, for instance, the bridge might be on level 0 and the water below it on level 1. Each terrain level contains Z rows of X tile definitions, which look like this:
Width (bits) | Data type | Purpose |
---|---|---|
2 | N/A | unknown/padding |
6 | uint | Surface type (grass, water, stone, etc.) |
8 | N/A | unknown/padding |
8 | uint | Height (For sloped tiles, the height of the bottom of the slope) |
3 | uint | Depth |
5 | uint | Slope height (For sloped tiles, the difference between the height at the top and the height at the bottom) |
8 | uint | Slope type |
14 | N/A | unknown/padding |
1 | uint | Can't walk on this tile |
1 | uint | Can't move cursor to this tile |
8 | N/A | unknown/padding |
These tile definitions are 8 bytes long. Each terrain level always has room for 256 tiles (i.e. each one is always 256 * 8 bytes long), even if the map doesn't use them all. In other words, the terrain chunk, including its header, is always 2 + 256 * 8 * 2 bytes long, no matter how big the map is. If the map is small, there will be a lot of padding.
Value | Meaning |
---|---|
0x00 | Flat |
0x85 | Incline N |
0x52 | Incline E |
0x25 | Incline S |
0x58 | Incline W |
0x41 | Convex NE |
0x11 | Convex SE |
0x14 | Convex SW |
0x44 | Convex NW |
0x96 | Concave NE |
0x66 | Concave SE |
0x69 | Concave SW |
0x99 | Concave NW |
Sometimes, there are 2 bytes of unknown data or padding at the end of this chunk.
Texture animation instructions
There are several entries in this block, each providing the instructions for a certain animation. Each entry has a texture region to copy from, a texture coordinate where that region should be pasted, and a delay and number of times to repeat this instruction.
Width (bits) | Data type | Purpose |
---|---|---|
8 | uint | Destination X coordinate (in 16-bit words, each of which is 4 texture pixels) |
8 | N/A | Unknown |
16 | uint | Destination Y coordinate (in pixels) |
16 | uint | Width of region to copy (in 16-bit words, each of which is 4 texture pixels) |
16 | uint | Height of region to copy (in pixels) |
8 | uint | Source X coordinate (in 16-bit words, each of which is 4 texture pixels) |
8 | N/A | Unknown |
16 | uint | Source Y coordinate (in pixels) |
24 | N/A | Unknown |
8 | uint | Number of times to repeat |
8 | N/A | Unknown |
8 | uint | Delay between animation frames (in 30ths of a second, approx.) |
16 | N/A | Unknown |
Palette animation instructions
I once had a vague idea of how this section works, but I've forgotten it all.
Texture palettes (grayscale)
These are just like the color palettes, but all the colors are gray. I don't know what purpose they serve, if any.
Mesh animation instructions
I partly understand this section. More info coming later.
Animated Meshes 1-8
Each of these chunks is very similar to the primary mesh chunk.
Polygon visibility angles
This chunk tells the game when it should and shouldn't draw each polygon. Specifically, for each polygon, there's a series of bits specifying whether it's visible from the north-northeast, northeast, east-northeast, east-southeast, southeast, and so on. This data is used to remove obstructions like walls from the the camera's view, and also to avoid drawing certain polygons that are unnecessary because they're covered up by other polygons anyway.
This chunk is always 4096 bytes long. It consists of 5 blocks:
- A block of unknown data 896 bytes long
- 1024 bytes for 512 textured triangles
- 1536 bytes for 768 textured quads
- 128 bytes for 64 untextured triangles
- 512 bytes for 256 untextured quads
The length of each block is always the same, regardless of the number of polygons the mesh actually contains. This means that there's a lot of padding for meshes with low polygon counts. It also means there's an upper limit on the number of polygons allowed in a mesh.
The visibility data for each polygon is stored in 16 bits. More on the structure of these 16 bits later.