This is the format used by levels in Jazz Jackrabbit 2.
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.
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 }
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.
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.
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.
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.