J2T File Format

J2T is the format used by Jazz Jackrabbit 2 tilesets.

File header

The data sections are compressed with zlib.

struct TILE_Header {
    char Copyright[180];
    char Magic[4] = "TILE";
    long Signature = 0xAFBEADDE
    char Title[32];
    short Version;  //0x200 for v1.23 and below, 0x201 for v1.24
    long FileSize;
    long CRC32;
    long CData1;    //compressed size of Data1
    long UData1;    //uncompressed size of Data1
    long CData2;    //compressed size of Data2
    long UData2;    //uncompressed size of Data2
    long CData3;    //compressed size of Data3
    long UData3;    //uncompressed size of Data3
    long CData4;    //compressed size of Data4
    long UData4;    //uncompressed size of Data4
}

Data1 (Tileset Info)

If the set is from 1.23 or 1.10o/Battery Check, then MAX_TILES is 1024 and the size of the buffer is 27652. If it’s from 1.24 or A Gigantic Adventure, then MAX_TILES is 4096 and the size of the buffer is 107524.

struct TilesetInfo {
    long PaletteColor[256];        //arranged RGBA
    long TileCount;                 //number of tiles, always a multiple of 10
    char FullyOpaque[MAX_TILES];    //1 if no transparency at all, otherwise 0
    char Unknown1[MAX_TILES];       //appears to be all zeros
    long ImageAddress[MAX_TILES];
    long Unknown2[MAX_TILES];       //appears to be all zeros
    long TMaskAddress[MAX_TILES];   //Transparency masking, for bitblt
    long Unknown3[MAX_TILES];       //appears to be all zeros
    long MaskAddress[MAX_TILES];    //Clipping or tile mask
    long FMaskAddress[MAX_TILES];   //flipped version of the above
}

The Address fields refer to the address or offset in their corresponding buffers from which to start reading.

Specifically, ImageAddress reads from Data2, TMaskAddress reads from Data3, MaskAddress and FMaskAddress reads from Data4, and you should probably memorise this to avoid confusion later on. If you are planning to make a tileset extractor of some sort, you would probably need only to use ImageAddress and MaskAddress.

Data2 (Image Address)

Each image is 1024 bytes long, uncompressed. Each byte represents a palette index, so you have a 32*32 picture just by reading each byte as a pixel. Note that each image here is unique, so for example if your tileset has a blank space in say, tile #42, then ImageAddress[42] will be equal to zero (offset zero in the Data2 buffer).

struct ImageAddress
{
    char Tiles[TileCount][1024];
};

Data3 (Transparency Mask)

Each mask is 128 bytes (a bit for each pixel), followed by another redundent descriptor as follows:

struct TransparencyMask
{
    struct Mask
    {
        struct MaskRow
        {
            char OpaqueCount;
            char TilesGroups[OpaqueCount*2];
        };
        char MaskMap[128];
        MaskRow Rows[32];
    };
    Mask TranspMasks[TileCount];
};

This descriptor describes each row by first giving in one byte the number of opaque sequences of pixels on the row, and then giving a byte which says how many transparent pixels there is first, then the number of opaque pixels on the row which are consecutive, etc.

Data4 (Clipping Mask)

Similar to Data2, except each mask is 128 bytes. This is because the mask is only 1 bit-per-pixel (black or white). If you are trying to extract mask information into a picture for the first time, expect some problems with bit orders; I can’t remember whether it reads MSB-first or LSB-first.

struct ClippingMask
{
    char TilesMasks[TileCount][128];
};

Notes

Remember that Data2 is not equivalent to the actual tileset graphics as seen in JCS, and Data4 is not the mask either. Data2 simply contains an image of each unique tile in the set, likewise with Data4. You must read Data1 if you want to find out exactly what image and mask a particular tile # has.

Related content outside the wiki

Category: