TMP TS Format


Copyright

Copyright (C) 2000 Olaf van der Spek

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Introduction

Written by Olaf van der Spek. This document explains the format of the TMP TS files used by Command & Conquer: Tiberian Sun.
TMP TS files are used to store templates, used as the terrain in TS maps. I will use C++ notation in this document.

typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;

Header

A TMP TS file consists of two parts, a header and a body. The header stores information about the size of the template.

struct t_tmp_ts_header
{
    __int32 cblocks_x;                  // width of blocks
    __int32 cblocks_y;                  // height in blocks
    __int32 cx;                         // width of each block, always 48
    __int32 cy;                         // height of each block, always 24
};
Directly after that, there is an index of the tiles. This index consists of c_files __int32 entries. When such an entry is zero, there is no tile. Otherwise, it's an offset from the start of the file and points to a TMP TS image header.

Body

The body contains the tiles. First, you have the TMP TS image header. The positions can be negative.

struct t_tmp_image_header
{
    __int32 x;                          // position
    __int32 y;
    __int32 unknown1[3];
    __int32 x_extra;                    // position of extra graphics
    __int32 y_extra;
    __int32 cx_extra;                   // size of extra graphics
    __int32 cy_extra;
    __int32 unknown2[4];
};
The normal graphics of a tile are horizontal. The (optional) extra graphics of a tile are vertical. If cx_extra is between 0 and 256, the extra graphics are present.
The normal graphics can be found directly after the TMP TS image header. It's a 576 byte block. Because TS uses an isometric grid, the tile is not rectangular. The first line has 4 pixels, the second line has eight pixels. Line 12 has 48 pixels, line 13 has 44 pixels and line 22 has 4 pixels.
After the normal graphics, there's another 576 byte block. This block probably contains height data.
Then you have the extra graphics (if present). They're rectangular, so it's a block of cx_extra * cy_extra bytes. They should be drawn at x_extra,y_extra.

Drawing

You can use the following code to determine how large the template is. The position relative to the first tile is given via x and y. The size of the template is given via cx and cy.

void Ctmp_ts_file::get_rect(int& x, int& y, int& cx, int& cy) const
{
    x = INT_MAX;
    y = INT_MAX;
    cx = INT_MIN;
    cy = INT_MIN;
    for (int i = 0; i < get_c_tiles(); i++)
    {
        if (get_index()[i])
        {
            int x_t = get_x(i);
            int y_t = get_y(i);
            int x2_t = x_t + 48;
            int y2_t = y_t + 24;
            if (has_extra_graphics(i))
            {
                int y_t_extra = get_y_extra(i);
                int y2_t_extra = y_t_extra + get_cy_extra(i);
                if (y_t_extra < y)
                    y = y_t_extra;
                if (y2_t_extra > cy)
                    cy = y2_t_extra;
            }
            if (x_t < x)
                x = x_t;
            if (x2_t > cx)
                cx = x2_t;
            if (y_t < y)
                y = y_t;
            if (y2_t > cy)
                cy = y2_t;
        }
    }
    cx -= x;
    cy -= y;
}
The following code can be used to actually draw the template. It assume you have already create a memory area, big enough to contain the template. This function also draws the extra graphics.
void Ctmp_ts_file::draw(byte* d) const
{
    int global_x, global_y, global_cx, global_cy;
    get_rect(global_x, global_y, global_cx, global_cy);
    memset(d, 0, global_cx * global_cy);
    for (int i = 0; i < get_c_tiles(); i++)
    {
        if (get_index()[i])
        {
            const byte* r = get_image(i);
            byte* w_line = d + get_x(i) - global_x + global_cx * (get_y(i) - global_y);
            int x = 24;
            int cx = 0;
            for (int y = 0; y < 12; y++)
            {
                cx += 4;
                x -= 2;
                memcpy(w_line + x, r, cx);
                r += cx;
                w_line += global_cx;
            }
            for (; y < 23; y++)
            {
                cx -= 4;
                x += 2;
                memcpy(w_line + x, r, cx);
                r += cx;
                w_line += global_cx;
            }
            if (has_extra_graphics(i))
            {
                r += 576;
                w_line = d + get_x_extra(i) - global_x + global_cx * (get_y_extra(i) - global_y);
                int cx = get_cx_extra(i);
                int cy = get_cy_extra(i);
                for (y = 0; y < cy; y++)
                {
                    byte* w = w_line;
                    for (int i = 0; i < cx; i++)
                    {
                        int v = *r++;
                        if (v)
                            *w = v;
                        w++;
                    }
                    w_line += global_cx;
                }
            }
        }
    }
}
This code assumes that the entire file is present in memory. It uses a number of functions that return pointers to certain locations in the file.
class Ctmp_ts_file: public Ccc_file_sh<t_tmp_ts_header>
{
public:
    int get_c_tiles() const
    {
        // returns the number of tiles
        return get_cblocks_x() * get_cblocks_y();
    }

    int get_cblocks_x() const
    {
        // returns a value from the header
        return get_header()->cblocks_x;
    }

    int get_cblocks_y() const
    {
        // returns a value from the header
        return get_header()->cblocks_y;
    }

    const t_tmp_image_header* get_image_header(int i) const
    {
        // returns an image header
        return reinterpret_cast(get_data() + get_index()[i]);
    }

    int get_x(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->x;
    }

    int get_y(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->y;
    }

    int get_x_extra(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->x_extra;
    }

    int get_y_extra(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->y_extra;
    }

    int get_cx_extra(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->cx_extra;
    }

    int get_cy_extra(int i) const
    {
        // returns a value from an image header
        return get_image_header(i)->cy_extra;
    }

    bool has_extra_graphics(int i) const
    {
        return get_image_header(i)->cx_extra > 0 && get_image_header(i)->cx_extra < 256;
    }

    const byte* get_image(int i) const
    {
        // returns a pointer to the start of an image
        return reinterpret_cast(get_image_header(i) + 1);
    }

    const int* get_index() const
    {
        // returns the index
        return reinterpret_cast(get_data() + sizeof(t_tmp_ts_header));
    }
};