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.
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.
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.
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.
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.
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.
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.
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.
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”.
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.