The J2L File Format
I had never really looked into the header with that much detail, so some parts here were stolen from another post, at least for the LEVL Header. This is mostly for stuff I've never thought about, like "Hide level in Home Cooked List" or passworded levels.
The LEVL Header
Ok, without further ado let's rewrite that as a more comprehensible struct:
Code:
struct LEVL_Header {
char Copyright[180];
char Magic[4] = "LEVL";
char PasswordHash[3]; //never really thought about this
char HideLevel;
char LevelName[32];
short Version; //I only looked at one file, had a value of 0x202
long FileSize;
long CRC32;
long CData1;
long UData1;
long CData2;
long UData2;
long CData3;
long UData3;
long CData4;
long UData4;
}
The header is followed by 4 zlib streams, and by now decompressing should be a piece of cake. 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. Confusing so far? Data3 and Data4 will take a while, so we'll go through the easier ones first (1 and 2 are in order anyway =/).
Data1 - General Level Data
Like everything else I've never had a need for research, I haven't looked into this buffer much. Someone else should probably update this. I've only looked at a few 1.23 files, and I think this has size 33517 (0x82ED) for the official levels, or 34029 (0x84ED) for levels saved in JCS.
Code:
struct J2L_Data1 {
Oh dear, I have no idea what the first 19 bytes are, so I'll assume
char Unknown[7];
long CRC32;
long Unknown;
long BufferSize;
char LevelName[32];
char Tileset[32];
char BonusLevel[32];
char NextLevel[32];
char SecretLevel[32];
char MusicFile[32];
char HelpString[16][512]; //I'd rather call it "Text"
//Looking back, I actually don't know a lot of stuff :(
}
I'd assume the rest of Data1 contains individual layer headers such as the width, the height, the pitch, (auto)x-speed, (auto)y-speed, and those other flags you see in JCS. The most important thing you'll need to look for here is the layer4 width and height, since that's the only one I've ever used anyway.
Try poking into the buffer at offset 0x20FB and you'll read something like:
Code:
struct VitalLayerInfo {
char DoesLayerHaveAnyTiles[8];
long LayerWidth[8];
long LayerWidthRelated[8]; //Not sure, but its similar to width?
long LayerHeight[8];
}
The rest remains a mystery
. I'd assume (quite sure actually) that animating tiles would be somewhere in here, which explains it consuming a large space with mostly zeros.
Data2 - The Event Map
Each event is a
long (4 bytes long), so this buffer should be (Layer4Width * Layer4Height * 4) bytes long. So, reading an event from a coordinate in the level shouldn't be too difficult at all. The hard(er) part would be to parse the events. But since each event has a different set of arguments, I'll leave this as an exercise for the reader.
Data3 - The 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 set, it is a flipped tile. There are probably other flags for translucent or caption, but I haven't gone into detail with these. As an example, battle1.j2l has a dictionary that begins like this:
Code:
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.
Data4 - Taking words and stringing them together
Okay... so that's not really a proper name for Data4, but it describes what it does. Words are only defined for LayersThatHaveAnyTiles (refer to data2). For example, battle1.j2l again: the first layer that is "defined" is layer 3, which is 128x128. 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.
Slight correction: this part uses info from the LayerWidthRelated defined above. Or at least that's my assumption, because layer 8 pretends it has width 60 rather than the 30 we see in JCS? I don't really get it lol.
Enough random chitchat
, so 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, dunno why it didn't use the 30 to get 8 words per row. Could have something to do with tile width/height, or I'm just completely wrong.)
Since each 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.