J2L File Format

This is the format used by levels in Jazz Jackrabbit 2.

The LEVL Header

Here is a more comprehensible C struct. “Version” is 514 for 1.23, 1.10o, and Battery Check levels, 515 for TSF levels, and 256 for A Gigantic Adventure levels.

struct LEVL_Header
    char Copyright[180];
    char Magic[4] = "LEVL";
    char PasswordHash[3]; // 0xBEBA00 for no password
    char HideLevel;
    char LevelName[32];
    short Version;
    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

The header is followed by 4 zlib streams. Data1 contains general level data, Data2 contains events, Data3 is the dictionary of “8-byte” tile groups (4 tiles per group), and Data4 contains “words”, which it takes from the dictionary and outputs as proper level data.

Data1 (General Level Data)

MAX_TILES is 1024 for 1.23 and 1.10o/Battery Check levels, and 4096 for TSF and A Gigantic Adventure levels.

struct J2L_Data1
    short JCSHorizontalOffset; // In pixels
    short Security1; // 0xBA00 if passworded, 0x0000 otherwise
    short JCSVerticalOffset; // In pixels
    short Security2; // 0xBE00 if passworded, 0x0000 otherwise
    char SecAndLayer; // Upper 4 bits are set if passworded, zero otherwise. Lower 4 bits represent the layer number as last saved in JCS.
    char MinLight; // Multiply by 1.5625 to get value seen in JCS
    char StartLight; // Multiply by 1.5625 to get value seen in JCS
    short AnimCount;
    bool VerticalSplitscreen;
    bool IsLevelMultiplayer;
    long BufferSize;
    char LevelName[32];
    char Tileset[32];
    char BonusLevel[32];
    char NextLevel[32];
    char SecretLevel[32];
    char MusicFile[32];
    char HelpString[16][512];
    char SoundEffectPointer[48][64]; // only in version 256 (AGA)
    long LayerMiscProperties[8]; // Each property is a bit in the following order: Tile Width, Tile Height, Limit Visible Region, Texture Mode, Parallax Stars. This leaves 27 (32-5) unused bits for each layer?
    char "Type"[8]; // name from Michiel; function unknown
    bool DoesLayerHaveAnyTiles[8]; // must always be set to true for layer 4, or JJ2 will crash
    long LayerWidth[8];
    long LayerRealWidth[8]; // for when "Tile Width" is checked. The lowest common multiple of LayerWidth and 4.
    long LayerHeight[8];
    long LayerZAxis[8] = {-300, -200, -100, 0, 100, 200, 300, 400}; // nothing happens when you change these
    char "DetailLevel"[8]; // is set to 02 for layer 5 in Battle1 and Battle3, but is 00 the rest of the time, at least for JJ2 levels. No clear effect of altering. Name from Michiel.
    int "WaveX"[8]; // name from Michiel; function unknown
    int "WaveY"[8]; // name from Michiel; function unknown
    long LayerXSpeed[8]; // Divide by 65536 to get value seen in JCS
    long LayerYSpeed[8]; // Divide by 65536 to get value seen in JCSvalue
    long LayerAutoXSpeed[8]; // Divide by 65536 to get value seen in JCS
    long LayerAutoYSpeed[8]; // Divide by 65536 to get value seen in JCS
    char LayerTextureMode[8];
    char LayerTextureParams[8][3]; // Red, Green, Blue
    short AnimOffset; // MAX_TILES minus AnimCount, also called StaticTiles
    long TilesetEvents[MAX_TILES]; // same format as in Data2, for tiles
    bool IsEachTileFlipped[MAX_TILES]; // set to 1 if a tile appears flipped anywhere in the level
    char TileTypes[MAX_TILES]; // translucent=1 or caption=4, basically. Doesn't work on animated tiles.
    char "XMask"[MAX_TILES]; // tested to equal all zeroes in almost 4000 different levels, and editing it has no appreciable effect.  // Name from Michiel, who claims it is totally unused.
    char UnknownAGA[32768]; // only in version 256 (AGA)
    Animated_Tile Anim[128]; // or [256] in TSF.
                             // only the first [AnimCount] are needed; JCS will save all 128/256, but JJ2 will run your level either way.
    char Padding[512]; //all zeroes; only in levels saved with JCS

The animated tiles are 137 bytes long each and have the following structure:

struct Animated_Tile
    short FrameWait;
    short RandomWait;
    short PingPongWait;
    bool PingPong;
    char Speed;
    char FrameCount;
    short Frame[64]; // this can be a flipped tile or another animated tile

Data2 (Event Map)

Each event is a long (4 bytes long), so this buffer should be (Layer4Width * Layer4Height * 4) bytes long. The first byte is the Event Number, and the next three bytes make up an extended bitfield that houses all the parameters for the events. The first four bits of that bitfield are reserved for Difficulty, Illuminate Surroundings, and (in-game and in save files only) whether the object is active, and the other twenty are up for grabs for event-specific code.
Event ID: First 8 bits
Difficulty: Next 2 bits
Illuminate: Next 1 bit.
Is Active: Next 1 bit.
Parameters: The rest 20 bits

Use JCS.ini to get the numbers of parameters and their offsets.
Generator-events are events with ID 216.

Data2 (Event Map, AGA Style)

In levels of version 256, Data2 has a very different format. There are two major sections. First the file lists every event that is used in the level, each one padded out with null bytes to be sixty-four bytes long.

struct Data2AGAPart1
    short NumberOfDistinctEvents;
    string Event[NumberOfDistinctEvents][64];

The strings each have two parts, a filename before the slash ‘\’ and a sprite name after the slash. The filename is literally the name of a .res/.bres file pair, and the sprite name is an individual set of sprites stored within that .res file. For instance, the Chicken food pickup is written as Goodies\Chicken (plus 64-15 = 49 null bytes), meaning that the level includes the Chicken event stored in Goodies.res.

After that are the actual events. Unlike in later versions of .j2l files, each event includes its position in layer 4. There does not seem to be any explicit statement of how many events there are in the level, so a reader must simply know to stop at the end of the section.

struct AGAEvent
    short XPos;
    short YPos;
    short EventID;
    long Marker;
    //the rest of the structure is only included if the highest bit of Marker is set.
    long LengthOfParameterSection; //including its own four bytes
    short AreThereStrings; //02 if yes, 00 otherwise?
    short NumberOfLongs;
    long Parameter[NumberOfLongs*2];
    AGAString [???]; //I guess it just keeps looking for strings until it hits the LengthOfParameterSection length?
struct AGAString
    long StringLength; //including null byte
    string String; //ends with a null byte

Note that the “EventID” does not refer to a universal list of events like in JJ2 (as seen in JCS.ini). Rather, it refers to the list of events used in the level from the first part of Data2. So if one level has Goodies/Chicken as its first listed event, and another as its second, the first will use 00 00 and the second 01 00 for the EventID.

The purpose of the Marker long is so far unknown, although it seems to be a series of bit booleans. If the highest bit is set, then the event has parameters, which follow it; the only other bits that are ever set are 5-14 (32-16385). A given event ID does not always correspond to the same bits being set among those ten.

Similarly, although most instances of the same event have the same number of parameters, this is not always true. At a guess, if the two last longs are both blank, they will not be saved.

Data3 (Dictionary)

This is probably an unusual way of defining a buffer, but I use dictionary because it contains a lot of “words”, which cannot be used individually but have to be stringed together by Data4 to create something meaningful.

The size of this buffer is (WordCount * 8), but I’m not sure if the number of words is actually defined in the file, so I use UData3 instead. Each word contains 4 “Tiles”, and each Tile is a short which corresponds to that tile index in the J2T declared. If a Tile has its 0x1000 (4096) bit (TSF) or 0x400 (1024) (1.23) bit set, it is a flipped tile.

As an example, Battle1.j2l has a dictionary that begins like this:

00 00 00 00 00 00 00 00-00 00 00 00 51 00 0F 00
0F 00 0F 00 0F 00 0F 00-0F 00 0F 00 0F 00 0E 00

This probably doesn’t make much sense, but it defines the first 4 words in the dictionary:

word[0] = {0, 0, 0, 0} <-- an empty tile group, word[0] must always be this
word[1] = {0, 0, 81, 15} <-- contains two empty tiles, then two bricks
word[2] = {15, 15, 15, 15} <-- 4 bricks
word[3] = {15, 15, 15, 14} <-- 4 bricks, the 4th one using a different tile

Note that because it can use shorts, there can be a maximum of 65535 words (in theory). Each word is unique, so it usually a LOT less words, if similar tilegroups are used all over the level. This concept should not be difficult if you are already familiar with LWZ.


This section maps the Tiles in the previous section to the layers. Words are only defined for LayersThatHaveAnyTiles (refer to data1). For example, battle1.j2l again: the first layer that is “defined” is layer 3, which is 128×128. The width is 128, so that means it reads 32 words per row (remember there are 4 tiles per word). Note that it rounds up, so a 124 width would read 31 words per row, but 125 would be 32.

The first 32*128 words belong to layer 3, the next 32*128 words to layer 4, and the last 15*12 words to layer 8 (15 because its 60\4).

(If a layer has Tile Width checked, then the data are saved with respect to RealWidth, not Width, and so all the tiles are repeated up to three times in order to fill a space divisible into blocks of four tiles.)

Since each reference to a word is 2 bytes long, we should have 8372 words in Data4, thus a buffer size 16744. So basically, this is just a sort of “mapping” done which copies and paste those “words” in the dictionary onto a larger screen.

Related content outside the wiki