J2A File Format

This file format is used by Jazz Jackrabbit 2 for storing the game sprites and animations. Currently, the only file that uses this format is Anims.j2a.

File structure

The ALIB Header

The file begins with this header. Note that this is not a true C/C++ structure.

struct ALIB_Header
{
    char Magic[4] = "ALIB";     // Magic number
    long Unknown1 = 0x00BEBA00; // Little endian, unknown purpose
    long HeaderSize;            // Equals 464 bytes for v1.23 Anims.j2a
    short Version = 0x0200;     // Probably means v2.0
    short Unknown2 = 0x1808;    // Unknown purpose
    long FileSize;              // Equals 8182764 bytes for v1.23 anims.j2a
    long CRC32;                 // Note: CRC buffer starts after the end of header
    long SetCount;              // Number of sets in the Anims.j2a (109 in v1.23)
    long SetAddress[SetCount];  // Each set's starting address within the file
}

Right after the ALIB header, comes the first of many sets. However, to be more technical, we’ll call them ANIM sub-files. In addition, these ANIM sub-files each contain 4 sub-sub-files, which are, in order, Animation Info, Frame Info, Image Data, Sample Data. For simplicity, we will think of them as buffers and call them Data1 to Data4. The data sections are compressed using zlib.

Each ANIM sub-file starts with a 44-byte header like this:

struct ANIM_Header
{
    char Magic[4] = "ANIM";         // Magic number
    unsigned char AnimationCount;   // Number of animations in set
    unsigned char SampleCount;      // Number of sound samples in set
    short FrameCount;               // Total number of frames in set
    long PriorSampleCount;             // Total number of sound sample across all sets preceding this one
    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
    char Palette[1024];           // Only present in Melk
}

After that you should see your familiar zlib stream that starts with 78 DA. As soon as that stream ends the next one begins, until the end of Data4’s stream. That’s where the next set starts and the next ANIM header appears.

Data1 (Animation Info)

Assuming you have already uncompressed this, you will get a buffer of (UData1) bytes, which also happens to be (8 * AnimationCount). This is because each animation info is only 8 bytes long:

struct AnimInfo
{
    short FrameCount;   // Number of frames for this particular animation
    short FPS;          // Most likely frames per second
    long Reserved;      // Used internally by Jazz2.exe
}

Should be pretty straightforward, no explanation required.

Data2 (Frame Info)

Same as above, uncompress, and discover that UData2 = 24 * FrameCount. Each frame info is 24 bytes long:

struct FrameInfo
{
    short Width;
    short Height;
    short ColdspotX;    // Relative to hotspot
    short ColdspotY;    // Relative to hotspot
    short HotspotX;
    short HotspotY;
    short GunspotX;     // Relative to hotspot
    short GunspotY;     // Relative to hotspot
    long ImageAddress;  // Address in Data3 where image starts
    long MaskAddress;   // Address in Data3 where mask starts
}

An important note here is that the hotspot location is not relative to the frame. Instead, the frame is drawn relative to the hotspot. This means that hotspotx and hotspoty are usually negative.

Data3 (Image Data)

By now uncompressing it should be a breeze. But how do you use the info contained in here? JJ2 uses its own format (or maybe one that already exists, but I don’t recognize), which is similar to RLE. The first four bytes contain width and height, although the most significant bit of the width short will be set for sprites that are normally drawn semi-transparently, such as the Rapier enemy’s sprites.

After those 4 bytes, try to read the image data as codebytes. codebyte < 128 (0x80) means to skip that many pixels. codebyte > 128 (0x80) means to read and set that many pixels from the buffer onto the image. codebyte = 128 (0x80) means to skip to the next row. Note that the overall image data is aligned to 8 bytes, so there can be up to 7 bytes of unused bytes between the current frame’s image data and the next one.

You probably already realized that the image is 8-bits, so get a palette from some random level to start with. Anyway, the mask data (used for determining whether two sprites are overlapping one another) is also in this Data3, and is 2-bit (black or white), and aligned to 4 bytes.

Data4 (Sample Data)

Example of using Anims.j2a

Just a very basic example, we’ll use the first frame of the first animation of the first set for this tutorial. This assumes that you have already decompressed the first ANIM sub-file into Data1, Data2 and Data3 buffers.

Data1 (Animation Info)

We will just look at the first animation (8 bytes):

07 00 0A 00-00 00 00 00

The first 07 00 means it has 7 frames in the animation (first 7 * 24 bytes in Data2) and the 0A 00 stands for 10 something (I assumed FPS). Straightforward.

Data2 (Frame Info)

We shall just look at the first frame defined in this buffer (in this case, the first frame of the first animation), 24 bytes long:

11 00 10 00 00 00 00 00-F7 FF F5 FF 00 00 00 00
00 00 00 00 D0 05 02 00

The first 11 00 means a width of 17 pixels, 10 00 a height of 16 pixels. The 00 00 and 00 00 after it would refer to the coldspot’s x and y, except a zero means that none is defined.

The F7 FF which converts to -9 in a signed short, meaning that the frame starts drawing 9 pixels to the left of the hotspot. Similarly the F5 FF means the frame starts drawing 11 pixels above.

The next four zeros obey the same rule as the coldspot.

The four zeros after that indicate the starting address of the image within Data3 (image data). Note that this address is aligned to 8 bytes (i.e. is always a multiple of 8).

Finally the last four zeros indicate the starting address (in Data3) of the mask for this frame. This one is also aligned to 8 bytes. If the mask address is given as -1 (FF FF FF FF), then there is no mask given for it.

Data3 (Image Data)

Yay, we’ve come to the really fun part. It is slightly technical but not very difficult. If you are already familiar with RLE this concept should be a breeze. Remember we had an image address of 00000000, so we will read address 0 of Data3:

11 80 10 00 06 81 23 80-03 81 24 02 82 25 24 80
81 25 01 83 25 23 25 02-82 23 25 80 85 24 23 24
22 24 02 81 25 80 85 23-24 23 21 23 01 81 25 80
88 24 25 23 40 22 25 22-25 80 89 2f ...

As mentioned before the first 4 bytes of the image data for the frame is the width and height (again), except that the most significant bit of the width serves as a flag to indicate that JJ2 should draw this image semi-transparently. So after the 4 bytes, the image begins, as a series of codebytes. Refer to previous post(s) for more details, but you could imagine it as being rearranged like this:

06 81 23 80                         // Skips 6 bytes, then copies one byte, then NEXT
03 81 24 02 82 25 24 80             // Skips 3 bytes, copies 1, skips 2, copies 2
81 25 01 83 25 23 25 02 82 23 25 80
85 24 23 24 22 24 02 81 25 80
85 23 24 23 21 23 01 81 25 80
88 24 25 23 40 22 25 22 25 80
89 2f ...

And you’ll end up with something looking like this:

Note: I included a palette for convenience. But basically, those few bytes we just went through would have drawn the first 6 rows of the “flame”.

Related content outside the wiki

Category: