PDA

View Full Version : JJ2 File Format Specifications


Neobeo
Apr 2, 2006, 12:46 PM
Edit: I just slipped this tileset extractor (http://www.jazz2online.com/J2Ov2/downloads/info.php?levelID=4087) on top so people can see it.

Well, I was working on a file-format entry for the ERE, but the formatting never came out the way I wanted it. Seeing as I'm in somewhat of a rush, I've decided to dump my article here, in the hopes that someone can move it to ERE or some other public article.

Zlib Compression in JJ2
This article will give you a crash course to understanding zlib, how it can be compressed / decompressed and how it is implemented in JJ2. You need either zlib.lib and zlib.h if you’re programming for C++, or zlib.dll if you’re programming for Visual Basic. Armed with this knowledge alone, it is possible to decrypt JJ2 files into their raw formats, and then figure out the formats from there.

Declaring zlib functions
Just about every programming language requires you to declare a function before you can use it. Notes about declares: The <samp>compress</samp> function will not be used at all, and is only provided for closure. This is because it is superseded by <samp>compress2</samp>, which allows compression at higher levels. Also, if you do not care about the integrity of your files, you can ignore the <samp>crc32</samp> function and skip all crc checks. Without further ado:

Declares in C++
#include &lt;zlib.h&gt;

Declares in VB
Don’t forget to add "Private"s in front of them if you’re going to use them in your forms.
Declare Function compress Lib "zlib.dll" (ByRef dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long) As Long
Declare Function compress2 Lib "zlib.dll" (ByRef dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long, ByVal Level As Integer) As Long
Declare Function uncompress Lib "zlib.dll" (ByRef dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long) As Long
Declare Function crc32 Lib "zlib.dll" (ByVal crc As Long, ByRef buffer As Any, ByVal bufferLen As Long) As Long

How compression is used in JJ2 files
As a bare minimum, all zlib-compressed files in JJ2 files should at least provide the compressed and uncompressed sizes in its header. For example, consider an example zlib section of a file which goes:
<table><tr><td>
<samp>0000 </samp>
<samp>0010</samp>
<samp>0020</samp>
<samp>0030</samp>
<samp>0040</samp>
</td><td>
<samp>44 00 00 00 4F 00 00 00 78 DA E3 60 80 00 17 73 </samp>
<samp>69 30 ED F0 53 98 41 E0 97 30 03 23 90 DD 92 53</samp>
<samp>C6 78 E7 87 30 C3 7B 31 47 06 35 89 03 FC 20 F9</samp>
<samp>04 0A DC DB 3C 07 B0 5A 01 55 39 06 95 04 25 86</samp>
<samp>2B 40 1A 0C 9E 30 00 00 2B 02 0E A6</samp>
</td><td>
<samp>D...O...X..`...^</samp>
<samp>%0..S.A..0.#...S</samp>
<samp>.x..0.{1G.5... .</samp>
<samp>....<..Z.U9...%.</samp>
<samp> +@...0...Q.[</samp>
</td></tr></table>

In this self-made example, the first four bytes (which reads 68 in hex) represents the size of the compressed stream (the input data). The next four bytes (which reads 79 in hex) represents the size of the uncompressed stream (the output data). Armed with that data, we now know that we will be decompressing a 68-byte buffer into a 79-byte one. Note that zlib streams starts with a "78 DA" when compressed at the maximum level, and this can be very useful for skimming through large files to find out where a zlib stream begins or how many zlib streams there are.

In case you are already confused, a zlib stream is basically an array of bytes which contains compressed data, and is very unfriendly to the human eye. Fortunately, there is a very simple function which can be used to decompress a zlib stream into a buffer. This function is uncompress.

Using uncompress in C++
For this short snippet we will assume that you have the above "example" file stored as "tutorial.dat". This assumes you are writing a console program and have already #included anything necessary.


char *in;
char *out;
int CompressedSize;
int UncompressedSize;

int main() {
FILE *fi = fopen("tutorial.dat", "rb"); //Opens tutorial.dat for binary reading
fread(&CompressedSize, 4, 1, fi); //Reads the first four bytes and stores it
fread(&UncompressedSize, 4, 1, fi); //Reads the next four bytes and stores it

in = malloc(CompressedSize); //Allocates a size of CompressedSize for input stream
fread(in, 1, CompressedSize, fi); //Reads CompressedSize bytes from the file
fclose(fi); //We finished reading the file, no need to use it anymore

out = malloc(UncompressedSize); //Allocates a size of UncompressedSize for output
uncompress(out, &UncompressedSize, in, CompressedSize); //Decompresses, YAY :)

//Here "out" contains the uncompressed data, so do whatever you want with it.
//For simplicity, the above example is actually a string, so print it to screen:
printf(out);
}

That wasn’t too bad was it? Once you can understand the concepts of malloc and uncompress, you can decompress just about every file format in JJ2. Don’t forget to free what you malloc though. Note that we pass a pointer to the second argument of uncompress, which will return with the uncompressed size of the buffer. Assuming you already had the right size in the first place, this value will not change. Also, the return value of uncompress determines whether the operation was a success or not. However, to keep things simple, we assume that our zlib stream is valid, and will not error-handle it. Note: I would usually prefer to use gzread(look it up on google), but chose uncompress because it is compatible with VB.

Using uncompress in VB
Here we use the same assumptions: a file tutorial.dat with containing that binary data.


Dim inBuffer() As Byte
Dim outBuffer() As Byte
Dim CompressedSize As Long
Dim UncompressedSize As Long

Sub Main()
Open "tutorial.dat" For Binary Access Read As #1 'Opens tutorial.dat for reading
Get #1, , CompressedSize 'Reads the first four bytes, stores in CompressedSize
Get #1, , UncompressedSize 'Reads the next four bytes, stores in UncompressedSize

ReDim inBuffer(CompressedSize - 1) 'Create buffer of CompressedSize bytes
Get #1, , inBuffer 'Reads bytes into inBuffer
Close #1 'Finish reading from file, no need to use it anymore

ReDim outBuffer(UncompressedSize - 1) 'Create buffer of UncompressedSize bytes
Uncompress outBuffer(0), UncompressedSize, inBuffer(0), CompressedSize 'YAY :)

'Here "outBuffer" contains the uncompressed data, so do whatever you want with it.
End Sub

The VB function actually looks slightly harder than the C++ version. One thing to note is that ReDim uses "x-1" bytes when you want to allocate x bytes, because the array goes from buffer(0) to buffer(x-1).

Neobeo
Apr 2, 2006, 12:48 PM
Compressing files
Overall, uncompressing shouldn’t be too difficult, since you already know both the exact compressed AND decompressed size. Trying to compress data however, is more difficult, because you don’t know how big the new zlib stream will be. However, the zlib manual states that the compressed size of a buffer will be at most (1.01 * size + 12) bytes big, regardless of compression level. Here we will use compress2, using a compression level of 9 (maximum).

Using compress2 in C++
For this short snippet we will assume that the in buffer already contains a string called "Let us...". We will compress this into zlib and turn it into a file that is similar to JJ2's file formats.


//char *in;
char in[] = "Let us attempt to compress this string";
char *out;
int CompressedSize;
int UncompressedSize;

int main() {
UncompressedSize = sizeof(in);
CompressedSize = (int)((float)(UncompressedSize * 1.01) + 12); //Allocate space

out = malloc(CompressedSize); //Allocates a size of UncompressedSize for output
compress2(out, &CompressedSize, in, UncompressedSize, 9); //Compresses, YAY :)
//Note that CompressedSize now holds the real uncompressed size

//Here "out" contains the compressed data, so do whatever you want with it.
//For our example, we will write this to testcompress.dat
FILE *fo = fopen("testcompress.dat", "wb"); //Opens testcompress.dat for writing
fwrite(out, 4, 1, &CompressedSize); //Writes the compressed size
fwrite(out, 4, 1, &UncompressedSize); //Writes the uncompressed size
fwrite(out, 1, CompressedSize, fo); //Writes compressed stream to file
fclose(out); //We finished writing to the file, no need to use it anymore
}

Again, if you are implementing this, remember to #include, and don’t forget to free what you malloc. If you are fussy and want your out buffer to be the right size, use realloc to adjust it.

Using compress2 in VB
Same thing as above, string to zlib, write to testcompress.dat.


'Dim inBuffer() As Byte
Dim inBuffer As String
Dim outBuffer() As Byte
Dim CompressedSize As Long
Dim UncompressedSize As Long

Sub Main()
inBuffer = "Let us attempt to compress this string"
UncompressedSize = Len(inBuffer) + 1 'Add a null-char to the end of the string
CompressedSize = UncompressedSize * 1.01 + 12 'Sets maximum output size

ReDim outBuffer(CompressedSize - 1) 'Allocates output space
compress2 outBuffer(0), CompressedSize, inBuffer, UncompressedSize, 9 'YAY :)
ReDim Preserve outBuffer(CompressedSize - 1) 'Adjusts output space

'Here "outBuffer" contains the uncompressed data, so do whatever you want with it.
'For out example, we’ll just do something simple - dump it into testcompress.dat
Open "testcompress.dat" For Binary Access Write As #1 'Opens file for writing
Put #1, , CompressedSize 'Writes CompressedSize to the first four bytes
Put #1, , UncompressedSize 'Writes UncompressedSize to the next four bytes
Put #1, , outBuffer 'Writes the output buffer to the file
Close #1 'Finish writing to file, no need to use it anymore
End Sub

Again, the Visual Basic function looks slightly harder than C++. (inserts rant about how everyone using VB should start using VC++) Only thing to note is the use of ReDim Preserve. Remember how the UncompressedSize value changes after going through compress2? We adjust the buffer so that it fits exactly the compressed stream, and prevent any problems with Put #1, , outBuffer.

CRC Checking

Finally we get to the crc32 stage. All (or most) of the JJ2 files contain an internal (and some even external) CRC check to verify that the file is not corrupted in any way. Seeing how most programmers are lazy, they will probably skip the CRC mechanism and just assume that file is valid, but it will be included here just for completeness anyway.

<samp>Declare Function crc32 Lib "zlib.dll" (ByVal crc As Long, ByRef buffer As Any, ByVal bufferLen As Long) As Long

or, in C++ format,

uLong crc32 (uLong crc, const Bytef *buf, uInt len);</samp>

As you can see, the crc32 function takes 3 arguments. The first one is a previous crc value, in case we do the CRC check in chunks. For simplicity, we will always use CRC on the whole buffer at once, which means we leave CRC as 0. buf/buffer is the buffer, and len/bufferLen is the length of the buffer. If you’ve fully understood the uncompress and compress2 functions then there is no need for an example here, since the buffer and length arguments are used here in exactly the same way as before.

Final notes/other stuff
None of the above code has been tested. They were jotted down directly on notepad and have never gone through any stage of compilation. If there are any bugs within them, report them here. The posts below will give info regarding specific file format headers, but this information alone should already be adventurous enough. ;D

Neobeo
Apr 2, 2006, 12:48 PM
The J2A File Format
Well, it should probably be called the anims.j2a file format, since that's the only file I'm aware of that uses the j2a extension.

The ALIB Header
This is not a C/C++ structure, but I will be using a similar format to define the ALIB header.


struct ALIB_Header {
char Magic[4] = "ALIB"; //just an identifier, ignore null-char overflow
long Unknown = 0x00BEBA00; //little endian, no idea what its for
long HeaderSize; //Equals 464 bytes for v1.23 anims.j2a
short Version = 0x0200; //Probably means v2.0
short Unknown = 0x1808; //no idea what this is
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, which is not all that confusing, 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 as call them Data1 to Data4.

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

struct ANIM_Header {
char Magic[4] = "ANIM"; //just an identifier, ignore null-char overflow
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 SampleUnknown; //Unknown, but related to the sound samples
long CData1; //Compressed size of Data1 (Animation Info)
long UData1; //Uncompressed, remember the zlib tutorial?
long CData2;
long UData2;
long CData3;
long UData3;
long CData4;
long UData4; //lazy all the way up to here
}
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; //not really sure what this is, so I'm guessing FPS
long Reserved; //zeroes, 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 the hotspot!!
short ColdspotY; //relative to the hotspot
short HotspotX; //This is a misnomer. In reality, this is where the
short HotspotY; //frame starts drawing relative to the "hotspot".
short GunspotX; //relative to hotspot
short GunspotY; //...
long ImageAddress; //Address in Data3 where image starts
long MaskAddress; //Address in Data3 where mask starts
}
What the relatives mean is that the hotspotx and hotspoty value are almost always negative values, (there are notable exceptions of course). I will try to include an example at the end of everything.

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 recognise), which is similar to RLE. The first four bytes contain width and height, except that the width has some flag that is sometimes set on its most significant bit. I have no idea what that is, so we'll skip it.

After those 4 bytes, try to read the image data as codebytes. A codebyte < 128 (0x80) means to skip that many pixels. A codebyte > 128 (0x80) means to read and set that many pixels from the buffer onto the image. A 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 realised that the image is 8-bits, so get a palette from some random level to start with. Anyway, the mask data is also in this Data3, and is 2-bit (black or white). I have yet to check whether this is clipping mask or transparency mask (more likely the latter), but anyway the mask data is aligned to 4 bytes.

Data4 - Sample Data
I have never been good with sound. In short, I haven't even attempted to find out what could possibly lie in here.

End of anims.j2a
The next post should include an example of reading a single frame in anims.j2a. Unless I'm very lazy or out of time, in which case I'll jump to J2T and J2L.

Neobeo
Apr 2, 2006, 12:49 PM
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.

Peeking into Data1 - Animation Info
Okay, 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.

Peeking into 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
I should probably have done this in a table, but I'm just too lazy:

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, means 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 (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 aligned to bytes. If the mask address is given as -1 (FF FF FF FF), then there is no mask given for it.

Peeking into 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 is set. This could possibly a flag, or something else, I haven't quite researched this yet. 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:
<table><tr><td>
http://www.files.bz/files/5589/j2aPALimage.png
</td><td>
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" :).

Also, I'm not a HTML expert or anything, so some stuff could not appear the way I wanted to. Anyway, it would be great if anyone could help convert this into an ERE-readable format, since I now hate Textile.

Oh yeah, and if anyone wondered what happened to Data4 (Sample Data), I have no idea how to use it. So figure it out yourself :P.

This ends the J2A section of the file formats, very dull stuff. So I'll probably be making the J2T and J2L versions much more condensed (even though they can be much more complicated than this).
</td></tr></table>

Neobeo
Apr 2, 2006, 12:50 PM
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:

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.

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:

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 <samp>long</samp> (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 <samp>short</samp> 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:
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.

Neobeo
Apr 2, 2006, 12:50 PM
The J2T File Format
My time is slowly running out, so we'll cut to the chase.

struct TILE_Header {
char Copyright[180];
char Magic[4] = "TILE";
long Signature = 0xAFBEADDE //looks like DEADBEAF on a hex editor
char LevelName[32];
short Version; //0x200 for v1.23, 0x201 for v1.24
long FileSize;
long CRC32;
long CData1;
long UData1;
long CData2;
long UData2;
long CData3;
long UData3;
long CData4;
long UData4;
}

Data1 - Tileset Info
For the files I checked so far, this buffer always has a size of 27652 bytes for v1.23, or 107524 bytes for TSF. This is due to it supporting up to 4096 tiles instead of the original 1024.

#if version = 0x0200 then MAX_TILES = 1024
#if version = 0x0201 then MAX_TILES = 4096

struct TilesetInfo {
long PaletteColor[256]; //in the format {red, green, blue, zero}
long TileCount; //number of tiles, always a multiple of 10
char Unknown[MAX_TILES]; //I suspect this has something to do with transparency; haven't looked at it yet
char Zeros[MAX_TILES]; //Unsure, all zeros?
long ImageAddress[MAX_TILES];
long Zeros[MAX_TILES];
long TMaskAddress[MAX_TILES]; //Transparency masking, for bitblt
long Zeros[MAX_TILES];
long MaskAddress[MAX_TILES]; //Clipping mask, or the "tile mask" as it is called in JCS
long FMaskAddress[MAX_TILES]; //flipped version of the above
}

First question that comes to your mind is: what are those address thingies? I should probably have called them offsets, but they basically refer to the address in their corresponding buffers from which to start reading.

In specific, ImageAddress reads from Data2, TMaskAddress reads from Data3, MaskAddress and FMaskAddress reads from Data4, and you should probably memorise this to avoid confusion later on. If you are planning to make a tileset extractor of some sort, you would probably need only to use ImageAddress and MaskAddress.

Data2 - Image Address
Each image is 1024 bytes long, uncompressed. Each byte represents a palette index, so you have a 32*32 picture just by reading each byte as a pixel. Note that each image here is unique, so for example if your tileset has a blank space in say, tile #42, then ImageAddress[42] will be equal to zero (offset zero in the Data2 buffer).

Data3 - Transparency Mask
Similar to Data2, except each mask is 128 bytes. This is because the mask is only 1 bit-per-pixel (black or white). If you are trying to extract mask information into a picture for the first time, expect some problems with bit orders; I can't remember whether it reads MSB-first or LSB-first.

Data4 - Clipping Mask
Same notes as Data3.

Other notes
Remember that Data2 is not equivalent to the actual tileset graphics as seen in JCS, and Data4 is not the mask either. Data2 simply contains an image of each unique tile in the set, likewise with Data4. You must read Data1 if you want to find out exactly what image and mask a particular tile# has.

EDIT: Very minor edit, don't ask.

Neobeo
Apr 2, 2006, 12:51 PM
Post-release notes

We've only covered 3 file formats (J2A, J2T, J2L) in the series. If you're adventurous enough, you could use the same methods to hack the other JJ2 file formats. This is sort of like a parting gift from me, and I hope you will have more use for it than I did. I really hope that when I get back here (maybe 6 months?) there will be some useful programs and new ERE entries containing this information :D. Well, have fun!

n0
Apr 2, 2006, 01:01 PM
I love you. Now I just need to finish my C++ course and I WILL use this.

CrimiClown
Apr 2, 2006, 01:03 PM
I haven't a CLUE what this is all about but DANG you rock! =D

cooba
Apr 2, 2006, 01:03 PM
...Neobeo = awesome. That's all I have to say for now.

CrimiClown
Apr 2, 2006, 01:05 PM
OMG 7-double post!

Ice M A N
Apr 2, 2006, 01:16 PM
Once again, awesome work...

I can't wait to get back to working with this stuff.. (I didn't know J2L except for the header and first part of Data1, or a fair bit of J2A data2)

I'll try to find some more useful code/documentation I made to share later..

one thing I guess I'll have to double check is where you have:

(in J2T) char Unknown[256]; //I suspect this has something to do with transparency; haven't looked at it yet

I have a longer thing written down (entirely possible I had expanded it based on the bits), but I think it flagged tiles that either had some transparency and/or some pure white I think (probably a bug or white used to indicate transparent or somesuch..)..

a PHP J2T decoder and a couple comments I gave someone..:
it's slow and poorly made (requires GD, only does image [no mask], doesn't really do anything cleanly), but it's more of a proof of concept that you can do stuff in non-C/C++/VB/whatever...



<?php
define("VERSION_TSF", 513);
define("VERSION_123", 512);
set_time_limit(60);//!! MegaMegatropolis broke 30...

$filename = "MegaMegatropolis.j2t";//last tileset I was testing with :)

$fp = fopen($filename,"rb");

$header = fread($fp, 262); //262 = sizeof(J2THEADER);
//unpack will make an array with index [1] giving be the int I'm looking for

list(,$version) = unpack('v',substr($header,220,2));//short int for version

//I've only tested this with one TSF tileset, but I think it's right
if ($version == VERSION_TSF) {
$MAXTILES = 4096;
} else {
$MAXTILES = 1024;
}

//compressed/uncompressed sizes of following sections:
list(,$s1clen) = unpack('V',substr($header,230,4));
list(,$s1len) = unpack('V',substr($header,234,4));
list(,$s2clen) = unpack('V',substr($header,238,4));
list(,$s2len) = unpack('V',substr($header,242,4));
unset($header);


//read & uncompress said sections
$s1compressed = fread($fp, $s1clen);
$s1 = gzuncompress($s1compressed,$s1len);
unset($s1compressed);
$s2compressed = fread($fp,$s2clen);
$s2 = gzuncompress($s2compressed,$s2len);
unset($s2compressed);

fclose($fp);


list(,$numtiles) = unpack('V',substr($s1,1024,4));

//Perhaps limit numtiles to 100? (remember to keep multiple of 10)
//this way: a lot quicker (depending on how big it really is)
//and people can't rip tilesets
//uncomment next line to do so:
//$numtiles = min($numtiles,100);

$height = $numtiles*1024/320;

$img = imagecreate(320,$height);

//palette
for ($i = 0; $i < 256; $i++) {
$r = ord($s1[$i*4+0]);
$g = ord($s1[$i*4+1]);
$b = ord($s1[$i*4+2]);
imagecolorallocate($img, $r,$g,$b);
}

//maps tiles from the "J2T order" to "JCS order"
$offsets = unpack('V*',substr($s1,1024+4+2*$MAXTILES,4*$MAXTI LES));

unset($s1);

//top left corner of where to place current tile
$xoff = 0;
$yoff = 0;

for ($i = 0; $i < $numtiles; $i++) { //each tile

for ($j = 0; $j < 32; $j++) { // each row of pixels in tile
for ($k = 0; $k < 32; $k++) { // each pixel in row
$curpix = ord($s2[$offsets[$i+1]+32*$j+$k]);
imagesetpixel($img,$xoff+$k,$yoff+$j,$curpix);
}
}

//move where "current tile" goes
$xoff+=32;
if ($xoff == 320) {
$xoff=0;
$yoff+=32;
}
}

unset($s2,$offsets);
imagepng($img,"./write/$filename.png");
imagedestroy($img);
echo "DONE";

/*
You may also be interested in this stuff that I found out...

typedef struct _J2THEADER {
char copyright[180];
DWORD id; // 0x454c4954/TILE
DWORD magic; //0xafbeadde/DEADBEAF
char name[32];
short int version;
DWORD filesize;
DWORD checksum; //probably adler32 checksum

DWORD s1clen;
DWORD s1len;
DWORD s2clen;
DWORD s2len;
DWORD s3clen;
DWORD s3len;
DWORD s4clen;
DWORD s4len;
} J2THEADER;

typedef struct _J2TSECTION1 {
BYTE palette[256][4];
DWORD numtiles;
BYTE fastblit[1024];//in TSF, all these array will be 4096
BYTE reserved1[1024];
DWORD jcsorder[1024];
DWORD reserved2[1024];
DWORD unknown[1024];
DWORD reserved3[1024];
DWORD mask[1024];
DWORD flippedmask[1024];
} J2TSECTION1;


sections 2=gfx

section3.. still haven't looked at much.. based on a 10 second glance
from a while ago, I thought it was blit masks... (i.e. transparency masks..)

section 4 = masks (with flipped masks as well)
*/
?>

Odin
Apr 2, 2006, 01:22 PM
*Watches as Neobeo bursts through the wall* Aww, yeah!

Neobeo
Apr 2, 2006, 01:50 PM
(in J2T) char Unknown[256]; //I suspect this has something to do with transparency; haven't looked at it yet
Oops, that should have said Unknown[MAX_TILES]; .. fixed it :D.

I have never learnt any PHP beyond "hello world", but it sure looks like great stuff (it actually looks a lot like C/C++) ;). I didn't really have much time to look through the structures you defined, but I guess FastBlit would be a good way of putting it ;D.

I've never actually used most of the information in J2T either, I just did some last-minute research to provide closure for my article. So I'll be really glad if people will fill in the missing information, publish it in ERE, all in time for my "first return visit".

Iam Canadian
Apr 2, 2006, 02:03 PM
That's pure gobbledegook to me, but I'm hoping it can be put to some practical purpose.

Odin
Apr 2, 2006, 02:07 PM
That's pure gobbledegook to me, but I'm hoping it can be put to some practical purpose.

If you don't understand the purpose of this, it basically means that these are the methods used to read and write to JJ2 data files. Ergo, it means that this is how we can edit crap.

Iam Canadian
Apr 2, 2006, 02:16 PM
Awesome. I didn't have the attention span to actually read all of that.

Neobeo
Apr 2, 2006, 04:58 PM
If you need a fully featured example, I've released an open-source Tileset Extractor (http://www.jazz2online.com/J2Ov2/downloads/info.php?levelID=4087) which supports both v1.23 and TSF. I've never had any use for it, but others might, and besides J2T is the easiest format to understand, yada yada etc...

Birdie
Apr 2, 2006, 08:55 PM
:) This is good, very good, extremly good. I've just started learning C++ a bit ago iam not very advanced, but i can't wait until i've learned enough to use this.
EDIT:
This probably deserves a sticky!

jam
Apr 2, 2006, 10:46 PM
Neobeo, you own dude! I would say keep up the good work but unfortunately that wouldn't be very easy considering where you are off to :\

Black Ninja
Apr 2, 2006, 11:15 PM
<b>OMG</b>

Baggers
Apr 2, 2006, 11:23 PM
Neobeo: Well written and very thorough, great work man!

Grytolle
Apr 3, 2006, 12:03 AM
Neobeo, you own dude! I would say keep up the good work but unfortunately that wouldn't be very easy considering where you are off to :\jam u help me wif the dll now k? i got source^^

DoubleGJ
Apr 3, 2006, 08:06 AM
Awesome. Don't understand much yet, but I'm on the way. ;)

Marijn
Apr 3, 2006, 08:32 AM
Article this! :D

Birdie
Apr 4, 2006, 05:15 PM
Once again, awesome work...

I can't wait to get back to working with this stuff.. (I didn't know J2L except for the header and first part of Data1, or a fair bit of J2A data2)

I'll try to find some more useful code/documentation I made to share later..

one thing I guess I'll have to double check is where you have:

(in J2T) char Unknown[256]; //I suspect this has something to do with transparency; haven't looked at it yet

I have a longer thing written down (entirely possible I had expanded it based on the bits), but I think it flagged tiles that either had some transparency and/or some pure white I think (probably a bug or white used to indicate transparent or somesuch..)..

a PHP J2T decoder and a couple comments I gave someone..:
it's slow and poorly made (requires GD, only does image [no mask], doesn't really do anything cleanly), but it's more of a proof of concept that you can do stuff in non-C/C++/VB/whatever...



<?php
define("VERSION_TSF", 513);
define("VERSION_123", 512);
set_time_limit(60);//!! MegaMegatropolis broke 30...

$filename = "MegaMegatropolis.j2t";//last tileset I was testing with :)

$fp = fopen($filename,"rb");

$header = fread($fp, 262); //262 = sizeof(J2THEADER);
//unpack will make an array with index [1] giving be the int I'm looking for

list(,$version) = unpack('v',substr($header,220,2));//short int for version

//I've only tested this with one TSF tileset, but I think it's right
if ($version == VERSION_TSF) {
$MAXTILES = 4096;
} else {
$MAXTILES = 1024;
}

//compressed/uncompressed sizes of following sections:
list(,$s1clen) = unpack('V',substr($header,230,4));
list(,$s1len) = unpack('V',substr($header,234,4));
list(,$s2clen) = unpack('V',substr($header,238,4));
list(,$s2len) = unpack('V',substr($header,242,4));
unset($header);


//read & uncompress said sections
$s1compressed = fread($fp, $s1clen);
$s1 = gzuncompress($s1compressed,$s1len);
unset($s1compressed);
$s2compressed = fread($fp,$s2clen);
$s2 = gzuncompress($s2compressed,$s2len);
unset($s2compressed);

fclose($fp);


list(,$numtiles) = unpack('V',substr($s1,1024,4));

//Perhaps limit numtiles to 100? (remember to keep multiple of 10)
//this way: a lot quicker (depending on how big it really is)
//and people can't rip tilesets
//uncomment next line to do so:
//$numtiles = min($numtiles,100);

$height = $numtiles*1024/320;

$img = imagecreate(320,$height);

//palette
for ($i = 0; $i < 256; $i++) {
$r = ord($s1[$i*4+0]);
$g = ord($s1[$i*4+1]);
$b = ord($s1[$i*4+2]);
imagecolorallocate($img, $r,$g,$b);
}

//maps tiles from the "J2T order" to "JCS order"
$offsets = unpack('V*',substr($s1,1024+4+2*$MAXTILES,4*$MAXTI LES));

unset($s1);

//top left corner of where to place current tile
$xoff = 0;
$yoff = 0;

for ($i = 0; $i < $numtiles; $i++) { //each tile

for ($j = 0; $j < 32; $j++) { // each row of pixels in tile
for ($k = 0; $k < 32; $k++) { // each pixel in row
$curpix = ord($s2[$offsets[$i+1]+32*$j+$k]);
imagesetpixel($img,$xoff+$k,$yoff+$j,$curpix);
}
}

//move where "current tile" goes
$xoff+=32;
if ($xoff == 320) {
$xoff=0;
$yoff+=32;
}
}

unset($s2,$offsets);
imagepng($img,"./write/$filename.png");
imagedestroy($img);
echo "DONE";

/*
You may also be interested in this stuff that I found out...

typedef struct _J2THEADER {
char copyright[180];
DWORD id; // 0x454c4954/TILE
DWORD magic; //0xafbeadde/DEADBEAF
char name[32];
short int version;
DWORD filesize;
DWORD checksum; //probably adler32 checksum

DWORD s1clen;
DWORD s1len;
DWORD s2clen;
DWORD s2len;
DWORD s3clen;
DWORD s3len;
DWORD s4clen;
DWORD s4len;
} J2THEADER;

typedef struct _J2TSECTION1 {
BYTE palette[256][4];
DWORD numtiles;
BYTE fastblit[1024];//in TSF, all these array will be 4096
BYTE reserved1[1024];
DWORD jcsorder[1024];
DWORD reserved2[1024];
DWORD unknown[1024];
DWORD reserved3[1024];
DWORD mask[1024];
DWORD flippedmask[1024];
} J2TSECTION1;


sections 2=gfx

section3.. still haven't looked at much.. based on a 10 second glance
from a while ago, I thought it was blit masks... (i.e. transparency masks..)

section 4 = masks (with flipped masks as well)
*/
?>

:) A tileset previewer should be added to J2O

Ice M A N
Apr 4, 2006, 06:20 PM
"[...]it's slow and poorly made (requires GD[...]"

it's possible, but I don't really see anyone rushing to implement it anytime soon...

Bobby aka Dizzy
Apr 4, 2006, 08:01 PM
School keeps me too busy right now... maybe late May?

Sorry about that Ice M A N.

Marijn
Apr 5, 2006, 01:14 AM
:) A tileset previewer should be added to J2O
I agree :D
(download Section)

Slow ?


public bool Excist(string name)
{
//database stuff. Discover if the filename is allready in the DB.
return result;
}
public string GetUrl(string tilename)
{
//Get url from database.
return url;
}

public string GenerateUrl(string tilename)
{
string url = "";

if(Excist(tilename))
{
//generate image
//database stuff...
//Set string.
}
else{
GetUrl(string tilename)
}
return url;

}



Sorry for the C# code. This will perform a check, if the name given up is not in the database, the (slow) funtion will do his work, set the url in database. and return the string to the function. or else.. the file will just seach up in the database and find the location of the ( allready ) renderd image!

Stijn
Apr 5, 2006, 07:04 AM
J2O is written in PHP, foo.


$aTilesetData = /* some code to retrieve upload info from database */;
if(is_readable('rendered/'.$aTilesetData['filename'].$aTilesetData['uploadtime']'.png')) {
//rendered image exists
} else {
//generate image and save
}


But I'm sure Bobby/Frank can write this themselves. Just wanted to correct Marijn ;)

Ice M A N
Apr 5, 2006, 08:04 AM
yes yes... I suggested that obvious solution as early as the code was made... it's just a matter of getting around to doing it, taking into account the tileset would need to be readjusted when reuploaded, what if a pack includes a tileset already "generated" (could use a combination filesize name and/or hash in the J2T file) etc.. (plus does J2O's host have GD enabled?)

School keeps me too busy right now... maybe late May?

Sorry about that Ice M A N.
We're in the same boat. Nothing to be sorry about.

blurredd
Apr 5, 2006, 10:21 AM
It seems a lot more practical for the uploader to include screenshots, though. The tileset itself only shows so much, while a screenshot can be a lot more informative.

Here's an idea: What about something that could take a scaled screenshot of an entire level (or rather, all the layers where the X and Y speeds equaled 1) with perhaps all of the visible events? I know Neobeo practically made something like this already, but I think it would have its uses.

FQuist
Apr 5, 2006, 10:29 AM
Also (I and Bob spoke about that before) you would have issues with wether uploaders from the past actually wanted screenshots of their levels on J2O. Issue would be wether to only work with permission (= most old tilesets won't have a preview) or not. J2C posted screenies from tilesets but that wasn't retrospective I think.

Ice M A N
Apr 5, 2006, 11:22 AM
Here's an idea: What about something that could take a scaled screenshot of an entire level (or rather, all the layers where the X and Y speeds equaled 1) with perhaps all of the visible events? I know Neobeo practically made something like this already, but I think it would have its uses.

A few potential issues (at least with using this idea on J2O):
*neobeo (apparently?) didn't find where the X and Y speeds are (although it should be trivial to find that.. I just don't have JCS/JJ2 so it makes it difficult).. and it would also need to handle layers that tile, etc...
*once J2A edits start coming out, the visible events will change (we could do JCS-style labels, but it wouldn't really be useful in a scaled image...)... even using the standard anims would somewhat difficult, requireing prodding around in anims.j2a.. actually.. no.. we could just preprocess it and get the first frame of each event and just use those PNGs or whatever to composite the image..
*I'd guess it'd be amazingly slow in PHP (which begs the question does the J2O server allow for PHP to run an external C/whatever program somehow..?)
*Should we worry about CliffyB style hidden messages in levels (a la JJ1)
*What about retroactive appication to the current uploads (as fquist was talking about)

Of course I was planning on doing something like what you said anyway (a complete composite view of levels) once school is out (i.e. this summer) and perhaps starting a 2D game compatible with JJ2 files... an "OpenJazz2" if you will..

And then again, as you said, just having the uploader provide a preview image would be more practical...

Birdie
Apr 5, 2006, 11:26 AM
Also (I and Bob spoke about that before) you would have issues with wether uploaders from the past actually wanted screenshots of their levels on J2O. Issue would be wether to only work with permission (= most old tilesets won't have a preview) or not. J2C posted screenies from tilesets but that wasn't retrospective I think.
Perhaps there could be a check box type thing when you upload a level that asks you if you want there to be a screenshot.

Grytolle
Apr 5, 2006, 11:40 AM
&lt;OpenJazz23

Monolith
Apr 5, 2006, 07:42 PM
A few thoughts on tileset previews for J2O.

The images should be kept lightweight, as in small in file size. The purpose of a preview is just to let the user get a quick glance at what the tileset looks like without having to download it and all. Therefore, the quality of the preview image can be dropped down greatly in order to save on file sizes (and download times). I'm thinking scaling the image down to 50%, and save it as a low-quality JPEG so that the compression can be way up.

See if you can use a compiled (c?) version of this conversion program on the server as PHP or other scripting languages are relatively slow. Then just invoke the compiled program from the site script.

The images should obviously be cached. If there's a concern about size, delete preview images that haven't been viewed in over a month or so. Generate preview images on demand.

blurredd
Apr 6, 2006, 06:18 AM
Also (I and Bob spoke about that before) you would have issues with wether uploaders from the past actually wanted screenshots of their levels on J2O. Issue would be wether to only work with permission (= most old tilesets won't have a preview) or not. J2C posted screenies from tilesets but that wasn't retrospective I think.
I just was referring to new uploads where the creater wants to include a screenshot. However, I'm not against having others upload preview shots for older levels and tilesets. I don't think it's important though (plus I'm getting off-topic).

Also, I didn't mean to imply my entire-level screenshot idea would be done dynamically on J2O. It would just be a program that people may choose to use.

Cpp
Apr 7, 2006, 12:28 PM
Neobeo you should also make a 1.24 to 1.23 J2L converter.
The JCS patch I released a while ago really sux bad.

Neobeo
Apr 7, 2006, 08:50 PM
It seems a lot more practical for the uploader to include screenshots, though. The tileset itself only shows so much, while a screenshot can be a lot more informative.

Here's an idea: What about something that could take a scaled screenshot of an entire level (or rather, all the layers where the X and Y speeds equaled 1) with perhaps all of the visible events? I know Neobeo practically made something like this already, but I think it would have its uses.
Well ok, this is a prototype from 3 weeks ago, haven't worked on it since then. Made in Visual C++, it only reads layer 4 and represents the players as dots. (I probably stole the concept/idea from Overlord's VCR) The actual purpose of it was to test out its ability to parse J2L/J2T from within memory rather than reading player's positions. Also, while I'm able to read events, I found it impractical to include them.

If you see a chatlog in any of them, that was made in VB, and has been abandoned in place of a C++ one. So...
http://www.files.bz/files/5589/vcrtest1.png
http://www.files.bz/files/5589/vcrtest2.png
http://www.files.bz/files/5589/vcrtest3.png
http://www.files.bz/files/5589/vcrtest4.png
http://www.files.bz/files/5589/vcrchat1.png
http://www.files.bz/files/5589/vcrchat2.png
http://www.files.bz/files/5589/vcrchat3.png
http://www.files.bz/files/5589/vcrchat4.png
(Coincidence? I just noticed today that all the vcrchat screenshots have BlurredD in them)

About anyone discussing tileset screenshots, (and if you haven't noticed), I uploaded an opensource tileset extractor. As for OpenJazz2, it sounds great and good luck with it. I actually tried making a Jazz2 implementation once, more of an ASM to C++ decompilation rather than a re-creation of the game engine, and haven't really gotten that far. Finally, the 1.24 to 1.23 J2L converter should work, except I have never made a 1.24 level and therefore have no idea what the differences are.

Cpp
Apr 7, 2006, 11:42 PM
Well I can send you some levels saved in 1.23 and 1.24 so that you can see the difference.
Also you could include a layer 3 on the VCR thing as well as make it display events as original sprites. I myself had some problems dealing with 32 players for theres not enough different colors that I could use for the dots.

Marijn
Apr 9, 2006, 02:51 AM
Well ok, this is a prototype ... differences are.
Well that chatlog test is a fancy program :D

Radium
Apr 9, 2006, 03:55 PM
http://www.files.bz/files/5589/vcrtest1.png
http://www.files.bz/files/5589/vcrtest2.png
http://www.files.bz/files/5589/vcrtest3.png
http://www.files.bz/files/5589/vcrtest4.png
http://www.files.bz/files/5589/vcrchat1.png
http://www.files.bz/files/5589/vcrchat2.png
http://www.files.bz/files/5589/vcrchat3.png
http://www.files.bz/files/5589/vcrchat4.png
=O

Wow. Release plz D=

Monolith
Apr 9, 2006, 05:32 PM
Looks like cheating. Or at least an undetectable unfair advantage.

Neobeo
Apr 9, 2006, 05:43 PM
=O

Wow. Release plz D=

No, because

Looks like cheating. Or at least an undetectable unfair advantage.

The VCR was actually designed to test the capabilities of parsing J2L and J2T (back to topic!), and it has no plans of being released, unless as some sort of server tool. If you meant release the chatlog, which is completely harmless, that will probably come out eventually (except I no longer have the time <strike>and/or motivation</strike> to work on it).

Pako
Apr 11, 2006, 01:11 PM
Looks like cheating. Or at least an undetectable unfair advantage.
Well, make this only usable for the server. This would be perfect to know where is everyone when:
1.) You are on an hotel, and someone entered on the "INCREDIBLE SEKRET SUPAH ELITE ROM!!!11" thing (Haphazy ;P). How to know that? With this program. Also useful on Ground Forces (especially on the Tower of Doom).

cooba
Apr 11, 2006, 01:13 PM
This could be too much of a hassle, but how about VCR diplays layer 4 data only if the gametype is treasure?

Marijn
Apr 30, 2006, 08:14 AM
//decodejj2t.cs

using System;
using System.Runtime.InteropServices;


unsafe class app
{
char[] input;
char *output;
int CompressedSize;
int UncompressedSize;


static void Main()
{
app a = new app();
a.Decode();
Console.ReadLine();
}

public unsafe void Decode()
{
string (-) = "Let us attempt to compress this string";
input = (-).ToCharArray();

UncompressedSize = input.Length;
CompressedSize = (int)((float)(UncompressedSize * 1.01 ) + 12);

compress2(output, &CompressedSize, input, UncompressedSize, 9);


Console.WriteLine("Done..");
}

[DllImport("zlib.dll")]
static extern long compress(ref object dest, ref object destLen, ref object src, long srcLen);
[DllImport("zlib.dll")]
static extern long compress2( char* dest, int* destLen, char[] src, long srcLen, int Level);
[DllImport("zlib.dll")]
static extern long uncompress(ref object dest, ref object destLen, ref object src, long srcLen);
[DllImport("zlib.dll")]
static extern long crc32(long crc, ref object buffer, long bufferLen);

}

AAh.. Supid formats.. The C# compiler complaints about this error:

decodejj2t.cs(30,20): error CS0212: You can only take the address of an unfixed expression inside of a fixed statement initializer

decodejj2t.cs(30,20): error CS0212: Alleen het adres van een niet-vaste
expressie kan binnen de initialisatiefunctie van een vaste instructie
worden gebruikt

I need to use the keyword "fixed" but.... Where :'(

Cpp
Apr 30, 2006, 02:47 PM
Is that CLI you're using?

Marijn
May 1, 2006, 01:42 AM
No, C#.
My problem is because I love te managed framework, I'd never had a reason to use the unsafe keyword.. :'(


If you meant release the chatlog, which is completely harmless, that will probably come out eventually (except I no longer have the time and/or motivation to work on it).

Just release :D

Onag
Aug 15, 2006, 07:21 PM
You can ban me for resurrecting this, but...

Has anyone actually tried decompressing any of the files using these file formats? Every time I try to do it with a .j2t, I run into a problem with the Data1 block. Specifically, When I inflate/decompress the block of 4153 bytes, I fill up the 27652 bytes it's supposed to be with 4 bytes still remaining in the compressed block.

For example:

CData1 = 4153
UData1 = 27652

So, I allocate an array of 4153 bytes and copy the CData1 block into it. I then use a zlib library (a managed/.NET one called ICSharpCode.SharpZipLib) to inflate/decompress that data into another array of 27652 bytes.

The 27652 byte array is filled up with uncompressed data, but there are still 4 bytes remaining in CData1 that have not yet been touched.

The header seems to be right, and all of the other blocks work fine, but I can't figure out what is going on with those 4 bytes.

Anyway, I'm wondering if anyone else has been able to get this working. If so, I'll know the problem is either in my code, or in the way the zlib library is working.

-Nag

PhAyzoN
Aug 15, 2006, 09:28 PM
Wow dude. This stuff is great (for the community)!

EDIT: Woah, old thread :s

Cpp
Aug 15, 2006, 10:00 PM
Onag: I've done it with J2L format in C++ with zlib1.dll and it works like a charm. Decompiles the level into 4 streams allowing me to manually edit them and then recompile them into a modified J2L which runs fine. Don't know about the 4 bytes you're mentioning there as I only care for a working decompress/recompress. Might be CRC32?

EDIT: Here's the code I use to decompress:

// Uncompress the data pDataComp => pDataUncomp
bool CJ2FD::UncompressData() {

int i;

// Check if all compressed data pointers exist
for (i = 0; i < 4; i++)
if (pDataComp[i] == 0)
return false;

// Check the compressed data file sizes
for (i = 0; i < 4; i++)
if (!(Header.CData[i] > 0))
return false;

//Allocate memory for the uncompressed data
for (i = 0; i < 4; i++) {
if (pDataUncomp[i] != 0) delete[] pDataUncomp[i];
pDataUncomp[i] = new unsigned char[Header.UData[i]];
}

// Finally uncompress the data
for (i = 0; i < 4; i++)
if (uncompress(pDataUncomp[i], &Header.UData[i], pDataComp[i], Header.CData[i]) != Z_OK)
return false;

return true;
}


The pDataComp[4] are pointers to 4 char arrays where each holds the compressed data of a single stream (CData). The function above will decompress the data into pDataUncomp[4], pointers to 4 arrays of uncompressed data.


unsigned char* pDataComp[4]; // Pointer array for compressed data
unsigned char* pDataUncomp[4]; // Pointer array for uncompressed data

Onag
Aug 16, 2006, 08:24 AM
Thanks Overlord.

I think the problem lies in way the zlib library I'm using is working. Sometimes, it leaves 4 bytes, sometimes it doesn't. The uncompressed data *appears* to be intact, though I haven't tried using all of it yet.

I'm going to continue looking for a C# zlib that actually works. In the mean time, I just close my eyes and ignore the extra bytes. Hopefully things will work out.

-Nag

Neobeo
Jan 21, 2007, 02:52 PM
Four-month revival, but I was just wondering whether anyone has been using this information. Looking back, there are some incomplete stuff, inconsistencies and mistakes in my original specs. I was thinking of updating them, but it is probably pointless if no one takes a second look.

Alternatively, I have been working on a set of C++ libraries that allows one to edit JJ2 files. (J2L class based on Overlord's code) These are used in the new JSD and J2L editor, and some other secret programs. If there's enough interest I'm willing to release these libraries, which can be useful for the casual programmer. Or if there are more VB programmers or so, I could release them in the form of a VB DLL ;\.

Cpp
Jan 21, 2007, 03:27 PM
Always keep your posts up to date. ;)
I did use this thread as a reference quite a lot while working on the J2LC code and there will likely be such time again.

FQuist
Jan 21, 2007, 03:48 PM
J2LC code o.o

Cpp
Jan 22, 2007, 12:32 AM
That's short for Jazz 2 (1.24 to 1.23) Level Converter (has nothing to do with the level group). It was supposed to open J2L files only, but ended up being able to open all related formats. And so I named it C_J2FF as Class Jazz 2 File Format.

Marijn
Jan 23, 2007, 09:30 AM
I can't seems to start with C#,
how to get the byte* array, to an byte[] array, and so on.

Can someone give me some pointers?

n0
Jan 24, 2007, 12:06 PM
Wait, it can handle .lev files, OL?

Neobeo
Jan 24, 2007, 02:02 PM
Nope, it doesn't. In fact, I'm interested in .lev files. Where can I find these?

Violet CLM
Jan 24, 2007, 05:43 PM
They are included in 1.00g (and presumably h).

EDIT: .lev is a common extension for lazy people. The actual format is usually different from game to game, though. The only relevant .lev is the JJ2 .lev.

EDIT #2: Umm, yes. Thank you for quoting the response I made to Drmoo in response to Drmoo.

Dermo
Jan 25, 2007, 11:05 AM
LEV is the file extension for the Level file associated with NetHack 3.x.

Cpp
Jan 25, 2007, 04:25 PM
Those "lev" files take a noticeable amount of extra space compared to regular "j2l". Besides its an outdated format, doesn't seem to be compressed afaik.

ThunderPX
Jan 26, 2007, 05:54 AM
LEV is the file extension for the Level file associated with NetHack 3.x.

EDIT: .lev is a common extension for lazy people. The actual format is usually different from game to game, though. The only relevant .lev is the JJ2 .lev.

<!-- zomg --!>

Torkell
Feb 14, 2007, 03:04 AM
I'm going to have a try at sticking this lot into the ERE. The description looks pretty good, so I'm going to put it in mostly as-is, just tidying up bits where it makes sense for the wiki.

Edit: done the three file specifications (see J2A File Format (http://www.jazz2online.com/J2Ov2/wiki/index.php?J2A_File_Format), J2L File Format (http://www.jazz2online.com/J2Ov2/wiki/index.php?J2L_File_Format) and J2T File Format (http://www.jazz2online.com/J2Ov2/wiki/index.php?J2T_File_Format)). I'm just about to add the zlib info.

Edit: zlib (http://www.jazz2online.com/J2Ov2/wiki/index.php?zlib) done. These pages need to be linked into the main wiki, but should do for now.

Neobeo
Feb 14, 2007, 05:29 AM
Yay, this gives me a lot more incentive at trying to fix up some minor errors and missing information. Keep it up.

Cpp
Feb 15, 2007, 05:18 AM
I'm going to post my j2l/j2t (de)compiler source code (CJ2FF Class) here in the near future so feel free to integrate that as well.

~Ol

EDIT: Oh and you might want to update the the J2L Data1 with this info:

short JCSCameraX;
short PasswordRelated1;
short JCSCameraY;
short PasswordRelated2;
char PasswordLayerFlags;
char AmbientLightningMin;
char AmbientLightningStart;
short NumberOfAnimatedTiles;
char HoriOrVertSplitScreen;
char IsThisMultiplayerLevel;
long BufferSize;
char LevelName[32];
char Tileset[32];
char BonusLevel[32];
char NextLevel[32];
char SecretLevel[32];
char MusicFile[32];
char HelpString[16][512];

Neobeo
Feb 15, 2007, 08:42 PM
Oh don't worry, I have far more complete specifications that that. It's just a matter of whether I will end up updating or not.

Also, what do you think of a TSF - 1.23 Level converter? (I have such a program, but never quite released it because it was a half-assed job)

Grytolle
Feb 15, 2007, 09:57 PM
I'm quite sure such a program would be greatly appreciated

Cpp
Feb 16, 2007, 12:35 AM
As promised, here is the (de)compiler source code. Have fun.

http://downlink.lordprime.com/jazzjackrabbit/2/code/j2ff.zip

Cataphract
Feb 16, 2007, 10:19 AM
What about the really cool stuff, something like the layer 4+tileset parser I've seen?
I shouldn't mind getting hold of that code :)

n0
Feb 16, 2007, 07:24 PM
Hot stuff Neobeo and Overlord... and thanks to Torkell for the wiki upload.

Between the three of you, I have regained interest in cracking this game of ours.

Cataphract
Feb 17, 2007, 08:15 PM
Oh don't worry, I have far more complete specifications that that. It's just a matter of whether I will end up updating or not.

If you do have more complete specifications, it would certainly be very helpful if you shared them.

With the ones currently on this thread, I'm quite sure that the moment I tried to do something a bit more complicated I should end up in a dead end and have to start poking around with a binary comparison program, a task for which I have neither the time -- the second semester is near --, nor the inclination -- debugging code is already boring enough.
Therefore, anyone would be reluctant in investing his time with such uncertain results.

The (marked) incompleteness of j2t specification is compensated by the open-source implementation of the tileset extractor, but the j2l specification or the relation between the two have no reference implementations.

Obviously, incomplete documentation is quite preferable to nothing, however, I should point more emphasis is unfortunately given to side issues such as the interface with zlib (IMO a brief reference to the relevant functions should suffice), rather than the specification per se.

Cataphract
Feb 21, 2007, 10:33 AM
On a related note, I've uploaded an <a href="http://www.jazz2online.com/J2Ov2/downloads/info.php?levelID=4598">implementation in C of a layer extractor</a>.

Neobeo
May 1, 2007, 06:26 AM
Recently noticed a surge in people interested in 1.10o (version 0x0200) J2L format. Unfortunately I have not yet researched this file format, so it would be great if someone could provide this information (and/or update it to the ERE).

Violet CLM
May 1, 2007, 07:10 AM
It's sufficiently similar that if you add the JJ2 header at the start of the file and modify the checksum to reflect the new filesize, 1.23 will be able to run it.

Violet CLM
Dec 20, 2008, 03:13 PM
CRC Checking

Finally we get to the crc32 stage. All (or most) of the JJ2 files contain an internal (and some even external) CRC check to verify that the file is not corrupted in any way. Seeing how most programmers are lazy, they will probably skip the CRC mechanism and just assume that file is valid, but it will be included here just for completeness anyway.

<samp>Declare Function crc32 Lib "zlib.dll" (ByVal crc As Long, ByRef buffer As Any, ByVal bufferLen As Long) As Long

or, in C++ format,

uLong crc32 (uLong crc, const Bytef *buf, uInt len);</samp>

As you can see, the crc32 function takes 3 arguments. The first one is a previous crc value, in case we do the CRC check in chunks. For simplicity, we will always use CRC on the whole buffer at once, which means we leave CRC as 0. buf/buffer is the buffer, and len/bufferLen is the length of the buffer. If you’ve fully understood the uncompress and compress2 functions then there is no need for an example here, since the buffer and length arguments are used here in exactly the same way as before.
How is this applied for actually working with, say, .j2l files? JJ2 won't run with an improper CRC long, but I'm not sure what buffer I'm supposed to be using, if .j2l files have four separate zlib streams and all sorts of other data.

Ricerind
Dec 20, 2008, 03:25 PM
It's a running CRC of all four compressed streams.

Violet CLM
Dec 21, 2008, 11:10 AM
Sorry, but could you be more specific? I'm new to the concept, and Google offers very few hits for "running CRC." What is the implementation? CRC the first block, then append the second and CRC again, then...? or what?

Cpp
Dec 21, 2008, 11:58 AM
I think it's the whole data block.

EDIT: My server is currently off-line so I'll just pastebin my source code.
http://pastebin.com/m45d6c54c
http://pastebin.com/m269ed95e

Ricerind
Dec 21, 2008, 12:42 PM
Yes that is basically taking the CRC32 of the whole compressed data block, everything that's not part of the header.

Violet CLM
Dec 21, 2008, 07:16 PM
Okay, it looks like I can read and understand that. Thank you both! :D

Krezack
Feb 3, 2009, 11:38 PM
This should be stickied.

Violet CLM
Feb 5, 2009, 10:07 AM
It's in ERE...
but as it's been bumped, I figure I may as well ask, is fileHeader.crc32 = crc32(0L, Z_NULL, 0); substantially different from fileHeader.crc32 = 0;? I assume it is, but I don't really know what those variables refer to.

Cpp
Feb 6, 2009, 10:11 PM
Not quite sure, but I believe the first line initializes the CRC32 for further calculation while the second line only sets it to zero. Check the output variable and see whether they are the same. I think they should differ.

Violet CLM
Feb 7, 2009, 08:45 AM
I don't use the language, though. The only semi-native CRC implementation at my disposal gets the CRC of an entire file, without asking me what polynomial I would like, so I'm rebuilding it by hand.

Krezack
Feb 8, 2009, 08:07 PM
Four-month revival, but I was just wondering whether anyone has been using this information.

I'm coding a function in Python now that basically takes a j2t file and pulls out the mask and image so they can be used in a game I'm making (i.e. my game can accept Jazz 2 tilesets).

So yes, I certainly use it. More accurate specifications would certainly be welcome.

FawFul
Feb 8, 2009, 10:07 PM
isn't there already something for that called "tileset extractor?"

Violet CLM
Feb 8, 2009, 10:33 PM
That renders tilesets as image files, but if you're making a game, it's simpler just to have the game read .j2t files directly.

djazz
Mar 10, 2009, 12:10 AM
HELLO
I have coded some in PHP now, and I can read ALL J2L data and put it in nicely arrays. I'm even about to read out event-data, but those parameters are hardcoded. It uses JCS.ini for the event-names.
The "Data3" stream is parsed into a easy to use array and all Level Properties too.
I haven't yet finished the read-out of the level, with those "words" in Data3. ALL Layer Properties are parsed into an array, as the Help Strings are too.
I love what you do Neobeo, and thanks the I C E Man's tileset-php code :-)


Have a look here (not complete):
http://djazz.mine.nu/content/jj2related/JJ2_formats/j2l/

Stijn
Mar 10, 2009, 01:59 AM
Oh, that's awesome. I started writing a class to do that once but never got around to finishing it. Great that you've done it. Will the source code be available?

Violet CLM
Mar 11, 2009, 08:28 PM
It's in ERE...
but as it's been bumped, I figure I may as well ask, is fileHeader.crc32 = crc32(0L, Z_NULL, 0); substantially different from fileHeader.crc32 = 0;? I assume it is, but I don't really know what those variables refer to.
I ask again. I kind of need to be able to compile things... what is the result of this setting? As I understand it otherwise, taking the CRC (with any polynomial) of 0 is just going to be 0, so there's got to be some sort of starting value, but what is it? Or do I just totally misunderstand the process?

djazz
Mar 26, 2009, 01:57 AM
Oh, that's awesome. I started writing a class to do that once but never got around to finishing it. Great that you've done it. Will the source code be available?

Thank you :-)
Yes I will release the source soon!

Stijn
May 4, 2009, 03:08 PM
I made a J2T image generator thingy (in PHP) for J2O today and decided to share it.

Documentation: http://www.stijnpeeters.nl/j2o/class.Tileset/
Source: http://stijnpeeters.nl/j2o/class.Tileset/source.php

It's very basic and doesn't do any file integrity checks, but perhaps it is of use to someone.

(Props to Neobeo, Ice M A N and Grytolle for hints/input/posts in this thread)

Grytolle
May 4, 2009, 03:20 PM
Yeah you'll find that this thread is full of usefull hints made by me :P "jam lets make dll ok??:D lol hi btw"

Stijn
May 4, 2009, 03:26 PM
I meant on IRC!

Violet CLM
May 4, 2009, 10:12 PM
Can anyone answer my question? Please? Or do I just need to go learn a new programming language for one simple task?

djazz
May 5, 2009, 12:27 AM
Ok here is the source:
http://djazz.mine.nu/content/jj2related/JJ2_formats/j2l/index.phps
I haven't modified it since last post
:-)

Grytolle
May 5, 2009, 01:27 PM
I have no idea, UR, sorry

Violet CLM
May 6, 2009, 09:10 AM
Another question (though the first remains open), has anyone tried zlibbing the blocks with compression rates other than that which JCS uses? Can JJ2 still read them?

Cpp
May 6, 2009, 09:15 AM
Can anyone answer my question? Please? Or do I just need to go learn a new programming language for one simple task?

This snippet is taken from the zlib manual (http://www.zlib.net/manual.html#crc32).

uLong crc32 (uLong crc, const Bytef *buf, uInt len);
Update a running crc with the bytes buf[0..len-1] and return the updated crc. If buf is NULL, this function returns the required initial value for the crc. Pre- and post-conditioning (one's complement) is performed within this function so it shouldn't be done by the application. Usage example:

uLong crc = crc32(0L, Z_NULL, 0);

while (read_buffer(buffer, length) != EOF) {
crc = crc32(crc, buffer, length);
}
if (crc != original_crc) error();

Also, have you taken a look at the C++ source code I posted a few replies earlier?

Another question (though the first remains open), has anyone tried zlibbing the blocks with compression rates other than that which JCS uses? Can JJ2 still read them?
Yes, it can.

Violet CLM
May 6, 2009, 10:38 AM
If buf is NULL, this function returns the required initial value for the crc.
I'm asking what this required initial value is. The example usage there is the same as in your source code (albeit with while instead of for), and I think I've managed to hardcode the crc-ing of the four buffers with respect to an existing polynomial, but I don't know what the polynomial should start as, because I don't know what the value of crc32(0L, Z_NULL, 0) is.

Cpp
May 6, 2009, 11:09 AM
Why do you want to hardcode it in? Last time I checked crc32(0L, Z_NULL, 0), it returned a zero, but I am not sure whether this is always the case. That's why I always initialize the crc variable with whatever value the crc32(0L, Z_NULL, 0) function call returns.

Violet CLM
May 6, 2009, 11:22 AM
Unless I go learn a new language, the only semi-native CRC support I have is extremely limited and seems only to use a fixed polynomial. Therefore I go through the bit stream and gradually xor onto the polynomial manually, a bit at a time. But if the initial polynomial is 0, then xor isn't going to do a thing and the final CRC will still be 0, right?

Cpp
May 6, 2009, 11:34 AM
Ah, now I understand your problem. You're not using the zlib library and/or C++ language. Personally I am not sure how the code you're using works, but I've checked the zlib source code and apparently the crc32 function always returns zero when the buffer param is set to Z_NULL. So in this case crc32(0L, Z_NULL, 0) = 0. If you pass a zero to your function, it won't work?

unsigned long ZEXPORT crc32(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
unsigned len;
{
if (buf == Z_NULL) return 0UL;

#ifdef DYNAMIC_CRC_TABLE
if (crc_table_empty)
make_crc_table();
#endif /* DYNAMIC_CRC_TABLE */

#ifdef BYFOUR
if (sizeof(void *) == sizeof(ptrdiff_t)) {
u4 endian;

endian = 1;
if (*((unsigned char *)(&endian)))
return crc32_little(crc, buf, len);
else
return crc32_big(crc, buf, len);
}
#endif /* BYFOUR */
crc = crc ^ 0xffffffffUL;
while (len >= 8) {
DO8;
len -= 8;
}
if (len) do {
DO1;
} while (--len);
return crc ^ 0xffffffffUL;
}

Violet CLM
May 9, 2009, 09:40 AM
Huh. I guess I just don't understand the function somehow, then... I'll have to do some more digging around and find an explanation of CRC that doesn't instruct me to xor lots of stuff by 0. Thanks.

Stijn
May 25, 2009, 04:08 AM
I updated my Tileset class and wrote a Level class, capable of rendering a preview of an arbitrary layer in a level. Note that especially the Level class is very basic and neither have error handling or checksum checking or stuff like that built-in.

Source code:
http://www.jazz2online.com/junk/source.php?file=class.Level.php
http://www.jazz2online.com/junk/source.php?file=class.Tileset.php

Thanks to Neobeo/J2NSM for <s>the source code</s> their file format documentation.

djazz
May 31, 2009, 09:47 AM
Wow, fantastic Stijn ;-)

I haven't tried it out yet, but I will! :D
Your code looks much better than mine too.

You maybe should add an example showing how to use?

GJ! :-)


________
Edit:

I got some errors that's listed below:

Notice: Undefined index: tiles in C:\wamp\www\Daniels_Server\content\jj2related\JJ2_ formats\j2l\class.Tileset.php on line 265

Warning: imagecreatetruecolor() [function.imagecreatetruecolor]: Invalid image dimensions in C:\wamp\www\Daniels_Server\content\jj2related\JJ2_ formats\j2l\class.Tileset.php on line 265

Warning: imagecolorallocate(): supplied argument is not a valid Image resource in C:\wamp\www\Daniels_Server\content\jj2related\JJ2_ formats\j2l\class.Tileset.php on line 266

Warning: imagefill(): supplied argument is not a valid Image resource in C:\wamp\www\Daniels_Server\content\jj2related\JJ2_ formats\j2l\class.Tileset.php on line 266

Warning: imagecopy(): supplied argument is not a valid Image resource in C:\wamp\www\Daniels_Server\content\jj2related\JJ2_ formats\j2l\class.Level.php on line 638

On line 265: $iImag e = imagecreatetruecolor(320,(($this->aOffsets['tiles']/10)*32));

Do you know how to fix this? I have tried with multiple j2l files.

my code for the classes:
<?php
require_once("class.Level.php");
$Level = new Level("test.j2l");
//header("Content-type: image/png");
imagepng($Level->getRealPreview());
?>

Stijn
May 31, 2009, 10:06 AM
&lt;?php
require 'class.Level.php';
require 'class.Tileset.php';

$oLevel = new Level('battle1.j2l');

header('Content-type: image/png');
echo $oLevel->getRealPreview();

To see it in action check the J2O downloads and click a file name in the "file contents" part ;)

I've made a few tweaks to it since I published the code there, I'll probably put an updated version online soon.

djazz
May 31, 2009, 10:11 AM
Sorry still not working for me :(

And please make the source files downloadable, that copy-paste adds # signs

Stijn
May 31, 2009, 10:12 AM
Source and documentation updated now... several changes to the code structure, but the most interesting improvement will be that animated, flipped and translucent tiles are now supported.

What does not work for you? It might be hard to debug since the classes still lack proper error handling. I've had little need for that since they work for their primary purpose - previews on J2O - but it's open source so feel free to improve that by yourself ;)

Stijn
May 31, 2009, 11:06 AM
Oh, I see the errors you pasted - strange... "tiles" should be defined at that point since the method that defines it is called from the object constructor... unless the constructor terminates before it reaches that point because it can't find the tileset file. Are you 100% sure the tileset path passed on to the Tileset constructor (line 596, class.Level.php) is correct? Did you perhaps forget to change the Level::ROOT_DIR constant to point to the correct location?

Cpp
May 31, 2009, 01:03 PM
To see it in action check the J2O downloads and click a file name in the "file contents" part ;)This is indeed a nice addition to the website. May I suggest a feature where you can choose to download individual files from the package... assuming the new J2O file storage standard is in place.

EDIT: I just noticed the foreground layer tiling doesn't seem to work.
http://www.jazz2online.com/J2Ov2/downloads/viewfile.php?fileID=6448&sizex=160&sizey=248

EDIT2: Is it just me or is this level displayed entirely black?
http://www.jazz2online.com/J2Ov2/downloads/viewfile.php?fileID=11209&sizex=1024&sizey=1024

Stijn
May 31, 2009, 02:09 PM
Yes @ both. When a tileset is not found it will use black tiles.

We will not allow downloading individual files I think, simply because people upload their stuff as archives to our site, not as individual files :) An option to, for example, ignore music files might be implemented later though.

EDIT: In the case of this particular level it appears that there are two different sets named "pk2.j2t" on J2O. Since there is no way to determine which one is correct based on the J2L file alone, it uses the wrong tileset. The black tiles are references to tiles that don't exist in the "other" pk2.j2t.

Cpp
May 31, 2009, 09:49 PM
EDIT: In the case of this particular level it appears that there are two different sets named "pk2.j2t" on J2O. Since there is no way to determine which one is correct based on the J2L file alone, it uses the wrong tileset. The black tiles are references to tiles that don't exist in the "other" pk2.j2t.Is it not obvious to select the one that comes along in the archive?

Stijn
Jun 1, 2009, 12:43 AM
Not if the code is structured like it is now.

djazz
Jun 1, 2009, 05:56 AM
Ok, got it fixed, I don't know how but it's working :D

Are there any todos left? flipped tiles, have you fixed them? Here is a code for flipping images: http://se2.php.net/manual/en/function.imagecopy.php#85992

More improovements is to put an image where it should be a event

Stijn
Jun 1, 2009, 08:20 AM
Flipped tiles are supported.

Events are certainly possible but image generation would take a lot longer then so it's not a priority for me (the primary use for me personally is still J2O image preview).

djazz
Jun 1, 2009, 11:04 AM
See you've updated the source, but, could you please add a download link, it's so hard to remove all # signs and extra linebreaks

The PHP works rly nice, but it's very slow

Jerrythabest
Jun 3, 2009, 02:51 PM
Has someone started on an AJAX implementation of JJ2 yet? :lol:


Really, reading all this makes me think we're almost ready for that. They should hurry with those 100 THz 50-core processors that are probably needed for playing 'JSJJ2' (JavaScriptJJ2) though.

djazz
Jun 3, 2009, 09:54 PM
Yea, I have thought about that too, but it's very hard I think.

I can now make php exe programs with a GUI, maybe we could make a completely php-based JJ2?

Grytolle
Jun 4, 2009, 01:13 AM
Sounds optimized! Do it man

Jerrythabest
Jun 4, 2009, 03:36 PM
Hmm... I think the best you could do with PHP with stuff like this is by using it as some sort of core layer to build a Javascript on top of it that will handle all the I/O. PHP is good at keeping track of stuff, but I'm not so optimistic about its abilities to react immediately to user input.

Stijn
Jun 4, 2009, 05:05 PM
protip: this will lead nowhere

Seriously, porting JJ2 to javascript+php, what have you been smoking? :p

FQuist
Jun 5, 2009, 03:24 AM
yeah
you don't need php/javascript for that.... html and the canvas element is enough..................

Black Ninja
Jun 5, 2009, 12:25 PM
HTML is a luxury, a JJ2 port using straight Assembly is the way to go. Machine code would be even better!

djazz
Jun 5, 2009, 01:06 PM
It wouldn't be hard to do a JCS based on HTML, CSS, JavaScript and PHP. Just look at my level editor, it has tiles and rules (events)!
Thanks to Stijn for making it easier to read J2l and J2t!

A web-based JJ2 is nearly impossible, I think, with HTML, PHP and JS+Ajax. Because it need to handle TCP and UDP connections.

Stijn
Jun 5, 2009, 01:29 PM
It wouldn't be hard to do a JCS based on HTML, CSS, JavaScript and PHP. Just look at my level editor, it has tiles and rules (events)!
You might find this (http://stijn.fuzic.nl/webjcs) interesting - it was made in the era of firefox 2 and optimized for that browser, so it doesn't look perfect in later versions, but I guess you get the idea.

FQuist made a working version of a collaborative web-based JCS a year or two ago (you could create a level together, and see the changes others made real-time), which that page of mine is based on. Note that back then the J2L and J2T formats were not documented yet.

djazz
Jun 6, 2009, 01:48 AM
Awesome Stijn! does it use any PHP or is it static HTML?

Stijn
Jun 6, 2009, 01:58 AM
It uses some PHP for breaking up a tileset image in 32x32 tile images, but apart from that it's all Javascript/CSS/HTML. The PHP part could probably be improved a lot (to handle .j2t files directly for example).

Jerrythabest
Jun 7, 2009, 03:38 AM
I remember that script... it's so awesome :D

Although I wasn't serious about the AJAX JJ2, it would be pretty awesome :D

CownLeme
Nov 21, 2009, 05:38 PM
Just wondering if anyone could help me with a problem I am having. I need to have a lot o webpages that pull a line of text from a txt file so that when the file is changed, all the HTML files will have the new info when they start.

Any ideas? Please let me know.

SpazAlex0777
Dec 21, 2009, 08:56 AM
Good guide! :D

Sfaizst
Dec 23, 2009, 06:42 PM
Hello
Good Job Guys,
I tryed to create basics on this thread a component for Delphi and i have the most finished but I have a problem with compress2, I have more parameters as you in the function header and i dont know 3 of them, i hope someone can help me.

How the compress2 Function is looking at delphi:
ZlibEx.ZCompress2(inBuffer,inSize,outBuffer,outSiz e,compressionlevel, windowBits,memLevel,strategy)

the first 4 i know (...) and i think the compression level is "max" (9)
WindowBits and memLevel are Integer Values (4byte, in c++ "int")
The strategy have 5 possible values:
- zsDefault(0)
- zsFiltered(1)
- zsHuffman(2)
- zsRLE(3)
- zsFixed(4)

I hope someone can help me with this, when i get this working i post my component for Delphi here.

Sfaizst

Violet CLM
Dec 31, 2009, 12:56 PM
Does anyone know if Data3 in .j2t files is read by JCS, or only JJ2?

djazz
Jan 2, 2010, 09:23 AM
The J2L format
...
Data2 - The Event Map
Each event is a <samp>long</samp> (4 bytes long), so this buffer should be (Layer4Width * Layer4Height * 4) bytes long.

I've done testings and research about this and this is what I have found so far:

Each event looks like this (4 bytes):
struct Event {
char EventID;
char Unknown[3];
}
Where unknown are the event parameters. If there are no parameters, all three are 0.

I've done some deeper research about the Generator event (216), and found out this:
If the Event is 12 (Belt AccR) and the Delay is 3, the structure of the Event Data is:
216 192 48 0
192 / 16 = 12
48 / 16 = 3

But this ONLY applies when Event or Delay is below 15.
Example with values above 15:
Event: 58 (TNT)
Delay: 29

And we get the following result:
216 160 211 1

As you can see, the Event and Delay are more hard coded than before.
I've made a PHP script that calculates the Delay:
<?php
$event=chr(216).chr(160).chr(211).chr(1);
$delay = intval(ceil(((ord(substr($event,2,1)))/16)+(ord(substr($event,3,1))*15)));
?>
Where $event is the four byte part from the Event Map, and this code sets $delay to 29, which is the correct delay.
This code isn't tested with all the combinations of event and delays, as there are a lot of combinations:
256*32 = 8192
All I know it works with the most event and delays, and there may be some exceptions.


If we/someone will make a new JCS or make a JCS patch, or something else related to events, please help me/us.
If this information was useful, or you got any questions/suggestions/further knowledge, please let me know.
Comments are appreciated!

Violet CLM
Jan 2, 2010, 10:39 AM
DJazz: No worries, events are understood.
<a href="http://www.jazz2online.com/jcf/showpost.php?p=405360&postcount=52">Neobeo addresses your research.</a>
<a href="http://www.jazz2online.com/jcsref/node.php?node=84&mode=id">Same info, in a JCSref article.</a> (Scroll down to "Parameters vs. Bitfields.")

djazz
Jan 2, 2010, 04:50 PM
Aah, I haven't seen those pages before, thanks!
That solution is much more simpler than mine.. :D

Anyway, it was so interesting that I made a PHP script to get all the event data and it's parameters.

It's heavily commented, so you easy understand what it does :P


<?php
/*
This code is made by DJazzy of J2NSM, and a few lines are from Stijn's J2L reader PHP-class.
Thanks to Neobeo, who made the research, and to Jerry for his questions.

Changelog:
-changed the behavior for Generators
-some small changes in the parameter loop
-rearranged the code

Todo:
-add error handling
*/

// If you are not using PHP5, then use this function
if(!function_exists('str_split')) {
function str_split($string,$string_length=1) {
if(strlen($string)>$string_length || !$string_length) {
do {
$c = strlen($string);
$parts[] = substr($string,0,$string_length);
$string = substr($string,$string_length);
} while($string !== false);
} else {
$parts = array($string);
}
return $parts;
}
}

function getJcsIni($path) {

// Open the JCS.ini file for reading
$handle = fopen($path, "r");

// Init the main JCSini variable (local)
$JCSini=array();

// Init the read variable, when true: put data in the JCSini variable
$read=false;

// If the file could be opened for reading, continue
if ($handle) {

// Index for each EventID
$i=0;

// Start the while loop, loops until End Of File is found
while (!feof($handle)) {

// Store data buffer, read max 4096 bytes, a row in the file
$buffer = fgets($handle, 4096);

// If it find the row with "Events" in it, set read to true, index to 1 and continue to next loop/row
if(strpos($buffer,'Events') && strlen($buffer)==10 && $read==false) {$i=1;$read=true;continue;}

// If read is true, start store the event info in the JCSini variable
if($read==true) {$JCSini[$i] = explode("|",preg_replace('/(\d+)\=/i','',$buffer));}

// Only store 255 variables
if($i==255) break;

// Increment index with 1
$i++;

} // End WHILE-loop

// Close the file
fclose($handle);

// Return JCSini
return $JCSini;

} // End IF-statement

// If it couldn't open file for reading, return FALSE
return false;

} // End of function

function parseEventMap($eventdata,$JCSini) {

// Split up the EventMap using str_split (PHP5)
$eventdata = str_split($eventdata,4);

// The main variable containing all events
$Event=array();

// Init the EventIndex variable
$i=0;

// Main loop. Loop through each event
foreach($eventdata as $val) {

// If there are no event, or an event with ID 0, skip this and continue on next
if(ord(substr($val,0,1))==0) {$i++;continue;}

// Get the field of bits
list(,$bitfield) = unpack('V', substr($val, 0, 4));

// Make it 32-bit, add zeros until it is 32-bit length
$fullBitfield = str_pad(decbin($bitfield),32,"0",STR_PAD_LEFT);

// Store the Event ID
$Event[$i]['id']=bindec(substr($fullBitfield,-8,8));

// Store the name, trim away NULLs and white spaces
$Event[$i]['name']=trim($JCSini[$Event[$i]['id']][0]);

// Store the short name, that is displayed on the event in JCS. Rows are separated by a "\n" (linebreak)
$Event[$i]['shortname']=trim($JCSini[$Event[$i]['id']][3]).((trim($JCSini[$Event[$i]['id']][4])=="")?"":"\n".trim($JCSini[$Event[$i]['id']][4])."");

// Store difficulty. 0 = Normal, 1 = Easy, 2 = Hard, 3 = MP only
$Event[$i]['difficulty']=bindec(substr($fullBitfield,-10,2));

// Store the illuminate checkbox's value
$Event[$i]['illuminate']=(substr($fullBitfield,-11,1)==1)? true : false;

// Stores true if the event is an generator (event 216), else false
$Event[$i]['generator']=($Event[$i]['id']==216)? true : false;

// Init a temporary variable, that will contain the bitfields names and lengths
$param=array();

// Init a temporary variable, to store all data of the final parameters
$params=array();

// Init the final result variableof the parameters, fill with NULL if some parameter don't exists
$Event[$i]['params']=array(NULL,NULL,NULL,NULL);

// Parameter index holder
$p=0;

// Init variable that holds the last position for reading bitfields
$lastpos=-12;

// Loop through each parameter
for($j=5;$j <= 8; $j++) {

// If there was no parameter at this id, break the loop
if(!($JCSini[$Event[$i]['id']][$j])) {$param[$j]=null;break;}

// Split up name and the bitfield length number
$param[$p]=explode(":",$JCSini[$Event[$i]['id']][$j]);

// Store the current parameter's name
$params[$p]['name']=$param[$p][0];

// Store the value of the current parameter
$params[$p]['value']=bindec(substr($fullBitfield,$lastpos-intval(str_replace("-","",$param[$p][1])),intval(str_replace("-","",$param[$p][1]))));

// This IF-statement handles negative bitfield lengths
if($param[$p][1] < 0 && $params[$p]['value'] >= bindec("".pow(10,intval(str_replace("-","",$param[$p][1]))-1)))
// If current parameter is negative, store it's value again
$params[$p]['value']=-(bindec(pow(10,intval(str_replace("-","",$param[$p][1]))))-$params[$p]['value']);

// Change the last position for reading, going on next parameter
$lastpos-=intval(str_replace("-","",$param[$p][1]));

// Store the temporary data to the Event variable
$Event[$i]['params'][$p]=$params[$p];

// Increment the index with 1
$p++;

} // End FOR-loop

// Do this if the event is a Generator
if($Event[$i]['generator']) {

// Re-store the new EventID
$Event[$i]['id']=$params[0]['value'];

// Re-store the new name
$Event[$i]['name']=trim($JCSini[$Event[$i]['id']][0]);

// Re-store the new shortname
$Event[$i]['shortname']=trim($JCSini[$Event[$i]['id']][3]).((trim($JCSini[$Event[$i]['id']][4])=="")?"":"\n".trim($JCSini[$Event[$i]['id']][4])."");

} // End Generator IF-statement

// Increment the EventIndex variable with 1
$i++;

} // End FOREACH-loop

// Return the Event variable
return $Event;

} // End function



// Path to your JCS.ini
$JCSiniPath = "JCS.ini";

// Read, parse and store it in JCSini
$JCSini = getJcsIni($JCSiniPath);

// Enter the path where your J2L file is located
$infile = "level.j2l";

// Open file for reading
$f=fopen($infile,"r");

// Store the J2L file header
$header = fread($f, 262);

// Init the variable where it store compressed sized
$offsets = array();

// Store the sizes in the offsets variable
list(,$offsets['info_c']) = unpack('V', substr($header, 230, 4));
list(,$offsets['evnt_c']) = unpack('V', substr($header, 238, 4));

// Read the levelinfo stream (needed to move the fread file pointer)
$LevelInfo = gzuncompress(fread($f, $offsets['info_c']));

// Read the actual event stream
$EventData = gzuncompress(fread($f, $offsets['evnt_c']));

// Close the file
fclose($f);

// Make the actual parsing of the eventmap stream
$events=parseEventMap($EventData,$JCSini);

// Output the EventMap
print_r($events);
?>


If you would find any errors or bugs, please report them! Enjoy!

The first working code was made in three hours!

Violet CLM
Jan 2, 2010, 05:10 PM
Very nice! Particularly remembering to account for the negative parameters. Depending on the context that gets used in, one probably also wants to put in some exception for the Generator event, since giving the name of the event being generated is more useful than the number.

djazz
Jan 3, 2010, 04:20 AM
:) Thanks

Depending on the context that gets used in, one probably also wants to put in some exception for the Generator event, since giving the name of the event being generated is more useful than the number.

This is fixed now. The ID, name and shortname are fetched from the Event-parameter with JCS.ini, and the <samp>$Event[$index]['generator']</samp> now contains a boolean you can check whenever it is a generator or not. The event's parameters Event and Delay are still in the params subarray.

Foly
Jan 3, 2010, 09:50 PM
DJazz: No worries, events are understood.
<a href="http://www.jazz2online.com/jcf/showpost.php?p=405360&postcount=52">Neobeo addresses your research.</a>
<a href="http://www.jazz2online.com/jcsref/node.php?node=84&mode=id">Same info, in a JCSref article.</a> (Scroll down to "Parameters vs. Bitfields.")

I read the article about the event theory. Someone probably found it out already but this means you can make up to 1 million triggers. My question is will there be more lag if I use trigger 500.000 and trigger 1 compared to trigger 5 and trigger 1? And will there be more lag if im able to use 1 million triggers with jcs instead of 31? And is there any jcs.ini around that good and has edits like this (+MCE+SCE)?

Also in that article: I tried to give the apple |Speed:-8 in jcs but it appeared in the name of the event, how do you change it in jcs so it will appear in a box where you can edit it?

Violet CLM
Jan 3, 2010, 10:08 PM
You will not be able to use more than 32 (0-31) triggers because that's all that the game code allows for, the trigger events only read the first six bytes and not the others for figuring out the TriggerID.

As for Apple, JCS names use <i>two</i> fields, so that you can have multi-line event names, not just one, so you need a blank field between Apple and the parameters. Compare to, say, Echo.
141=Apple |+|Food |Apple | |Speed:-8

Foly
Jan 4, 2010, 02:25 AM
You will not be able to use more than 32 (0-31) triggers because that's all that the game code allows for, the trigger events only read the first six bytes and not the others for figuring out the TriggerID.

As for Apple, JCS names use <i>two</i> fields, so that you can have multi-line event names, not just one, so you need a blank field between Apple and the parameters. Compare to, say, Echo.
141=Apple |+|Food |Apple | |Speed:-8

Ah, too bad. I guess you need the source code to make some real changes. Anyway, thanks for the information (and all stuff on jcsref).

Stijn
Jan 9, 2010, 01:38 PM
http://mods.jazz2online.com/j2b.txt

(Very) incomplete J2B documentation, not complete by far but at least more readable than Dr. Eggman's article. Thanks to Torkell for additions and corrections. If you have any more information on the format, feel free to share.

Stijn
Jan 30, 2010, 11:58 AM
Python tileset renderer: http://www.jazz2online.com/junk/source.php?file=tileset.py

Can be optimized (it doesn't cache tiles, for example), but I thought, why not put it up here.

Krezack
Jan 31, 2010, 02:01 AM
Python tileset renderer: http://www.jazz2online.com/junk/source.php?file=tileset.py

Can be optimized (it doesn't cache tiles, for example), but I thought, why not put it up here.
Cheers. (y)

Stijn
Jan 31, 2010, 06:52 AM
I believe the J2L specification is wrong:


JcsLayerWidth[0 to 7] Byte (1 byte each) Width of layer as seen in JCS. (decreased by Tile Width setting)
LayerWidth[0 to 7] Byte (1 byte each) Width of layer as used by JJ2 and other DataStreams.


This <em>can't</em> be right, as 1 byte can't hold a value higher than 256 and layer width can be far over 256 (1023, according to JCS). I'm having similar troubles with some other layer data fields. Does anyone have the correct format for the this and other layer data in the J2L file?

blurredd
Jan 31, 2010, 10:40 AM
I thought you would've know the correct specification considering that you made the php Level class. Anyway, here is the struct (http://pastebin.com/f149a3a9e) that I have been using recently. Some of it is based on your Level class.

Stijn
Jan 31, 2010, 10:49 AM
Yes, but I only got that to work after skipping random numbers of bytes (the LayerUnknown parts, for example - their size also doesn't match NeoBeo's article), and I hoped someone else had a more robust solution.

Thanks for the code! I see you also haven't figured it out all, but at least a C struct comes in handy for what I was trying to do.

cooba
Jan 31, 2010, 11:12 AM
#ifdef TSF_RELEASE
#define MAX_ANIMATING_TILES 256
#else
#define MAX_ANIMATING_TILES 128
#endifAre you sure about this? I could have sworn that I once had about 130-140 animations, and it was a 1.23 level. Unfortunately I lost it so I can't check and be sure.

blurredd
Jan 31, 2010, 09:17 PM
I'm sure. You can't even add more than 128 animating tiles in 1.23 JCS.

Stijn
Feb 1, 2010, 03:18 PM
I dunno if this has been posted before (I couldn't find anything about it), so here goes: a list of what set/animation goes with JCS events. Formatted as a Python dictionary (remove the newlines if you want to use it in Python though), but should be easy to convert.


#format: Event ID (as in jcs.ini): (setID, animationID)
events = {
29: (55, 14), #Jazz Level Start
30: (86, 14), #Spaz Level Start
31: (86, 14), #Multiplayer Level Start
33: (1, 30), #Freezer Ammo+3
34: (1, 26), #Bouncer Ammo+3
35: (1, 35), #Seeker Ammo+3
36: (1, 50), #3Way Ammo+3
37: (1, 58), #Toaster Ammo+3
38: (1, 60), #TNT Ammo+3
39: (1, 62), #Gun8 Ammo+3
40: (1, 69), #Gun9 Ammo+3
41: (100, 5), #Still Turtleshell
42: (103, 2), #Swinging Vine
43: (1, 2), #Bomb
44: (68, 85), #Silver Coin
45: (68, 38), #Gold Coin
46: (68, 6), #Gun crate
47: (68, 6), #Carrot crate
48: (68, 6), #1Up crate
49: (68, 4), #Gem barrel
50: (68, 4), #Carrot barrel
51: (68, 4), #1up barrel
52: (68, 6), #Bomb Crate
53: (68, 56), #Freezer Ammo+15
54: (68, 55), #Bouncer Ammo+15
55: (68, 57), #Seeker Ammo+15
56: (68, 58), #3Way Ammo+15
57: (68, 59), #Toaster Ammo+15F
58: (68, 91), #TNT (armed explosive, no pickup)
59: (68, 37), #Airboard
60: (93, 6), #Frozen Green Spring
61: (68, 30), #Gun Fast Fire
62: (68, 6), #Spring Crate
63: (68, 23), #Red Gem +1
64: (68, 23), #Green Gem +1
65: (68, 23), #Blue Gem +1
66: (68, 23), #Purple Gem +1
67: (68, 35), #Super Red Gem
68: (9, 4), #Birdy
69: (68, 4), #Gun Barrel
70: (68, 6), #Gem Crate
71: (68, 71), #Jazz<->Spaz
72: (68, 22), #Carrot Energy +1
73: (68, 83), #Full Energy
74: (68, 32), #Fire Shield
75: (68, 11), #Water Shield
76: (68, 52), #Lightning Shield
79: (68, 34), #Fast Feet
80: (68, 1), #Extra Live
81: (68, 29), #End of Level signpost
83: (68, 15), #Save point signpost
84: (12, 1), #Bonus Level signpost
85: (93, 8), #Red Spring
86: (93, 6), #Green Spring
87: (93, 1), #Blue Spring
88: (68, 73), #Invincibility
89: (68, 88), #Extra Time
90: (68, 43), #Freeze Enemies
91: (93, 9), #Hor Red Spring
92: (93, 7), #Hor Green Spring
93: (93, 2), #Hor Blue Spring
94: (68, 69), #Morph Into Bird
95: (68, 53), #Scenery Trigger Crate
96: (68, 41), #Fly carrot
97: (49, 1), #RectGem Red
98: (49, 1), #RectGem Green
99: (49, 1), #RectGem Blue
100: (99, 1), #Tuf Turt
101: (98, 6), #Tuf Boss
102: (59, 3), #Lab Rat
103: (33, 1), #Dragon
104: (60, 5), #Lizard
105: (16, 1), #Bee
106: (73, 3), #Rapier
107: (85, 1), #Sparks
108: (2, 2), #Bat
109: (96, 7), #Sucker
110: (21, 1), #Caterpillar
111: (19, 3), #Cheshire1
112: (20, 3), #Cheshire2
113: (52, 5), #Hatter
114: (8, 5), #Bilsy Boss
115: (80, 3), #Skeleton
116: (30, 1), #Doggy Dogg
117: (100, 8), #Norm Turtle
118: (53, 1), #Helmut
120: (25, 1), #Demon
123: (32, 1), #Dragon Fly
124: (64, 7), #Monkey
125: (41, 2), #Fat Chick
126: (42, 1), #Fencer
127: (43, 1), #Fish
128: (65, 4), #Moth
129: (94, 1), #Steam
130: (76, 1), #Rotating Rock
131: (68, 61), #Blaster PowerUp
132: (68, 62), #Bouncy PowerUp
133: (68, 63), #Ice gun PowerUp
134: (67, 64), #Seek PowerUp
135: (68, 65), #RF PowerUp
136: (68, 66), #Toaster PowerUP
137: (69, 5), #PIN: Left Paddle
138: (69, 6), #PIN: Right Paddle
139: (69, 1), #PIN: 500 Bump
140: (69, 3), #PIN: Carrot Bump
141: (68, 2), #Apple
142: (68, 3), #Banana
143: (68, 17), #Cherry
144: (68, 72), #Orange
145: (68, 75), #Pear
146: (68, 80), #Pretzel
147: (68, 82), #Strawberry
151: (72, 1), #Queen Boss
152: (96, 5), #Floating Sucker
153: (14, 1), #Bridge
154: (68, 49), #Lemon
155: (68, 51), #Lime
156: (68, 90), #Thing
157: (68, 93), #Watermelon
158: (68, 74), #Peach
159: (68, 39), #Grapes
160: (68, 50), #Lettuce
161: (68, 27), #Eggplant
162: (68, 24), #Cucumb
163: (68, 21), #Soft Drink
164: (68, 76), #Soda Pop
165: (68, 54), #Milk
166: (68, 77), #Pie
167: (68, 13), #Cake
168: (68, 26), #Donut
169: (68, 25), #Cupcake
170: (68, 19), #Chips
171: (68, 14), #Candy
172: (58, 20), #Chocbar
173: (68, 44), #Icecream
174: (68, 12), #Burger
175: (68, 78), #Pizza
176: (68, 33), #Fries
177: (68, 18), #Chicken Leg
178: (68, 81), #Sandwich
179: (68, 89), #Taco
180: (68, 92), #Weenie
181: (68, 40), #Ham
182: (68, 16), #Cheese
183: (60, 3), #Float Lizard
184: (64, 6), #Stand Monkey
190: (74, 2), #Raven
191: (97, 1), #Tube Turtle
192: (68, 36 #Gem Ring
193: (81, 1), #Small Tree
195: (102, 4), #Uterus
196: (102, 8), #Crab
197: (109, 1), #Witch
198: (77, 2), #Rocket Turtle
199: (15, 1), #Bubba
200: (28, 9), #Devil devan boss
201: (27, 2), #Devan (robot boss)
202: (75, 4), #Robot (robot boss)
203: (18, 1), #Carrotus pole
204: (71, 1), #Psych pole
205: (29, 1), #Diamondus pole
209: (48, 1), #Fruit Platform
210: (11, 1), #Boll Platform
211: (51, 1), #Grass Platform
212: (70, 1), #Pink Platform
213: (84, 1), #Sonic Platform
214: (92, 1), #Spike Platform
215: (90, 1), #Spike Boll
217: (38, 1), #Eva
220: (68, 67), #Gun8 Powerup
221: (68, 68), #Gun9 Powerup
223: (91, 1), #3D Spike Boll
226: (60, 1), #Copter
228: (68, 88), #Stopwatch
229: (58, 1), #Jungle Pole
231: (6, 1), #Big Rock
232: (5, 1), #Big Box
235: (83, 3), #Bolly Boss
236: (17, 1), #Butterfly
237: (4, 1), #BeeBoy
241: (101, 7), #Tweedle Boss
244: (44, 2) #CTF Base + Flag
}


For some events the parameter influences what they look like (warps, platforms) or the event is composed out of several animations (CTF bases, Bolly boss). This is not reflected in the list. I tried to choose the sprite closest to the "idle" animation for each event, but in some cases the in-game version might use a slightly different animation.

Events which have no sprite are obviously excluded.

Stijn
Feb 14, 2010, 03:59 PM
Python tileset renderer: http://www.jazz2online.com/junk/source.php?file=tileset.py

Can be optimized (it doesn't cache tiles, for example), but I thought, why not put it up here.

Python episode (J2E) image renderer: http://www.jazz2online.com/junk/source.php?file=episode.py

Stijn
Feb 15, 2010, 12:00 PM
And the PHP port: http://www.jazz2online.com/junk/source.php?file=class.Episode.php

Stijn
Feb 22, 2010, 12:17 PM
Aaand a J2A renderer (for all those myriads of custom animation files out there):

http://www.jazz2online.com/junk/source.php?file=j2a.py

also needs http://www.jazz2online.com/junk/source.php?file=misc.py

The sprite drawing algorithm is a bit quirky, but seems to work and is reasonably fast.

djazz
Mar 1, 2010, 09:38 AM
Does anyone know if Data3 in .j2t files is read by JCS, or only JJ2?

I think JJ2 only, because when I use my Tileset Compiler, all looks fine in JCS, but weird black colors (#000) on transparent areas of tiles in JJ2. I think this has to do with the Data3 (Transparency Mask)

Violet CLM
Mar 1, 2010, 03:53 PM
Sweet! Thanks.

Jojo
Mar 8, 2010, 10:35 AM
Hello Stijn and others,
you may want to have a look at modplug's source code (load_j2b.cpp (http://modplug.svn.sourceforge.net/viewvc/modplug/trunk/OpenMPT/soundlib/load_j2b.cpp?view=log)) for a deeper insight into the J2B format. Everything is working correctly (apart from restart position, I have absolutely no clue where this could be hidden in the file, apparently it's hardcoded?) and I hope that there are at least a few helpful code comments in there. This is certainly more helpful than Dr. Eggman's findings (no offence).

Jojo
Mar 8, 2010, 10:35 AM
Oh and if you have any specific questions about the J2B format (EDIT: Although I see that you pretty much covered most of it already), just let me know. I feel like I could answer most of the stuff. =)

Stijn
Mar 8, 2010, 11:00 AM
Hey Jojo (saga_musix, right? ;)), I've actually been checking the OpenMPT sourceforge page every now and then so I could write a newspost when a new release is out (as it will contain J2B support) - and I've been using foo_dumb's source for some more insight in the format (though I've moved on to other stuff for the time being). OpenMPT's J2B support is based on foo_dumb's, right?

I'm not very knowledgable about digital audio so I'm probably not the one to continue the work, but here's hoping someone uses this information to "finish" the file format spec I started...

Jojo
Mar 8, 2010, 11:21 AM
Yep, this is Saga. :)
I've mostly been relying on the foo_dumb indeed, and veryfied that everyone is playing correctly. I really hope that we can push out a new official version of OpenMPT soon, a testing version has been released a few weeks ago and since then, a few bugs have been discovered that need to be fixed before we can release a public version. This will hopefully be the case in a few weeks!

Violet CLM
Aug 15, 2010, 10:20 PM
Has anyone looked into the save files yet? Primitively, they start out like this:


struct save_file {
char Version[4]; //"23 " for 1.23, "24 " for TSF
long ImgLen; //length of the first zlib-compressed block, which I believe is the screenshot showed in the Load menu
(said zlib-compressed block)
char Name[32]; //Name of save game
char Filename[32]; //Filename of level saved at (not used)
char Levelname[32]; //Said level's name (for display on load menu only)
long Players; //number of players (01-04)
long Gamemode; //traditional values
char Unknown[76];
}

Then there are two pairs of a long representing the length of a zlib-compressed block, followed by that block, then a bunch of seemingly uncompressed data, then three more pairs.

I haven't decompressed any of the other blocks, inspected those 84 bytes in more detail, or poked at those middle data (although I have a suspicion of what they might be) because I don't know if I'm just reinventing somebody else's wheel here. Yes? No?

Violet CLM
Aug 17, 2010, 11:26 PM
Confirmed that the first zlib block is indeed the save image:
<img src="http://www.tachyonlabs.com/sam/diamfromsave.png" />
I didn't bother loading up the menu palette for that render, so that's just a sixteen-color grayscale palette. The format is a simple bitmap, with each byte ranging in value from 0 to 15 and representing (in reverse order) a color on the 128-143 palette line.

ETA: The sixth/final zlib block is the triggers, it's a 32 bit string of zeroes (off) and ones (on). The fifth/penultimate is the dictionary, equivalent to Data3 in a .j2l file and seemingly using the exact same format. The second block seems to contain general info and is always 1444 bytes long. The fourth block is (width*height*4) bytes long, and looks to be the event map, with the otherwise-ignored fourth parameter bit (between difficulty/illuminate and the custom parameters) representing whether the object exists (1) or not (0). Zones, of course, leave this at 0. This event map is read in place of the one stored in the actual .j2l if you die, but is not read at the actual event of loading and will not itself add the sprites it needs.

ETA2: Ammo is stored in the second block, starting at offset 1024. First there are nine longs representing ammo (blaster seems to start at 100; Toaster maxes out at 3168 instead of 99, because it depletes more slowly), then nine 0/1 bytes for whether the gun is powered-up or not. Lives are in the same block at offset 416. Score is two longs starting at offset 968; I assume one is the real score and the other is the score it's displaying, which may or may not have finished adding up to the real score. Sugar rush duration is at 1212; shield id (only 01 through 04 have bullets) at 1132 and shield duration at 1136. Health is at 62 and you will die on loading if it's set to 0, forcing the game to load the event map in the fourth block.

ETA3: In the main file, the uncompressed data between the third and fourth zlib blocks is a long (minimum of 1), followed by (long-1) units (either longs of value 0 or strings of 128 bytes), representing objects current in memory:

struct object {
long unknown;
long original Xpos;
long Ypos;
long current Xpos;
char unknown[48];
byte Strength;
char unknown[67];
}


ETA4: Oh, huh, <a href="http://www.jazz2online.com/wiki/?List_of_Jazz_Jackrabbit_2_Memory_Addresses#Player-dependent%20memory%20addresses">these are some pretty familiar numbers right here.</a> :o

Violet CLM
Oct 17, 2010, 02:20 PM
Another question: does anyone know how data3 in .j2t files actually works? What Neobeo says (that it's the same format as data4) seems incorrect or at least only part of the story.

djazz
Oct 18, 2010, 06:48 AM
Another question: does anyone know how data3 in .j2t files actually works? What Neobeo says (that it's the same format as data4) seems incorrect or at least only part of the story.

Not yet. As no one has seen my tileset compiler, it's appropriate to show it now as I also have trouble with data3.
It works fine in JCS but when playing it in JJ2 it messes up.
Screen:
http://jazzjackrabbit.net/DJ/JJ2_screens/J2Tdata3.png

Source (http://jazzjackrabbit.net/DJ/TilesetCompiler.php.txt) for TilesetCompiler (PHP)

Violet CLM
Oct 18, 2010, 12:45 PM
JCS doesn't read data3 at all, so that makes sense.
Oh, hey, DJazz! Did you ever figure out how layer speeds worked? I feel I'm really close to finding the right equation but I have a sign wrong somewhere or something like that.

djazz
Oct 18, 2010, 09:47 PM
Oh, hey, DJazz! Did you ever figure out how layer speeds worked? I feel I'm really close to finding the right equation but I have a sign wrong somewhere or something like that.

I think so... here is some code from saving levels in WebJCS:

$LayerXSpeed = array(3.375, 2.25, 1, 1, 1, 0.25, 0.125, 0);
$LayerYSpeed = array(3.375, 2.25, 1, 1, 1, 0.25, 0.125, 0);
$LayerAutoXSpeed = array(0, 0, 0, 0, 0, 0, 0, 0);
$LayerAutoYSpeed = array(0, 0, 0, 0, 0, 0, 0, 0);

foreach($LayerXSpeed as $v) $Stream0.= pack("V", $v*65536);
foreach($LayerYSpeed as $v) $Stream0.= pack("V", $v*65536);
foreach($LayerAutoXSpeed as $v) $Stream0.= pack("V", $v*65536);
foreach($LayerAutoYSpeed as $v) $Stream0.= pack("V", $v*65536);

When loading use the same method but reverse (unpack and divide with 65536).

Violet CLM
Oct 18, 2010, 09:58 PM
Oh, sorry, I meant for displaying relative to the window, not storing. They're definitely multiples of 65536.

djazz
Oct 18, 2010, 10:13 PM
Oh, sorry, I meant for displaying relative to the window, not storing. They're definitely multiples of 65536.

Oh, that, ah, well, no :(

I'm using this code for WebJCS (JavaScript):
var w = parallaxC.offsetWidth;
var h = parallaxC.offsetHeight;
var scrollX = LayerDiv.scrollLeft;
var scrollY = LayerDiv.scrollTop;
if(h < 5) return;
var scrollXoffset = Math.round((LayerDiv.offsetWidth)/2-w/2);
var scrollYoffset = Math.round((LayerDiv.offsetHeight)/2-h/2);

var drawn = 0;
var srcpos=null;
for(var l = 7; l >= 0; l--) {
for(var ii=Math.floor((scrollX*layerSpeed[l][0]+scrollXoffset)/32)*32; ii <= Math.ceil((scrollX*layerSpeed[l][0]+scrollXoffset)/32)*32-(scrollX*layerSpeed[l][0]+scrollXoffset)+Math.floor((w)/32)*32+Math.floor((scrollX*layerSpeed[l][0]+scrollXoffset)/32)*32+32; ii+=32) {
for(var jj=Math.floor((scrollY*layerSpeed[l][1]+scrollYoffset)/32)*32; jj <= Math.ceil((scrollY*layerSpeed[l][1]+scrollYoffset)/32)*32-(scrollY*layerSpeed[l][1]+scrollYoffset)+Math.floor((h)/32)*32+Math.floor((scrollY*layerSpeed[l][1]+scrollYoffset)/32)*32+32; jj+=32) {

// Check for tiling layers etc...

// Using this drawing function
srcpos = idXY(tileData[l][ii/32][jj/32] % 1024, 10);
pcc.drawImage(tilesetImage, srcpos[0]*32, srcpos[1]*32, 32, 32, ii-(scrollX*layerSpeed[l][0]+scrollXoffset), jj-(scrollY*layerSpeed[l][1]+scrollYoffset), 32, 32);

It works, but it's not the same

Full source for parallax.js (http://jazzjackrabbit.net/DJ/WebJCS/js/parallax.js)

Still, the wanted result is this:
http://jazzjackrabbit.net/DJ/LvlView/

Violet CLM
Oct 20, 2010, 01:58 PM
Got it! For any resolution (width,height) at screen center (xpos,ypos), the top left corner of a window will be equivalent to, for a layer with speeds (xspeed,yspeed), pixel
(int((xpos-160)*xspeed-(width-320)/2), int((ypos-100)*yspeed-limitvisibleregion-(height-200)/2)).

(xpos, ypos, xspeed, and yspeed are here in nice, pretty units, rather than multiples of 65536; adjust accordingly for your own needs.)
(holds true for layers 1-7 only.)

Neobeo
Oct 21, 2010, 02:08 PM
Another question: does anyone know how data3 in .j2t files actually works? What Neobeo says (that it's the same format as data4) seems incorrect or at least only part of the story.

They should be the same format. Did you find a counter-example?

Not yet. As no one has seen my tileset compiler, it's appropriate to show it now as I also have trouble with data3.
It works fine in JCS but when playing it in JJ2 it messes up.

Source (http://jazzjackrabbit.net/DJ/TilesetCompiler.php.txt) for TilesetCompiler (PHP)
I took a quick look at the source. Try changing line 258 from
$tmp.=pack("N*",bindec(strrev($tmp3)));
to
$tmp.=pack("V*",bindec(strrev($tmp3)));

Jgke
Oct 21, 2010, 08:41 PM
Wow, a Neobeo sighting! Hi!

Violet CLM
Oct 21, 2010, 08:45 PM
They're probably the same format, I haven't looked in great detail, but there's more stuff in there too. Data4 seems to be nothing but 128-byte strings, but Data3 has some information between the strings and thus the offsets are not nice pretty multiples of 128.

djazz
Oct 21, 2010, 09:52 PM
I took a quick look at the source. Try changing line 258 from
$tmp.=pack("N*",bindec(strrev($tmp3)));
to
$tmp.=pack("V*",bindec(strrev($tmp3)));

Same result... I changed to N because V didnt work, forgot to change back

djazz
Oct 22, 2010, 07:12 AM
I made a little comparison between TilesetCompiler's data3 and JCS's data3, as seen in the image below.
I use unpack to "split" the stream into longs, and decbin to convert it to a binary string. The "tiles" are splitted with a line. I used automask in both, and JCS's mask and TilesetCompiler's mask are equal (same as PHP's data3, because of automask). Below the black there's the tileset I used.

http://jazzjackrabbit.net/DJ/JJ2_screens/data3.png

I was using this code:

$stream3 = unpack("V*", $s3); // JCS's data3
$stream3php = unpack("V*", $_Stream3); // TilesetCompiler's data3
$length = count($stream3) > count($stream3php) ? count($stream3) : count($stream3php); // Lengths do not equal, so use the biggest
for($i=1; $i < $length+1; $i+=1) {
if(isset($stream3[$i]))
echo str_replace("1", "X", str_replace("0", ".",str_pad(decbin($stream3[$i]), 32, "0")))."&nbsp;&nbsp;";
else
echo str_repeat("&nbsp;", 34);
if(isset($stream3php[$i]))
echo str_replace("1", "X", str_replace("0", ".",str_pad(decbin($stream3php[$i]), 32, "0")));
else
echo str_repeat("&nbsp;", 32);
echo "<br>";

if($i%32==0) echo str_repeat("-", 32)."&nbsp;&nbsp;".str_repeat("-", 32)."<br>"; // Each 32 row, add a line
}

This code is also in TilesetCompiler's updated source (http://jazzjackrabbit.net/DJ/TilesetCompiler.php.txt).

Neobeo
Oct 22, 2010, 02:01 PM
This code is also in TilesetCompiler's updated source (http://jazzjackrabbit.net/DJ/TilesetCompiler.php.txt).

Here's some stuff I added to your updated source. In particular, the only changes I made are between lines 293 and 324. I added 4 (or 5) new lines of code and modified 2 existing ones:

$isempty=true;
$curpx=0;
$tmp4=""; /********** NEOBEO-CODE **********/
for ($j = 0; $j < 32; $j++) { // each row of pixels in tile
$tmp3="";
for ($k = 0; $k < 32; $k++) { // each pixel in row
//$curpix = ord($s2[$offsets[$i+1]+32*$j+$k]);
$tmp2=imagecolorat($img,$xoff+$k,$yoff+$j);
if($tmp2!=0) {$isempty=false;$tmp3.="1";}
else $tmp3.="0";

$curpx++;
if($curpx==32) {
$curpx=0;
$tmp.=pack("V*",bindec(strrev($tmp3)));
$rle=preg_replace('/(0*)(1+)/e','chr(strlen("$1")).chr(strlen("$2"))',
rtrim($tmp3,0),-1,$count); /********** NEOBEO-CODE **********/
$tmp4.=chr($count).$rle; /********** NEOBEO-CODE **********/
$tmp3="";
}
}
}
$tmp.=$tmp4; /********** NEOBEO-CODE **********/


if(array_search($tmp,$_TransData)===FALSE && ($isempty===false || $i==0)) {

$_TransData[$i]=$tmp;

//echo ".".$tmp."<br>";

$_TransOffsets[$i]=$ioff; /********** NEOBEO-MODIFY **********/
$ioff+=strlen($tmp); /********** NEOBEO-MODIFY **********/
}
elseif(array_search($tmp,$_TransData)!==FALSE && $isempty===false) {
$_TransOffsets[$i]=$_TransOffsets[array_search($tmp,$_TransData)];
}

That should complete the data3 specifications. I'll probably update the specification in the (far) future.

Violet CLM
Oct 22, 2010, 03:48 PM
I'll update data1 in j2l files on the wiki if you explain what that stuff is in words or a struct instead of oddly-colored php.

Neobeo
Oct 22, 2010, 04:00 PM
I'll update data1 in j2l files on the wiki if you explain what that stuff is in words or a struct instead of oddly-colored php.

Basically, it's a run-length encoding similar to that of J2A. After the 128 bytes of transparency mask, there is a variable-length number of bytes that contain instructions saying how to draw stuff. In some sort of pseudocode:

For each row {
Read one byte (this tells you how many contiguous opaque regions there are in the row) and call this byte n
The next 2n bytes tell you alternatingly how many bytes to skip and how many bytes to draw
}

So basically, a fully transparent tile would have 32 "00"s after the 128-byte string (thus a total of 160). Whereas a fully opaque tile would have 32 "01 00 20" (in hex) which means, read 2 bytes, skip 0 pixels, draw 32 pixels ==> total of 128 + 3 * 32 = 224 pixels.

djazz
Oct 23, 2010, 02:53 AM
Here's some stuff I added to your updated source. In particular, the only changes I made are between lines 293 and 324. I added 4 (or 5) new lines of code and modified 2 existing ones:

//PHP CODE

That should complete the data3 specifications. I'll probably update the specification in the (far) future.

Yay, it's working now, thanks Neobeo! I also made an online version (http://jazzjackrabbit.net/DJ/TilesetCompiler/), so you can try it out!
Works in a recent Firefox and in Chrome/Chromium.

Jerrythabest
Oct 23, 2010, 09:14 AM
Basically, it's a run-length encoding similar to that of J2A. After the 128 bytes of transparency mask, there is a variable-length number of bytes that contain instructions saying how to draw stuff. In some sort of pseudocode:

For each row {
Read one byte (this tells you how many contiguous opaque regions there are in the row) and call this byte n
The next 2n bytes tell you alternatingly how many bytes to skip and how many bytes to draw
}

So basically, a fully transparent tile would have 32 "00"s after the 128-byte string (thus a total of 160). Whereas a fully opaque tile would have 32 "01 00 20" (in hex) which means, read 2 bytes, skip 0 pixels, draw 32 pixels ==> total of 128 + 3 * 32 = 224 pixels.

Does this mean you can make parts of a tile transparent regardless of the colours used for those pixels in the tile, by changing the data3 description of the tile?

Violet CLM
Oct 23, 2010, 10:49 AM
Yes. Of course, it would be simpler just not to have a colored pixel there at all.

Jerrythabest
Oct 23, 2010, 10:54 AM
Awesome. So there is a way to use pure #000 black in JJ2 after all.

djazz
Nov 17, 2010, 01:49 PM
Got it! For any resolution (width,height) at screen center (xpos,ypos), the top left corner of a window will be equivalent to, for a layer with speeds (xspeed,yspeed), pixel
(int((xpos-160)*xspeed-(width-320)/2), int((ypos-100)*yspeed-limitvisibleregion-(height-200)/2)).

(xpos, ypos, xspeed, and yspeed are here in nice, pretty units, rather than multiples of 65536; adjust accordingly for your own needs.)
(holds true for layers 1-7 only.)

Heh, it works! Thanks!
See live demo: http://jazzjackrabbit.net/DJ/WebJJ2/

Javascript code out of context:
finalpos = [(rx*Layers[i].width)+Math.floor(x*32-(lastCamPos[0]+160)*Layers[i].XSpeed)+(640-320)/2, (ry*Layers[i].height)+Math.floor(y*32-(lastCamPos[1]+100)*Layers[i].YSpeed)+(480-200)/2];

Violet CLM
Nov 17, 2010, 06:50 PM
If you're going with a fixed resolution, you should probably simplify the <strong>(640-320)/2</strong>-type segments. :P And it looks like you don't have the Limit Visible Region modifier in there, which is 140 pixels at 640x480. (I'm sure it's an easy enough calculation for the other resolutions, I've just never run the numbers.)

ETA: So that future readers don't have to do the math themselves, the LVR modifier is also (height-200)/2, so you needn't divide by two if LVR is on for the layer.

Sfaizst
Jul 9, 2011, 04:51 AM
My first and crappy old source for reading the tileset-pic itself out of a tileset, I'm currently working on new reader units (with classes,...), but this can helpful for pascal / delphi beginners (even if its not very good written) that want to try to decrypt the fileformats


JJFHack.pas: Mainly for reading and uncompressing zlib-compressed files (used ZlibEx for uncompressing, normal zlib should work as well):

unit JJFHack;



interface



uses

SysUtils, Classes, StdCtrls, ZLibEx;






Function StartJJF(FileName:String):Boolean;

procedure LoadBinary(var Memo:TMemo);

procedure LoadBinaryFunc(var Data; Count:Cardinal; Memo:TMemo);

function GetZlibStreamPos:Integer;

procedure AddZlibStreamPos(Position : Integer);

procedure GetCVar(const Position:Integer);

function ReadCVar(Number:Integer):Integer;

procedure GetUVar(const Position:Integer);

function Uncompress(const Index:Integer; MemoryStream:TMemoryStream):Boolean;

function Compress(var MemoryStream:TMemoryStream; const Index:Integer):Boolean;

function ReadString(var MemoryStream:TMemorystream; const Position, StrLength : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):AnsiString;

procedure WriteString(var MemoryStream:TMemorystream; InputStr : Ansistring; const Position, StrLength : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

function ReadInteger(var MemoryStream:TMemorystream; const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Integer;

procedure WriteInteger(var MemoryStream:TMemorystream; const InputInt, Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

function ReadWord(var MemoryStream:TMemorystream; const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Word;

procedure WriteWord(var MemoryStream:TMemorystream; const InputWord:Word; Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

procedure WriteByte(var MemoryStream:TMemorystream; const InputByte:Byte; Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

function ReadByte(var MemoryStream:TMemorystream; const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Byte;

Procedure FreeJJF;



var

FStream : TFileStream;

ZLList, CList, UList : TStringlist;



implementation





//File in einen Stream Laden

Function StartJJF(FileName:String):Boolean;

begin

ZLList := TStringlist.Create;

CList := TStringlist.Create;

UList := TStringlist.Create;

try

FStream := TFileStream.Create(FileName,fmOpenReadWrite);

Result := True;

except

Result := False;

end;

end;



//Lädt einen Stream in Memo (aufruf: LoadBinaryFunc(MS.Memory^,MS.Size,Memo)

procedure LoadBinaryFunc(var Data; Count: Cardinal; Memo: TMemo);

var

line: string[80];

i: Cardinal;

p: PAnsiChar;

nStr: string[4];

const

posStart = 1;

binStart = 7;

ascStart = 57;

HexChars: PAnsiChar = '0123456789ABCDEF';

begin



p := @Data;

line := '';

for i := 0 to Count - 1 do

begin

if (i mod 16) = 0 then

begin

if Length(line) > 0 then

Memo.Lines.Add(line);

FillChar(line, SizeOf(line), ' ');

line[0] := Chr(72);

nStr := Format('%4.4X', [i]);

Move(nStr[1], line[posStart], Length(nStr));

line[posStart + 4] := ':';

end;

if p[i] >= ' ' then

line[i mod 16 + ascStart] := p[i]

else

line[i mod 16 + ascStart] := '.';

line[binStart + 3 * (i mod 16)] := HexChars[(Ord(p[i]) shr 4) and $F];

line[binStart + 3 * (i mod 16) + 1] := HexChars[Ord(p[i]) and $F];

end;

Memo.Lines.Add(line);

end;



//Lädt Momentan genutzen FileStream in Memo

procedure LoadBinary(var Memo:TMemo);

var MStream:TMemoryStream;

begin

MStream.Create;

try

MStream.LoadFromStream(FStream);

LoadBinaryFunc(MStream.Memory^,MStream.Size,Memo);

finally

MStream.Free;

end;

end;



//Speichert Adressen der Komprimierten Streams in Stringlist, Rückgabewert: Anzahl

function GetZlibStreamPos:Integer;

var

I, I2 : Integer;

B1, B2 : Byte;

begin

ZLList.Clear;

I2 := 0;

for I := 0 to FStream.Size do

begin

FStream.Seek(I,soFromBeginning);

FStream.Read(B1,1);

FStream.Seek(I+1,soFromBeginning);

FStream.Read(B2,1);

if (B1=$78) and (B2=$DA) then

begin

I2 := I2+1;

ZLList.Add(IntToStr(I));

end;

end;

Result := I2;

end;



procedure AddZlibStreamPos(Position : Integer);

begin

ZLList.Add(IntToStr(Position));

end;



//Speichert Länge des komprimierten Sterams in StringList

procedure GetCVar(const Position:Integer);

var TmpInt : Integer;

begin

FStream.Seek(Position,soFromBeginning);

FStream.Read(TmpInt,4);

FStream.Seek(0,soFromBeginning);

CList.Add(IntToStr(TmPInt));

end;



function ReadCVar(Number:Integer):Integer;

begin

Result := StrToInt(Clist[Number]);

end;



//Speichert Länge des unkomprimierten Streams in StringList (nicht zwingend notwendig)

procedure GetUVar(const Position:Integer);

var TmpInt : Integer;

begin

FStream.Seek(Position,soFromBeginning);

FStream.Read(TmpInt,4);

FStream.Seek(0,soFromBeginning);

UList.Add(IntToStr(TmPInt));

end;



//Dekomprimiert, Ausgabe sind MemoryStream und Boolean obs geklappt hat

function Uncompress(const Index:Integer; MemoryStream:TMemoryStream):Boolean;

var

InChr : PAnsiChar;

OutBuf : Pointer;

OutBytes : Integer;

begin

try

InChr := AllocMem(StrToInt(CList[Index]));

fStream.Seek(StrtoInt(ZLList[Index]),soFromBeginning);

fStream.Read(InChr^,StrToInt(CList[Index]));

fStream.Seek(0,soFromBeginning);

ZDecompress(InChr,StrToInt(CList[Index]),OutBuf,OutBytes);

MemoryStream.Write(OutBuf^, OutBytes);

if (UList[Index] <> '') and (OutBytes=StrToInt(UList[Index])) then

Result := True Else

Result := False;

except

Result := False;

end;

end;



//Komprimiert, Ausgabe gibts obs geklappt hat und der neue String gleich lang ist

function Compress(var MemoryStream:TMemoryStream; const Index:Integer):Boolean;

var

InChr : PAnsiChar;

OutBuf : Pointer;

OutBytes : Integer;

begin

try

InChr := AllocMem(MemoryStream.Size);

MemoryStream.Position := 0;

MemoryStream.Read(InChr^,MemoryStream.Size);

MemoryStream.Position := 0;

ZCompress(InChr,MemoryStream.Size,OutBuf,OutBytes, zcLevel9);

fStream.Seek(StrtoInt(ZLList[Index]),soFromBeginning);

fStream.Write(OutBuf^,OutBytes);

fStream.Seek(0,soFromBeginning);

if (CList[Index] <> '') and (OutBytes=StrToInt(CList[Index])) then

Result := True Else

Result := False;

except

Result := False;

end;

end;



//Lese String aus Stream, var MemoryStream wird ignoriert wenn Filestream genutzt wird

function ReadString(var MemoryStream:TMemorystream;

const Position, StrLength : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):AnsiString;

var TmpStr : AnsiString;

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

SetLength(TmpStr,StrLength);

FStream.Read(TmpStr[1],StrLength);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

SetLength(TmpStr,StrLength);

MemoryStream.Read(TmpStr[1],StrLength);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

Result := TmpStr;

end;



//Schreibe String in den entsperchenden Stream

procedure WriteString(var MemoryStream:TMemorystream; InputStr : Ansistring;

const Position, StrLength : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

begin

if Length(InputStr) > StrLength then

SetLength(InputStr,StrLength);

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Write(PChar(InputStr)^,StrLength);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Write(PChar(InputStr)^,StrLength);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Lese Integer aus Stream

function ReadInteger(var MemoryStream:TMemorystream;

const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Integer;

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Read(Result,4);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Read(Result,4);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Schreibe Integer in den entsprechende Stream

procedure WriteInteger(var MemoryStream:TMemorystream;

const InputInt, Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Write(InputInt,4);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Write(InputInt,4);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Read Word aus Stream

function ReadWord(var MemoryStream:TMemorystream;

const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Word;

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Read(Result,2);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Read(Result,2);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Schreibe Word in den entsprechende Stream

procedure WriteWord(var MemoryStream:TMemorystream;

const InputWord:Word; Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Write(InputWord,2);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Write(InputWord,2);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Read Byte aus Stream, var MemoryStream wird ignoriert wenn Filestream genutzt wird

function ReadByte(var MemoryStream:TMemorystream;

const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False):Byte;

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Read(Result,1);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Read(Result,1);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Schreibe Word in den entsprechende Stream

procedure WriteByte(var MemoryStream:TMemorystream;

const InputByte:Byte; Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=Fa lse;NotSetBack:Boolean=False);

begin

if UseFileStream = True then

begin

if NotReadLast = False then

FStream.Seek(Position,soFromBeginning);

FStream.Write(InputByte,1);

if NotSetBack = False then

FStream.Seek(0,soFromBeginning);

end Else

begin

if NotReadLast = False then

MemoryStream.Position:=Position;

MemoryStream.Write(InputByte,1);

if NotSetBack = False then

MemoryStream.Position:=0;

end;

end;



//Schreibe das File und schließe alle offenen Streams (der komponente!)

procedure FreeJJF;

begin

try

FStream.Free;

except

end;

try

ZLList.Free;

except

end;

try

CList.Free;

except

end;

try

UList.Free;

except

end;

end;



end.


JJFhackTileSet.pas For reading Tileset informations, never finished it completly (used GR32 Images suite), i think the mask part is not working :D:

unit JJFHackTileset;



interface



uses JJFhack, Math, SysUtils, Classes, StdCtrls, Messages,Windows,

Graphics, GR32, ImgList ,Controls;



type TBit = 0..1;



Function OpenTileSet(const TileSetFileName : String):Byte;

Procedure ReadDataStream(const StreamNumber : Byte);

Procedure ReadPalette;

Function GetUsed_Tiles:Integer;

Procedure GetTileAddr;

Procedure GetMaskAddr;

Procedure GetTileSetImage;

Procedure GetMaskVars;

Procedure GetMaskImage;

Procedure LoadTiles(Bitmap:TBitmap32);

Procedure LoadTiles2(Bitmap:TBitmap);

Procedure LoadTilestoList(ImgList:TImageList);

procedure FlipList(ImgList,FlippedImgList:TImageList);

Procedure LoadMask(Bitmap:TBitmap);

Procedure FreeTileSet;



var

MAX_TILES : Integer;

DataStream : Array [0..3] of TMemoryStream;

DataStreamBool : Array [0..3] of Boolean;

Palette : Array [0..255,0..2] of Byte;

TileAddr,MaskAddr : Array of Integer;

TileColorArray : Array of Array [0..1023] of Array [0..2] of Byte;

MaskColorArray : Array of Array [0..1023] of TColor;

MaskBoolArray : Array of Boolean;

TileSetName : AnsiString;



type

TRGBArray = array[0..0] of TRGBTriple;

pRGBArray = ^TRGBArray;



implementation



function Significance(DigitNr: Byte; Base: Byte): integer; overload;

begin

result := Round(Power(Base,DigitNr));

end;



function Significance(BitNr: Byte): integer; overload;

begin

result := Round(Significance(BitNr,2));

end;



function GetBit(Value: longint; BitNr: Byte): TBit;

begin

result := Value and Significance(BitNr);

end;



function BitToBool(Bit: TBit): Boolean;

begin

case Bit of

0: result := False;

1: result := True;

end;

end;



function RGB2TColor(R, G, B: Byte): Integer;

begin

// convert hexa-decimal values to RGB

Result := R or (G shl 8) or (B shl 16); // ich mochte das OR halt viel lieber

end;



Function OpenTileSet(const TileSetFileName : String):Byte;

var

Tmp : TMemoryStream; //Temporary, this is not used

FVersion : Word; //TileSetVersion

begin

If JJFHack.StartJJF(TileSetFileName) = False then

begin Result := 1; JJFHack.FreeJJF; end Else

begin

If JJFHack.ReadString(Tmp,180,4,True) <> 'TILE' then

begin Result := 2; JJFHack.FreeJJF; end Else

begin

TileSetname := JJFHack.ReadString(Tmp,188,32,True);

FVersion := JJFHack.ReadWord(Tmp,220,True);

If FVersion = $200 then

MAX_TILES := 1024 Else

If FVersion = $201 then

MAX_TILES := 4096 Else

begin

Result := 3;

JJFHack.FreeJJF;

exit;

end;

//Informations for Data1:

JJFhack.GetCVar($E6);

JJFhack.GetUVar($EA);

JJFHack.AddZlibStreamPos($106);

//Informations for Data2:

JJFhack.GetCVar($EE);

JJFhack.GetUVar($F2);

JJFHack.AddZlibStreamPos($106+JJFHack.ReadCVar(0)) ;

//Informations for Data3:

JJFhack.GetCVar($F6);

JJFhack.GetUVar($FA);

JJFHack.AddZlibStreamPos($106+JJFHack.ReadCVar(0)+ JJFHack.ReadCVar(1));

//Informations for Data4:

JJFhack.GetCVar($FE);

JJFhack.GetUVar($102);

JJFHack.AddZlibStreamPos($106+JJFHack.ReadCVar(0)+ JJFHack.ReadCVar(1)+JJFHack.ReadCVar(2));

Result := 0;

end;

end;



end;



Procedure ReadDataStream(const StreamNumber : Byte);

begin

if DataStreamBool[StreamNumber] = False then

begin

DataStream[StreamNumber] := TMemoryStream.Create;

JJFHack.Uncompress(StreamNumber,DataStream[StreamNumber]);

end;

DataStreamBool[StreamNumber] := True;

end;



Procedure ReadPalette;

var

I : Integer;

begin

ReadDataStream(0);

for I := 0 to Length(Palette) - 1 do

begin

Palette[I,0] := JJFHack.ReadByte(DataStream[0],I*4);

Palette[I,1] := JJFHack.ReadByte(DataStream[0],1+I*4);

Palette[I,2] := JJFHack.ReadByte(DataStream[0],2+I*4);

end;

end;



Function GetUsed_Tiles:Integer;

begin

ReadDataStream(0);

Result := JJFHack.ReadInteger(DataStream[0],$400);

end;



Procedure GetTileAddr;

var

TmPAddr, I : Integer;

begin

ReadDataStream(0);

TmpAddr := $404 + (2*MAX_TILES);

SetLength(TileAddr,GetUsed_Tiles);

for I := 0 to Length(TileAddr) - 1 do

TileAddr[I] := JJFHack.ReadInteger(DataStream[0],TmpAddr+(I*4));

end;



Procedure GetMaskAddr;

var

TmPAddr, I : Integer;

begin

ReadDataStream(0);

TmpAddr := $404 + (10*MAX_TILES);

SetLength(MaskAddr,GetUsed_Tiles);

for I := 0 to Length(MaskAddr) - 1 do

MaskAddr[I] := JJFHack.ReadInteger(DataStream[0],TmpAddr+(I*4));

end;



Procedure GetTileSetImage;

var I, I2 : Integer;

begin

ReadPalette;

GetTileAddr;

SetLength(TileColorArray,GetUsed_Tiles);

ReadDataStream(1);

for I := 0 to Length(TileAddr) - 1 do

begin

for I2 := 0 to 1023 do

begin

TileColorArray[I,I2,0] := Palette[JJFHack.ReadByte(DataStream[1],TileAddr[I]+I2),0];

TileColorArray[I,I2,1] := Palette[JJFHack.ReadByte(DataStream[1],TileAddr[I]+I2),1];

TileColorArray[I,I2,2] := Palette[JJFHack.ReadByte(DataStream[1],TileAddr[I]+I2),2];

end;

end;

end;



Procedure GetMaskVars;

var

I, I2, I3, TmPInt, I4 : Integer;

B1, B0 :Byte;

begin

I4 := 0;

GetMaskAddr;

ReadDataStream(2);

SetLength(MaskBoolArray,Length(MaskAddr)*1024);

for I := 0 to Length(MaskAddr) - 1 do

begin

for I2 := 0 to 31 do

begin

TmPInt := JJFHack.ReadInteger(DataStream[2],MaskAddr[I]+(I2*4));

for I3 := 0 to 31 do

begin

MaskBoolArray[I4] := BitToBool(GetBit(TmPInt,I3));

I4 := I4 +1;

end;

end;

end;

end;



Procedure GetMaskImage;

var I, I2 : Integer;

begin

GetMaskVars;

SetLength(MaskColorArray,Length(MaskAddr));

for I := 0 to Length(MaskAddr) do

begin

for I2 := 0 to 1023 do

begin

if MaskBoolArray[MaskAddr[I]+I2] = True then

MaskColorArray[I,I2] := $FFFFFF Else

MaskColorArray[I,I2] := $000000;

end;

end;

end;



Procedure LoadTiles(Bitmap:TBitmap32);

var

I, I2, TilePosX, TilePosY, X, Y : Integer;

LogPal : TMaxLogPalette;

hPal : hPalette;

PLogPal : PLogPalette;

begin

GetTileSetImage;

Bitmap.Width := 320;

Bitmap.Height := (Length(TileAddr)Div 10)*32;

with LogPal do begin

palVersion:=$0300;

palNumEntries:=256;

for i:=0 to 255 do begin

with palPalEntry[i] do begin

peRed := Palette[I,0];

peGreen:= Palette[I,1];

peBlue := Palette[I,2];

peFlags:=0;

end;

end;

end;

pLogPal:=@LogPal;

hPal:=CreatePalette(pLogPal^);

//Bitmap.PixelFormat:=pf8Bit;

//Bitmap.Palette:=hPal;

for I2 := 0 to Length(TileAddr)-1 do

begin

if I2 = 0 then

begin

TilePosX := 0;

TilePosY := 0;

end Else

begin

if TilePosX < 9 then

TilePosX := TilePosX+1 Else

begin

TilePosX := 0;

TilePosY := TilePosY+1;

end;

end;



for I := 0 to 1023 do

begin

if I = 0 then

begin

x := 0;

y := 0;

end Else

begin

if x < 31 then

x := x +1 Else

begin

x := 0;

y := y+1;

end;

end;

Bitmap.Canvas.Pixels[X+(TilePosX*32),Y+(TilePosY*32)] := RGB2TColor(TileColorArray[I2,I,0],TileColorArray[I2,I,1],TileColorArray[I2,I,2]);

end;

end;

end;



Procedure LoadTiles2(Bitmap:TBitmap);

var

I, I2, TilePosX, TilePosY, X, Y : Integer;

LogPal : TMaxLogPalette;

hPal : hPalette;

PLogPal : PLogPalette;

begin

GetTileSetImage;

Bitmap.Width := 320;

Bitmap.Height := (Length(TileAddr)Div 10)*32;

with LogPal do begin

palVersion:=$0300;

palNumEntries:=256;

for i:=0 to 255 do begin

with palPalEntry[i] do begin

peRed := Palette[I,0];

peGreen:= Palette[I,1];

peBlue := Palette[I,2];

peFlags:=0;

end;

end;

end;

pLogPal:=@LogPal;

hPal:=CreatePalette(pLogPal^);

//Bitmap.PixelFormat:=pf8Bit;

//Bitmap.Palette:=hPal;

for I2 := 0 to Length(TileAddr)-1 do

begin

if I2 = 0 then

begin

TilePosX := 0;

TilePosY := 0;

end Else

begin

if TilePosX < 9 then

TilePosX := TilePosX+1 Else

begin

TilePosX := 0;

TilePosY := TilePosY+1;

end;

end;



for I := 0 to 1023 do

begin

if I = 0 then

begin

x := 0;

y := 0;

end Else

begin

if x < 31 then

x := x +1 Else

begin

x := 0;

y := y+1;

end;

end;

Bitmap.Canvas.Pixels[X+(TilePosX*32),Y+(TilePosY*32)] := RGB2TColor(TileColorArray[I2,I,0],TileColorArray[I2,I,1],TileColorArray[I2,I,2]);

end;

end;

end;



Procedure LoadTilestoList(ImgList:TImageList);

var

I, I2, X, Y : Integer;

LogPal : TMaxLogPalette;

hPal : hPalette;

PLogPal : PLogPalette;

Bitmap : TBitmap;

begin

Bitmap := TBitmap.Create;

try

GetTileSetImage;

Bitmap.Width := 32;

Bitmap.Height := 32;

{Bitmap.Width := 320;

Bitmap.Height := (Length(TileAddr)Div 10)*32; }

with LogPal do begin

palVersion:=$0300;

palNumEntries:=256;

for i:=0 to 255 do begin

with palPalEntry[i] do begin

peRed := Palette[I,0];

peGreen:= Palette[I,1];

peBlue := Palette[I,2];

peFlags:=0;

end;

end;

end;

pLogPal:=@LogPal;

hPal:=CreatePalette(pLogPal^);

Bitmap.PixelFormat:=pf8Bit;

Bitmap.Palette:=hPal;

for I2 := 0 to Length(TileAddr)-1 do

begin

for I := 0 to 1023 do

begin

if I = 0 then

begin

x := 0;

y := 0;

end Else

begin

if x < 31 then

x := x +1 Else

begin

x := 0;

y := y+1;

end;

end;

Bitmap.Canvas.Pixels[X,Y] := RGB2TColor(TileColorArray[I2,I,0],TileColorArray[I2,I,1],TileColorArray[I2,I,2]);

end;

ImgList.Add(Bitmap,Nil);

end;

finally

Bitmap.Free;

end;

end;



//Flip einzelnes Img:

procedure FlipImg(const Bitmap: TBitmap);

var Pict:TBitmap;

begin

(* neue Bitmap erzeugen *)

Pict:=TBitmap.Create;

(* zu spiegelnde Bitmap zuweisen *)

Pict.Assign(Bitmap);

//Vertical:

StretchBlt(Pict.Canvas.Handle, 0, 0, Pict.Width,

Pict.Height, Bitmap.Canvas.Handle,

Bitmap.Width-1, 0, -Bitmap.Width, Bitmap.Height, srccopy);

//Horzontal:

{

StretchBlt(Pict.Canvas.Handle, 0, 0,

Pict.Width, Pict.Height, Bitmap.Canvas.Handle,

0, Bitmap.Height-1, Bitmap.Width, -Bitmap.Height, srccopy); }



(* das Alte gegen das Neue Bild austauschen *)

Bitmap.Assign(Pict);

Pict.Free



end;



procedure FlipList(ImgList,FlippedImgList:TImageList);

var

TmpBmp: TBitmap;

I: Integer;

begin

TmpBmp := TBitmap.Create;

TmpBmp.Height :=32;

TmpBmp.Width :=32;

try

for I := 0 to ImgList.Count - 1 do

begin

ImgList.GetBitmap(I,TmpBmp);

FlipImg(TmpBmp);

FlippedImgList.Add(TmpBmp,nil);

end;

finally

TmpBmp.Free;

end;

end;



Procedure LoadMask(Bitmap:TBitmap);

var I, I2, X, Y, TilePosX, TilePosY : Integer;

begin

GetMaskImage;

Bitmap.Width := 320;

Bitmap.Height := (Length(MaskAddr)Div 10)*32;

Bitmap.PixelFormat:=pf8Bit;

for I2 := 0 to Length(MaskAddr)-1 do

begin

if I2 = 0 then

begin

TilePosX := 0;

TilePosY := 0;

end Else

begin

if TilePosX < 9 then

TilePosX := TilePosX+1 Else

begin

TilePosX := 0;

TilePosY := TilePosY+1;

end;

end;



for I := 0 to 1023 do

begin

if I = 0 then

begin

x := 0;

y := 0;

end Else

begin

if x < 31 then

x := x +1 Else

begin

x := 0;

y := y+1;

end;

end;

Bitmap.Canvas.Pixels[X+(TilePosX*32),Y+(TilePosY*32)] := MaskColorArray[I2,I];

end;

end;

end;



Procedure FreeTileSet;

var I:Integer;

begin

for I := 0 to 3 do

begin

DataStream[I].Free;

DataStreamBool[I] := False;

end;

JJFHack.FreeJJF;

end;



end.

djazz
Dec 5, 2011, 11:18 AM
I was able to extract the sound effects/sample data from the Anims.j2a file!

Place Anims.j2a in the same folder as this PHP script, it will create .wav files for each sample.

Download zip containing .wav files: http://djazz.mine.nu/files/sfxTSF_wav.zip

Listen to an extract here:
<audio controls src="http://djazz.mine.nu/files/sfx_63_17.wav">Your browser don't support the HTML5 Audio tag</audio>


error_reporting(E_ALL | E_NOTICE);

$filename = "Anims.j2a";

$AlibStruct = implode('/', array(
"A4Magic",
"VUnknown1",
"VHeaderSize",
"vVersion",
"vUnknown2",
"VFileSize",
"VChecksum",
"VSetCount"
));
$AnimStruct = implode('/', array(
"A4Magic",
"CAnimCount",
"CSampleCount",
"vTotalFrameCount",
"VSampleUnknown",
"VC1",
"VU1",
"VC2",
"VU2",
"VC3",
"VU3",
"VC4",
"VU4"
));
$AnimInfoStruct = implode('/', array(
"vFrameCount",
"vFPS",
"VUnknownZeroes"
));
$AnimFrameStruct = implode('/', array(
"vWidth",
"vHeight",
"vColdspotX",
"vColdspotY",
"vHotspotX",
"vHotspotY",
"vGunspotX",
"vGunspotY",
"VImageAddress",
"VMaskAddress"
));
$RiffStruct = implode('/', array(
"A4Magic",
"VLen",
"A4Format",
"A4SubChunk",
"x44",
"vmul",
"x2",
"Vlength",
"x8",
"Vrate"
));
$WaveStruct = implode('', array(
"A4",
"V",
"A4",
"A4",
"V",
"v",
"v",
"V",
"V",
"V",
"A4",
"V"
));

$f = fopen($filename, 'rb');
$AlibHeader = unpack($AlibStruct, fread($f, 28));
$AlibHeader['SetAddress'] = unpack("V".$AlibHeader['SetCount'], fread($f, $AlibHeader['SetCount']*4)); // Index start at 1

$AnimHeaders = array();
$AnimStreams = array();
$AnimInfo = array();
$AnimImages = array();
$RiffInfo = array();

for($i=0; $i < $AlibHeader['SetCount']; $i++) {
$AnimHeaders[$i] = unpack($AnimStruct, fread($f, 44));
$animStreams[$i] = array();
for($j=0; $j < 4; $j++) {
$animStreams[$i][$j] = gzuncompress(fread($f, $AnimHeaders[$i]['C'.($j+1)]));
}
$AnimInfo[$i] = array();
$AnimImages[$i] = array();
$frameOffset = 0;
for($j=0; $j < $AnimHeaders[$i]['AnimCount']; $j++) {
$AnimInfo[$i][$j] = unpack($AnimInfoStruct, substr($animStreams[$i][0], $j*8, 8));
$minw = 1;
$minh = 1;
$AnimInfo[$i][$j]['Frames'] = array();
for($k=0; $k < $AnimInfo[$i][$j]['FrameCount']; $k++) {
$AnimInfo[$i][$j]['Frames'][$k] = unpack($AnimFrameStruct, substr($animStreams[$i][1], $frameOffset*24, 24));
$frameOffset++;
$minw = max($minw, $AnimInfo[$i][$j]['Frames'][$k]['Width']);
$minh = max($minh, $AnimInfo[$i][$j]['Frames'][$k]['Height']);
}
$AnimInfo[$i][$j]['BoundWidth'] = $minw;
$AnimInfo[$i][$j]['BoundHeight'] = $minh;
/*$AnimImages[$i][$j] = imagecreate($minw, $minh*$AnimInfo[$i][$j]['FrameCount']);
for($k=0; $k < $AnimInfo[$i][$j]['FrameCount']; $k++) {
$imageOffset = $AnimInfo[$i][$j]['Frames'][$k]['ImageAddress'];
}*/

}
$RiffInfo[$i] = array();
$sampleOffset = 0;
for($j=0; $j < $AnimHeaders[$i]['SampleCount']; $j++) {
list(,$sfxLength) = unpack("V", substr($animStreams[$i][3], $sampleOffset, 4));
$RiffInfo[$i][$j] = unpack($RiffStruct, substr($animStreams[$i][3], $sampleOffset+4, $sfxLength-8));
$mul = $RiffInfo[$i][$j]['mul'] / 4 + 1;
$length = $RiffInfo[$i][$j]['length'];
$rate = $RiffInfo[$i][$j]['rate'];
$time = $length / $rate;
$length *= $mul;

$WaveHeader = pack($WaveStruct, "RIFF",
$length + 36,
"WAVE",
"fmt ",
16,
1,
1,
$rate,
$rate * $mul,
$mul * 0x80001,
"data",
$length
);

$sampleData = substr($animStreams[$i][3], $sampleOffset+4+80, $sfxLength-8-80);


for($k=0; $k < strlen($sampleData); $k++) {
$sampleData[$k] = chr(ord($sampleData[$k]) ^ ($mul << 7));
}
echo "writing sfx_".$i."_".$j.".wav ($time s)\n";
file_put_contents('sfx_'.$i.'_'.$j.'.wav', $WaveHeader . $sampleData);
$sampleOffset += $sfxLength;
}

}

fclose($f);



echo "success";

Violet CLM
Dec 5, 2011, 11:20 AM
...is that an unused Lori hurt sound? You can definitely tell why it didn't work out.

djazz
Dec 5, 2011, 11:28 AM
unused

The Anims.j2a library contains lots lots of unused sounds, and animations.

DoubleGJ
Dec 5, 2011, 01:34 PM
I thought these were all extracted before? Because I have a complete folder of JJ2 samples in wav on my PC, and if I remember well I got them from somewhere around here.

Neobeo
Dec 5, 2011, 09:12 PM
Well, while we're submitting code I thought I should submit a full sound-playing C# code that doesn't use any libraries outside the .NET framework. This plays the input sample number as per the Ambient Sound sequence. For TSF, sound #306 is called sLORISOUNDS_TOUCH. Too bad ambient sound only goes up to 255 though...

using System;
using System.IO;
using System.IO.Compression;

namespace jj2sound
{
class Program
{
static readonly byte[] magic = { 82, 73, 70, 70, 87, 65, 86, 69, 102, 109, 116, 32,
16, 0, 0, 0, 1, 0, 1, 0, 100, 97, 116, 97 };
static void Main()
{
using (var br = new BinaryReader(File.OpenRead(@"D:\Games\Jazz2TSF\Anims.j2a")))
{
br.ReadBytes(24);
int i, s, sets = br.ReadInt32();
while (true)
{
Console.Write("Play sample #");
if (!int.TryParse(Console.ReadLine(), out s) || s < 0) break;

for (i = 0; i < sets; i++)
{
br.BaseStream.Position = 28 + i * 4;
br.BaseStream.Position = br.ReadInt32() + 5;
int samples = br.ReadByte();
if (s < samples) break;
else s -= samples;
}
if (i == sets) break;

br.ReadBytes(6);
int[] size = new int[8]; for (i = 0; i < 8; i++) size[i] = br.ReadInt32();
br.ReadBytes(size[0] + size[2] + size[4] + 2);

using (var bz = new BinaryReader(new DeflateStream(br.BaseStream, 0, true)))
{
for (i = 0; i < s; i++) bz.ReadBytes(bz.ReadInt32() - 4);
bz.ReadBytes(64);
int mul = bz.ReadInt16() / 4 + 1; bz.ReadInt16();
int length = bz.ReadInt32(); bz.ReadInt64();
int rate = bz.ReadInt32(); bz.ReadInt64();
Console.WriteLine("Length: {0:0.000}s", (double)length / rate);
length *= mul;

using (var bw = new BinaryWriter(new MemoryStream()))
{
bw.Write(magic, 0, 4);
bw.Write(length + 36);
bw.Write(magic, 4, 16);
bw.Write(rate);
bw.Write(rate * mul);
bw.Write(mul * 0x80001);
bw.Write(magic, 20, 4);
bw.Write(length);
for (i = 0; i < length; i++) bw.Write((byte)(bz.ReadByte() ^ (mul << 7)));
bw.Seek(0, 0);
new System.Media.SoundPlayer(bw.BaseStream).PlaySync() ;
}
}
}
}
Console.WriteLine("Sample not found. Press any key to continue.");
Console.ReadKey();
}
}
}

Also, I'm pretty sure I uploaded a bunch of WAVs back in the day, but that must have been at least a few years ago.

Violet CLM
Dec 5, 2011, 09:46 PM
Mind if I steal that?

Neobeo
Dec 5, 2011, 10:00 PM
Sure, go ahead!

djazz
Dec 5, 2011, 11:49 PM
NEOBEO's CODE

I edited my post above with the edited code to output wav files, once again, thanks for letting me steal your code ^^

Btw, found two new weird lori sounds:
<audio controls src="http://djazz.mine.nu/files/sfx_63_9.wav">NO HTML5 SUPPORT</audio>
<audio controls src="http://djazz.mine.nu/files/sfx_63_10.wav">NO HTML5 SUPPORT</audio>

Wtf?
<audio controls src="http://djazz.mine.nu/files/sfx_113_4.wav">NO HTML5 SUPPORT</audio>

Enjoy!

DoubleGJ
Dec 6, 2011, 02:04 AM
I suppose these two Lori samples were meant for a new intro, I believe it was said that one was planned as well as a whole storyline for TSF, but it all got scrapped.

What about this one, guys? :D

<audio controls src="http://dl.dropbox.com/u/38637435/derp/set077snd002.wav">NO HTML5 SUPPORT</audio>

Jerrythabest
Dec 6, 2011, 10:03 AM
Too bad ambient sound only goes up to 255 though...Over the past years we've done so many things with JJ2 by figuring out how it works down below. Really, isn't there some way to make JJ2 play samples with a higher index? Has anyone ever tried to find out?

Violet CLM
Dec 6, 2011, 10:48 AM
I can't imagine you could do anything about that in JCS. It's not like Set Light where the parameter sizes in JCS.ini don't match up with how JJ2 reads them. There are more parameter bits following those used for specifying the sample, but they're dedicated to Amplify and so on.

Jerrythabest
Dec 6, 2011, 11:45 AM
For JJ2 to be able to look up a sound sample with an ID above 255 it needs to address those with at least a two-byte number. Since it gets only one byte from the event parameter, maybe we can somehow get it to fill the other(s) with something else than zeros. It would probably be a dirty bug if this is possible, but little would surprise me anymore.

Neobeo
Dec 6, 2011, 06:03 PM
For JJ2 to be able to look up a sound sample with an ID above 255 it needs to address those with at least a two-byte number. Since it gets only one byte from the event parameter, maybe we can somehow get it to fill the other(s) with something else than zeros. It would probably be a dirty bug if this is possible, but little would surprise me anymore.

Seems terribly unlikely. But it doesn't take much hex editing to get it to read a 9-bit number though. For 1.23 Jazz2.exe, patching the byte at 0x10820 from "08" to "09" does this. So you could place, say ambient sound 288 (sample = 132 + amplify = 1), which plays the sugar rush music.

djazz
Dec 10, 2011, 10:35 PM
I made a JJ2 sfx soundboard (http://djazz.mine.nu/apps/jj2soundboard/) in JavaScript.
It's multithreaded, the worker thread reads and parsers the Anims.j2a file and creates wave-buffers.
In the main thread, it creates a button for each sound, sorted after set.
Enjoy!

It requires Chrome and 1 Gb of RAM!

Neobeo
Dec 22, 2011, 04:54 PM
Since I get a new page claim, I thought I'd occupy this with one of JJ2's most elusive file formats -- the Jazz2 Cinematic Files (or J2Vs).

The J2V File Format
Like all other file formats, the file begins with a J2V Header, followed by 4 ZLIB streams. It's worth noting that the ZLIB streams are sync flushed every few frames, which explains the 00 00 FF FF at the end of every stream. So essentially the 4 streams are interleaved: Data1, Data2, Data3, Data4, back to Data1 etc. With the compressed size preceding each stream.

Another thing to note is that it only contains video data. I will talk about where the audio data comes from towards the end of the post.

The Header

struct J2V_Header {
char Magic[8] = "CineFeed";
int FileSize;
int CRC32; // of the lowercase filename, e.g. "intro" or "endinglq"
int Width;
int Height;
short BitsPerPixel;
short DelayBetweenFrames; // in milliseconds
int TotalFrames;
int MaxUSize1;
int MaxUSize2;
int MaxUSize3;
int MaxUSize4;
int MaxCSize;
}

The header is followed by 4 interleaved sync-flushed zlib streams. How JJ2 does it is to allocate 5 memory buffers of the 5 sizes given at the end of the header. Each "compressed string of bytes" is read and then uncompressed into the corresponding uncompressed buffer. But since we're in an age with unlimited memory, our best approach would be simply be to concatenate everything into one long buffer.

Data1 contains frame data given as instructions per row, Data2 contains absolute x offsets, Data3 contains relative y offsets, and Data4 contains raw palette and pixel data.

Data1 - Frame Data
This starts with a codebyte of either 0x00 (which means no palette), or 0x01 (which means read 1024 bytes from Data4 and update palette), followed by Height (i.e. 480 or 200) sets of instructions.

Here are the possible instructions:
0x00: Read the next two bytes, then copy that many bytes from Data4.
0x01-0x7F: Copy this many bytes from Data4.
0x80: End of line.
0x81: Read the next two bytes, then copy that many bytes from some offset given by Data2 and Data3.
0x82-0xFF: Read the next two bytes, then copy that many bytes minus 106 from some offset as above.

Data2 - Absolute X Offset
Just a bunch of offsets, in shorts.

Data3 - Relative Y Offset
Another bunch of offsets, in bytes.

Data4 - Raw Palette and Pixel Data
Palette data is pretty much 256 colours in RGBA format, for a total of 1024 bytes. Pixels are 1 byte each.

How the copying from Data2/Data3 is done
If you're copying to (x, y) on the current frame, you want to copy from (read(data2), y + read(data1) - 127) from the previous frame.

Sound Data
This is obtained from the corresponding SoundFXList.Name from Data.J2D, where it can be read as the following struct:

struct SoundFXList {
int Frame;
int Sample;
int Volume;
int Panning;
}
which gives a list of which frames sounds are played.

<hr>

Finally, here's some C# code which needs nothing but the .NET framework and compilation under unsafe code:

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace jj2video
{
class Program
{
static void Main()
{
var d = new BinaryReader[4];
int i, frame = 0, frames, width, height, delay;
using (var br = new BinaryReader(File.OpenRead(@"D:\Games\Jazz2\Intro.j2v")))
{
br.ReadBytes(16); width = br.ReadInt32(); height = br.ReadInt32();
br.ReadInt16(); delay = br.ReadInt16(); frames = br.ReadInt32();
br.ReadBytes(20);

var strs = new MemoryStream[4]; for (i = 0; i < 4; i++) strs[i] = new MemoryStream();
for (i = 0; br.BaseStream.Position != br.BaseStream.Length; i++)
{
foreach (var str in strs)
{
int len = br.ReadInt32();
str.Write(br.ReadBytes(len), 0, len);
}
}
for (i = 0; i < 4; i++)
{
strs[i].Position = 2;
d[i] = new BinaryReader(new System.IO.Compression.DeflateStream(strs[i], 0));
}
}

var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
var pb = new PictureBox() { Size = new Size(width, height) };
var f = new Form() { ClientSize = pb.Size }; f.Controls.Add(pb);
var t = new Timer() { Enabled = true, Interval = delay };
t.Tick += new EventHandler((s, e) =>
{
if (frame++ >= frames) return;

var data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, bmp.PixelFormat);
unsafe
{
var pixels = (byte*)data.Scan0;
var copy = new byte[data.Stride * height];
for (i = 0; i < copy.Length; i++) copy[i] = pixels[i];
if (d[0].ReadByte() == 1)
{
var p = bmp.Palette;
for (i = 0; i < 256; i++) p.Entries[i] = Color.FromArgb(d[3].ReadByte(), d[3].ReadByte(), d[3].ReadInt16());
bmp.Palette = p;
}

for (int y = 0; y < height; y++)
{
byte c;
int x = 0;
while ((c = d[0].ReadByte()) != 128)
{
if (c < 128)
{
int u = c == 0 ? d[0].ReadInt16() : c;
for (i = 0; i < u; i++) pixels[y * data.Stride + x++] = d[3].ReadByte();
}
else
{
int u = c == 0x81 ? d[0].ReadInt16() : c - 106;
int n = d[1].ReadInt16() + (d[2].ReadByte() + y - 127) * data.Stride;
for (i = 0; i < u; i++) pixels[y * data.Stride + x++] = copy[n++];
}
}
}
}
bmp.UnlockBits(data);
pb.Image = bmp;
});
Application.Run(f);
}
}
}

djazz
Jan 19, 2012, 01:00 PM
Nice neobeo, will try that out someday!

Anyway, got a message from Michiel today. You know, the creator of JCS.
But also some bad news, but we've heard that before. I quote:

Hey! As you made JCS, I wonder if you still have the J2L and J2T file format specifications?
As you see we have almost figured out everything, just a few places that have unknown purposes, have a look:
http://www.jazz2online.com/wiki/J2L_File_Format
http://www.jazz2online.com/wiki/J2T_File_FormatSorry, I can't get a grip on Arjan, he's promised to sort out what license to release the source code under, but he just doesn't make any progress.. I gave up on him. And he/his company holds the rights, not me, so I can't just release it.
I can find you the J2L and J2T structs though..
#ifndef __FILEFORMATS_H__
#define __FILEFORMATS_H__

#ifdef _LARGE_TILE_LIMIT
/*-------------------------------------------------------------------------------------------------
New version, large tile limits
-------------------------------------------------------------------------------------------------*/

#define MAXTILES 4096
#define MAXANIMTILES 256

#define TILESET_LATESTVERSION 0x201 // Version 2.01
#define LEVEL_LATESTVERSION 0x203 // Version 2.03
#else
/*-------------------------------------------------------------------------------------------------
Old version, old tile limits
-------------------------------------------------------------------------------------------------*/
#define MAXTILES 1024
#define MAXANIMTILES 128

#define TILESET_LATESTVERSION 0x200 // Version 2.00
#define LEVEL_LATESTVERSION 0x202 // Version 2.02
#endif

/*-------------------------------------------------------------------------------------------------
Constants, limits, etc
-------------------------------------------------------------------------------------------------*/
#define MAXTILES_OLD 1024
#define MAXTILESMASK (MAXTILES-1)
#define MAXTILESMASK_OLD (MAXTILES_OLD-1)
#define MAXLAYERS 8
#define MAXANIMTILES_OLD 128
#define MAXANIMTILEFRAMES 64

#define ALIB_LATESTVERSION 0x200 // Version 2.00
#define PLIB_LATESTVERSION 0x100 // Version 1.00

#define SECTIONTILES 4 // Width of section
#define SECTIONAND 3 // AND Mask
#define SECTIONSHIFT 2 // Shift count
#define SECTIONSIZE (SECTIONTILES*sizeof(word)) // Size of section
#define SECTIONWIDTH (SECTIONTILES*32) // Width in pixels
#define SECTIONWIDTHSHIFT (SECTIONSHIFT+5) // Shifter for width pixels
#define SECTIONHEIGHT 32 // Height in pixels
#define SECTIONHEIGHTSHIFT 5 // Shifter for height pixels
#define WIDTH_IN_SECTIONS(x) (((x)+SECTIONTILES-1) >> SECTIONSHIFT)

#define TILETYPE_NORMAL 0
#define TILETYPE_TRANS 1
#define TILETYPE_MENU 4

#pragma pack(push, 1)
#pragma warning(disable : 4200)



/*-------------------------------------------------------------------------------------------------
Level file
-------------------------------------------------------------------------------------------------*/



//
// Level header structure
//
typedef struct
{
char ID[4];
byte Magic[4];
char Name[32];
word Version;
dword FileSize, FileCRC;
dword HdrPackSize, HdrUnPackSize;
dword EventsPackSize, EventsUnPackSize;
dword SectionsPackSize, SectionsUnPackSize;
dword LayersPackSize, LayersUnPackSize;
} LevelFileHdr;



//
// Animating tile dump structure in level file
//
typedef struct
{
word DelayTime;
word DelayTimeAdder;
word PingPongDelay;
byte PingPong;
byte Speed;
byte NumFrames;
union
{
word DumpFrames[MAXANIMTILEFRAMES];
word *pFrames;
};
} AnimTile;



//
// Level dump structure, contains most of the level data
//
typedef struct
{
// Editor
int LayerX;
int LayerY;
byte CurLayer;
// General
byte AmbientMin;
byte AmbientStart;
word NumAnimTiles;
byte SplitScreenType;
byte MultiPlayer;
dword sizeof_LevelGameHdr;
// Strings
char LevelName[32];
char TileSet[32];
char BonusLevel[32];
char NextLevel[32];
char SecretLevel[32];
char MusicFile[32];
char HelpString[16][512];
// Layer data
dword lFlags[MAXLAYERS];
byte lType[MAXLAYERS];
byte lUsed[MAXLAYERS];
dword lEditorWidth[MAXLAYERS];
dword lWidth[MAXLAYERS];
dword lHeight[MAXLAYERS];
int lDepth[MAXLAYERS];
byte lDetailLevel[MAXLAYERS];
int lWaveX[MAXLAYERS];
int lWaveY[MAXLAYERS];
int lSpeedX[MAXLAYERS];
int lSpeedY[MAXLAYERS];
int lAutoSpeedX[MAXLAYERS];
int lAutoSpeedY[MAXLAYERS];
byte lTextureType[MAXLAYERS];
byte lTextureParam[MAXLAYERS][3];
// Tiles stuff
word tRemap;
int tEvent[MAXTILES];
byte tFlip[MAXTILES];
byte tType[MAXTILES];
byte tXMask[MAXTILES]; // ** UNUSED **
AnimTile tAnim[MAXANIMTILES];
// Reserved
dword Reserved[128];
} LevelGameHdr;



//
// Old level dump structure with less tiles and animtiles
//
typedef struct
{
// Editor
int LayerX;
int LayerY;
byte CurLayer;
// General
byte AmbientMin;
byte AmbientStart;
word NumAnimTiles;
byte SplitScreenType;
byte MultiPlayer;
dword sizeof_LevelGameHdr;
// Strings
char LevelName[32];
char TileSet[32];
char BonusLevel[32];
char NextLevel[32];
char SecretLevel[32];
char MusicFile[32];
char HelpString[16][512];
// Layer data
dword lFlags[MAXLAYERS];
byte lType[MAXLAYERS];
byte lUsed[MAXLAYERS];
dword lEditorWidth[MAXLAYERS];
dword lWidth[MAXLAYERS];
dword lHeight[MAXLAYERS];
int lDepth[MAXLAYERS];
byte lDetailLevel[MAXLAYERS];
int lWaveX[MAXLAYERS];
int lWaveY[MAXLAYERS];
int lSpeedX[MAXLAYERS];
int lSpeedY[MAXLAYERS];
int lAutoSpeedX[MAXLAYERS];
int lAutoSpeedY[MAXLAYERS];
byte lTextureType[MAXLAYERS];
byte lTextureParam[MAXLAYERS][3];
// Tiles stuff
word tRemap;
int tEvent[MAXTILES_OLD];
byte tFlip[MAXTILES_OLD];
byte tType[MAXTILES_OLD];
byte tXMask[MAXTILES_OLD]; // ** UNUSED **
AnimTile tAnim[MAXANIMTILES_OLD];
} LevelGameHdrOld;



//
// Convert old LevelGameHdrOld struct to new LevelGameHdr struct
//
#ifdef __cplusplus
inline void LevelGameHdr_202_To_203(LevelGameHdr *n)
#else
void __inline LevelGameHdr_202_To_203(LevelGameHdr *n)
#endif
{
const int TilesDiff = MAXTILES - MAXTILES_OLD;
const int AnimsDiff = MAXANIMTILES - MAXANIMTILES_OLD;
LevelGameHdrOld *o = (LevelGameHdrOld *)n;

// Copy old->new position
memcpy(n->tAnim, o->tAnim, MAXANIMTILES_OLD * sizeof(AnimTile));
memcpy(n->tXMask, o->tXMask, MAXTILES_OLD * sizeof(byte));
memcpy(n->tType, o->tType, MAXTILES_OLD * sizeof(byte));
memcpy(n->tFlip, o->tFlip, MAXTILES_OLD * sizeof(byte));
memcpy(n->tEvent, o->tEvent, MAXTILES_OLD * sizeof(int));

// Wipe out empty part of arrays
memset(n->tEvent + MAXTILES_OLD, 0, TilesDiff * sizeof(int));
memset(n->tFlip + MAXTILES_OLD, 0, TilesDiff * sizeof(byte));
memset(n->tType + MAXTILES_OLD, 0, TilesDiff * sizeof(byte));
memset(n->tXMask + MAXTILES_OLD, 0, TilesDiff * sizeof(byte));
memset(n->tAnim + MAXANIMTILES_OLD, 0, AnimsDiff * sizeof(AnimTile));
memset(n->Reserved, 0, 128 * sizeof(dword));
}




/*-------------------------------------------------------------------------------------------------
Tile file
-------------------------------------------------------------------------------------------------*/



//
// Tile file header structure
//
typedef struct
{
char ID[4]; // "TILE"
byte Magic[4]; // Magic number
char Name[32]; // Name of tileset
word Version;
dword FileSize, FileCRC; // Size of tile file
dword HdrPackSize, HdrUnPackSize; // Header packed and unpacked size
dword DataPackSize, DataUnPackSize; // Tiles Data packed and unpacked size
dword CodePackSize, CodeUnPackSize; // Tiles Code packed and unpacked size
dword MasksPackSize, MasksUnPackSize; // Tiles Masks packed and unpacked size
} TileFileHdr;



//
// Tile file structure
//
typedef struct
{
// Create info block
byte Palette[1024]; // Palette for tileset
dword NumTiles; // Amount of tiles
byte Full[MAXTILES*2]; // Tiles filled flags
byte *Data[MAXTILES*2]; // -> Tiles (OFFSET when in a file)
byte *Code[MAXTILES*2]; // -> Codes (OFFSET when in a file)
dword *Mask[MAXTILES*2]; // -> Masks (OFFSET when in a file)
} TileFile;



//
// Old tile file structure
//
typedef struct
{
// Create info block
byte Palette[1024]; // Palette for tileset
dword NumTiles; // Amount of tiles
byte Full[MAXTILES_OLD*2]; // Tiles filled flags
byte *Data[MAXTILES_OLD*2]; // -> Tiles (OFFSET when in a file)
byte *Code[MAXTILES_OLD*2]; // -> Codes (OFFSET when in a file)
dword *Mask[MAXTILES_OLD*2]; // -> Masks (OFFSET when in a file)
} TileFileOld;



//
// Convert old TileFileOld struct to new TileFile struct
//
#ifdef __cplusplus
inline void TileFile_200_To_201(TileFile *n)
#else
void __inline TileFile_200_To_201(TileFile *n)
#endif
{
const int TilesDiff = MAXTILES - MAXTILES_OLD;
TileFileOld *o = (TileFileOld *)n; // ->New tilefile

// Copy old->new position
memcpy(n->Mask+MAXTILES, o->Mask+MAXTILES_OLD, MAXTILES_OLD * sizeof(dword *));
memcpy(n->Mask, o->Mask, MAXTILES_OLD * sizeof(dword *));
memcpy(n->Code+MAXTILES, o->Code+MAXTILES_OLD, MAXTILES_OLD * sizeof(byte *));
memcpy(n->Code, o->Code, MAXTILES_OLD * sizeof(byte *));
memcpy(n->Data+MAXTILES, o->Data+MAXTILES_OLD, MAXTILES_OLD * sizeof(byte *));
memcpy(n->Data, o->Data, MAXTILES_OLD * sizeof(byte *));
memcpy(n->Full+MAXTILES, o->Full+MAXTILES_OLD, MAXTILES_OLD * sizeof(byte));
memcpy(n->Full, o->Full, MAXTILES_OLD * sizeof(byte));

// Wipe out empty part of arrays
memset(n->Full + MAXTILES_OLD, 0, TilesDiff * sizeof(byte));
memset(n->Full + MAXTILES_OLD + MAXTILES, 0, TilesDiff * sizeof(byte));
memset(n->Data + MAXTILES_OLD, 0, TilesDiff * sizeof(byte *));
memset(n->Data + MAXTILES_OLD + MAXTILES, 0, TilesDiff * sizeof(byte *));
memset(n->Code + MAXTILES_OLD, 0, TilesDiff * sizeof(byte *));
memset(n->Code + MAXTILES_OLD + MAXTILES, 0, TilesDiff * sizeof(byte *));
memset(n->Mask + MAXTILES_OLD, 0, TilesDiff * sizeof(dword *));
memset(n->Mask + MAXTILES_OLD + MAXTILES, 0, TilesDiff * sizeof(dword *));
}



/*-------------------------------------------------------------------------------------------------
Library file
-------------------------------------------------------------------------------------------------*/


//
// Library file header
//
typedef struct
{
char ID[4]; // Should be "PLIB"
byte Magic[4]; // Magic number
dword Version; // Version number
dword FileSize, FileCRC; // Size of music file
dword HdrPackSize, HdrUnPackSize; // Header packed and unpackacked size
} LibraryFileHdr;



//
// File in library header
//
typedef struct
{
char Name[32]; // File name or tag
dword Type; // Type of file
dword Offset, FileCRC; // Position in library file + unpacked file CRC
dword PackSize, UnPackSize; // Packed and unpacked size
} LibFile;



//
// Picture file in memory and as stored in library
//
typedef struct
{
int Width; // Width of image
int Height; // Height of image
int BPP; // Bit depth of image
byte Palette[256*4]; // Palette
byte Image[]; // Image data
} Picture;



/*-------------------------------------------------------------------------------------------------
Animation library file
-------------------------------------------------------------------------------------------------*/



//
// Animation library file header structure
//
typedef struct
{
char ID[4]; // "ALIB"
byte Magic[4]; // 0x00,0xBA,0xBE,0x00
dword HdrSize; // Header size
word Version; // Animation lib version
byte sizeof_Tanimations, sizeof_Tframes; // sizeof(Tanimations), sizeof(Tframes)
dword FileSize, FileCRC; // Size of file
dword NumAnims; // Amount of animations
dword AnmOffsets[]; // Offsets to animations in file
} AnimFileHdr;



//
// Animation library file animation dump
//
typedef struct
{
char ID[4]; // "ANIM"
byte NumStates; // Amount of states in anim
byte NumSamples; // Amount of samples in anim
word NumFrames; // Amount of frames in all anim states
dword BaseSampleNum; // Base sample number for samples
dword AnimHdrPackSize, AnimHdrUnPackSize; // Size of animation headers
dword FrameHdrPackSize, FrameHdrUnPackSize; // Size of frame headers
dword DataPackSize, DataUnPackSize; // Size of data+masks
dword SamplesPackSize, SamplesUnPackSize; // Size of samples
} AnimDump;



/*-------------------------------------------------------------------------------------------------
Music file
-------------------------------------------------------------------------------------------------*/



//
// Music file header structure
//
typedef struct
{
char ID[4]; // "MUSE"
byte Magic[4]; // Magic number
dword FileSize, FileCRC; // Size of music file
dword AmPackSize, AmUnPackSize; // Size of Advanced Module
} MusicFileHdr;


#pragma pack(pop)
#endif // __FILEFORMATS_H__??
there ya go

Enjoy!

Violet CLM
Jan 19, 2012, 01:20 PM
So the null layer properties (discounting z-index/depth) are Type, Detail Level, and Wave X and Wave Y. Fascinating. I am tempted to try one last time to get them to do something.

Jerrythabest
Jan 20, 2012, 07:45 AM
So really, no one has ever thought of mailing Michiel before? :lol:

Well, it's good to know what names they've given those unknown parts, I'm curious about what you'll find :)

djazz
Jan 20, 2012, 01:04 PM
So really, no one has ever thought of mailing Michiel before? :lol:
indeed
Saw his name in JCS's about dialog, added him on google+ and showed him WebJCS. He was impressed. Now we keep in contact on fb chat.
If you have something you want to ask him, add him or send me a PM.

Violet CLM
Jan 20, 2012, 04:42 PM
I mean, if he has any old prototypes of anything (the game, tilesets, whatever) that would be great, but I think really what's important is that he continue to nag Arjan to nag Guerrilla Games or whatever to get you the source code, because most anything else of interest is going to be included in that. The sooner we can try to figure out how to reconcile 1.23 (or 1.20? note that those structs he gave don't include passwords) source code with TSF and JJ2+, the better.

Stijn
Jan 20, 2012, 04:47 PM
Doesn't JJ2+ already have an option to host TSF as 1.23?

What's the actual benefit of having the source code? People seem to be able to do stuff without it just fine (f.ex. Neobeo in the recent cheating thread)

Toni_
Jan 21, 2012, 01:24 AM
Doesn't JJ2+ already have an option to host TSF as 1.23?

I think it does. /multiversion on lets the 1.23 players join the TSF server. I guess it's the same.

What's the actual benefit of having the source code? People seem to be able to do stuff without it just fine (f.ex. Neobeo in the recent cheating thread)

It can help a lot in making anti cheats. Also, don't give the source code to everyone if you get it. I suggest only nimrod, djazzy, and someone else who doesn't want / never made and published bad cheats should get the source code.

Neobeo
Jan 21, 2012, 06:49 AM
What's the actual benefit of having the source code?

Three immediate things come to mind.

To understand how stuff works. Of course, with enough patience you can get this from disassembling the relevant part of the exe. According to IDA Pro, I've only identified around 200 of the 1000+ functions in Jazz2.exe, and the ones I fully understand is probably less than half of that. It is relatively easy to find out "why an event works the way it does", but somewhat harder to figure out whether or not JJ2 reads from those "unknown" bytes in the J2L (since you'd have to essentially look at every function).
To change stuff. This is fine for minor fixes (e.g. bugfixes or hooks). But if you want to change, for example, the maximum number of players to more than 32, you'd have to allocate some memory somewhere else, and then find every function that has this hardcoded and change it. With a lot of patience and time, this could be done by hand, but it would be a lot easier to just change "#DEFINE MAXPLAYERS 32". I remember thinking something along the lines of "I'm never trying to create a high resolution patch ever again".
To port to a different system. Since there's a Mac version I assume it's endian-friendly and doesn't rely on Windows-specific code. I for one would really like to see JJ2 run on the Wii.

Grytolle
Jan 21, 2012, 05:39 PM
To port to a different system. Since there's a Mac version I assume it's endian-friendly and doesn't rely on Windows-specific code. I for one would really like to see JJ2 run on the Wii.
[/LIST]
They had to hire someone to port it, I think, and the MAC version is really buggy...

Violet CLM
Jul 30, 2012, 04:31 PM
Could you post an example .j2l? I looked at a few levels with fewer than 256 words and couldn't figure out what you meant.

minmay
Jul 31, 2012, 03:00 PM
I think he just means it probably can't reference words past 256 since it is only one byte and thus only has 256 possible values.

Violet CLM
Aug 1, 2012, 12:27 PM
Could you post an example .j2l?
Not that words are traditionally listed in stream1 anyway.

djazz
Oct 2, 2012, 12:54 PM
.j2d - There's very little useful stuff:
ogLogo.ColorLUT
Menu.ColorLUT
Shield.Plasma
SoundFXList.Ending
SoundFXList.Intro
SoundFXList.P2
SoundFXList.Logo
Menu.Texture.128x128
Menu.Texture.32x32
Menu.Texture.16x16
Tiles.Order
Tiles.Offset
Credits.Palette
Order.ShadeLUT
Order.Palette
Order.Newspaper
Order.Colors
ogLogo.Balls
Balls.Dump
Picture.OrderTexture128x128
Picture.Continue
Picture.CreditsEaster
Picture.Credits
Picture.Loading
Picture.Thanks
Picture.EasterTit
Picture.Title-XMas
Picture.Title
ogLogo.Palette
Std.Palette
Menu.Palette

Using this information only, I was able to make a J2D viewer, that shows some info and all the images that can be found in the 1.20/1.23 Data.j2d. I haven't bothered to figure out how the header and the first stream are structured, I'm no good at that. So please excuse my magic number constants :D
If you're interested in how it works, check out the source links!


Here's a link that maybe is interesting: http://djazz.mine.nu/lab/j2d/
I have only tested it in Google Chrome. Enjoy!


Some exported images:

http://djazz.mine.nu/lab/j2d/img/Picture.Title.png

http://djazz.mine.nu/lab/j2d/img/Picture.Continue.png
http://djazz.mine.nu/lab/j2d/img/Order.Newspaper.png

This is actually six images:
http://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.pnghttp://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.pnghttp://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.png
http://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.pnghttp://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.pnghttp://djazz.mine.nu/lab/j2d/img/Menu.Texture.128x128.png

Jerrythabest
Oct 2, 2012, 02:03 PM
Wait, are you saying this Picture.thanks OEM screen is still in our J2D's? Or was that extracted from 1.00g/h?

Ice M A N
Oct 2, 2012, 08:00 PM
Using this information only [...]

Impressive. Well, more surprising than anything. That quoted post gave me a quick 6.5+ year flashback...

djazz
Oct 20, 2012, 07:14 AM
Behold, a complete JJ2 language editor!

http://djazz.mine.nu/lab/j2s/

I made it on request from Grytolle, who wanted to improve his Swedish translation of JJ2.
Here it is, including translation of the helpstrings in the default levels:
http://www.jazz2online.com/downloads/6514/swedish-language-file/
Some are really funny, if you speak Swedish.

J2S File Format
From J2NSM Haxia
The Jazz Jackrabbit 2 Language file. Contains text strings and some other stuff.
struct J2S_File_Format {
unsigned int offset_array_size; // Size of the array with string offsets for the first text array
unsigned int text_array_size; // Size of the first text array
unsigned char text_array[text_array_size]; // The first text array - contains null-terminated strings
unsigned int offset_array[offset_array_size / 4]; // An array of integers, each containing the offset for a string in the first array.
unsigned int level_entry_count; // Number of level entries
level_entry entry[level_entry_count]; // See the level_entry struct definition below
unsigned int text_array_2_size; // The total size of the secondary text array
array_2 text_array_2[]; // Secondary text array - see definition below
};

struct level_entry { // 12 bytes each
unsigned char name[8]; // Null terminated...
char zeroByte;
char unknown; // Some amount maybe
unsigned short offset; // Some offset
};

I am not sure whether is a good idea to use a variable-length struct to display the data, but it's only for informational purposes.

struct array_2 { // Secondary text array entry
unsigned char some_index; // Probably has to do with level entries eg. which entry this text belongs to
unsigned char t_size; // Size of the following text
unsigned char text[t_size]; // The text itself (variable-length)
};
That's a brief definition of the J2S format. The first text array contains messages that you get to see during the game like Player entered the game, Player was roasted by Grytolle, etc. The offset array tells you where a certain text begins. Strings inside this array are null-terminated. The second half of the J2S contains some outdated level data. Probably the devs considered putting text that was to be displayed in levels into language files, but later moved it into J2L structure.

It is not a complete specification, see my code for how I did it. Also, TSF language files contains more helpstrings (in text_array_2), but TSF supports loading 1.20-1.23 language files. My program can only handle 1.20-1.23 languages. ENJOY!

Grytolle
Oct 20, 2012, 07:21 AM
Player was roasted by GrytolleI applaud your realistic examples! 8D

Violet CLM
Oct 20, 2012, 10:27 AM
The second half of the J2S contains some outdated level data. Probably the devs considered putting text that was to be displayed in levels into language files, but later moved it into J2L structure.
But JJ2 <em>does</em> use the text strings in the .j2s file, not the ones in the .j2l. That's why a hypothetical car.j2l will use the text strings from carrot1.j2l instead of whatever ones you try to specify for it internally. Unless I'm misunderstanding what you're saying here?

djazz
Oct 20, 2012, 10:49 AM
But JJ2 <em>does</em> use the text strings in the .j2s file, not the ones in the .j2l. That's why a hypothetical car.j2l will use the text strings from carrot1.j2l instead of whatever ones you try to specify for it internally. Unless I'm misunderstanding what you're saying here?

JJ2 uses the text from the .j2s files. I have no idea how it detect the filenames. I just copypasted that specification from J2NSM.

DoubleGJ
Oct 20, 2012, 03:34 PM
No knowledge here. Just experience.
Yeah JJ2 does use the J2S basing on some not entirely specified level name data that causes conflicts. That's why at first the levels of my levelpack were using filenames in Polish.
Anyway, it's a pretty neat concept that does work good on a vanilla JJ2 (entirely vanilla, meaning no custom levels etc.). What I could propose, is that given we seem to have conquered J2S as a group (lol claiming rights oh well), we could incorporate new elements like pregame or new gamemode prompts into the language files!

Because... uh... we can. AAAAND there could be people out there who use JJ2+ and yet barely know English! (just a guess but it could very well be true)

if I sound unreasonable well then I have my reasons I'm an artist and all

jvbsl
Jul 2, 2015, 02:41 PM
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:

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;
}

I extracted the Hash function for the passwords - if anyone is interested in something like that

uint32_t lookup[] = {0x00000000, 0x77073096, 0x0EE0E612C, 0x990951BA, 0x76DC419, 0x706AF48F,
0x0E963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0x0E0D5E91E,
0x97D2D988, 0x9B64C2B, 0x7EB17CBD, 0x0E7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0x0F3B97148, 0x84BE41DE, 0x1ADAD47D,
0x6DDDE4EB, 0x0F4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0,
0x0FD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0x0FA0F3D63,
0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0x0D56041E4, 0x0A2677172,
0x3C03E4D1, 0x4B04D447, 0x0D20D85FD, 0x0A50AB56B, 0x35B5A8FA,
0x42B2986C, 0x0DBBBC9D6, 0x0ACBCF940, 0x32D86CE3, 0x45DF5C75,
0x0DCD60DCF, 0x0ABD13D59, 0x26D930AC, 0x51DE003A, 0x0C8D75180,
0x0BFD06116, 0x21B4F4B5, 0x56B3C423, 0x0CFBA9599, 0x0B8BDA50F,
0x2802B89E, 0x5F058808, 0x0C60CD9B2, 0x0B10BE924, 0x2F6F7C87,
0x58684C11, 0x0C1611DAB, 0x0B6662D3D, 0x76DC4190, 0x1DB7106,
0x98D220BC, 0x0EFD5102A, 0x71B18589, 0x6B6B51F, 0x9FBFE4A5,
0x0E8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0x0E10E9818,
0x7F6A0DBB, 0x86D3D2D, 0x91646C97, 0x0E6635C01, 0x6B6B51F4,
0x1C6C6162, 0x856530D8, 0x0F262004E, 0x6C0695ED, 0x1B01A57B,
0x8208F4C1, 0x0F50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
0x0FCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0x0FBD44C65,
0x4DB26158, 0x3AB551CE, 0x0A3BC0074, 0x0D4BB30E2, 0x4ADFA541,
0x3DD895D7, 0x0A4D1C46D, 0x0D3D6F4FB, 0x4369E96A, 0x346ED9FC,
0x0AD678846, 0x0DA60B8D0, 0x44042D73, 0x33031DE5, 0x0AA0A4C5F,
0x0DD0D7CC9, 0x5005713C, 0x270241AA, 0x0BE0B1010, 0x0C90C2086,
0x5768B525, 0x206F85B3, 0x0B966D409, 0x0CE61E49F, 0x5EDEF90E,
0x29D9C998, 0x0B0D09822, 0x0C7D7A8B4, 0x59B33D17, 0x2EB40D81,
0x0B7BD5C3B, 0x0C0BA6CAD, 0x0EDB88320, 0x9ABFB3B6, 0x3B6E20C,
0x74B1D29A, 0x0EAD54739, 0x9DD277AF, 0x4DB2615, 0x73DC1683,
0x0E3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0x0E40ECF0B,
0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0x0F00F9344, 0x8708A3D2,
0x1E01F268, 0x6906C2FE, 0x0F762575D, 0x806567CB, 0x196C3671,
0x6E6B06E7, 0x0FED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0x0F9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0x0D6D6A3E8,
0x0A1D1937E, 0x38D8C2C4, 0x4FDFF252, 0x0D1BB67F1, 0x0A6BC5767,
0x3FB506DD, 0x48B2364B, 0x0D80D2BDA, 0x0AF0A1B4C, 0x36034AF6,
0x41047A60, 0x0DF60EFC3, 0x0A867DF55, 0x316E8EEF, 0x4669BE79,
0x0CB61B38C, 0x0BC66831A, 0x256FD2A0, 0x5268E236, 0x0CC0C7795,
0x0BB0B4703, 0x220216B9, 0x5505262F, 0x0C5BA3BBE, 0x0B2BD0B28,
0x2BB45A92, 0x5CB36A04, 0x0C2D7FFA7, 0x0B5D0CF31, 0x2CD99E8B,
0x5BDEAE1D, 0x9B64C2B0, 0x0EC63F226, 0x756AA39C, 0x26D930A,
0x9C0906A9, 0x0EB0E363F, 0x72076785, 0x5005713, 0x95BF4A82,
0x0E2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0x0E5D5BE0D,
0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0x0F1D4E242, 0x68DDB3F8,
0x1FDA836E, 0x81BE16CD, 0x0F6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0x0FF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF,
0x0F862AE69, 0x616BFFD3, 0x166CCF45, 0x0A00AE278, 0x0D70DD2EE,
0x4E048354, 0x3903B3C2, 0x0A7672661, 0x0D06016F7, 0x4969474D,
0x3E6E77DB, 0x0AED16A4A, 0x0D9D65ADC, 0x40DF0B66, 0x37D83BF0,
0x0A9BCAE53, 0x0DEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0x0BDBDF21C,
0x0CABAC28A, 0x53B39330, 0x24B4A3A6, 0x0BAD03605, 0x0CDD70693,
0x54DE5729, 0x23D967BF, 0x0B3667A2E, 0x0C4614AB8, 0x5D681B02,
0x2A6F2B94, 0x0B40BBE37, 0x0C30C8EA1, 0x5A05DF1B};
uint32_t hash(char* input)
{
uint32_t hash = 0xFFFFFFFF;
int count = strlen(input);

for (int i=0;i<count;i++)
{
uint32_t currentChar=input[i] & 0xFF,lookupIndex=hash;
lookupIndex = lookupIndex & 0xFF;
lookupIndex = lookupIndex ^ currentChar;
hash = hash >> 8;
hash ^= lookup[lookupIndex];
}
hash = ~hash;
hash &= 0xFFFFFF;
return hash;
}

Edit: It is actually the normal CRC-algorithm(don't use that implementation - use other ones found on the internet) - look post below. Just the lower 3 bytes are used(LE)
can probably still be optimized and written better as I translated it myself from ASM.

Stijn
Jul 2, 2015, 02:54 PM
Also known as the CRC algorithm (https://msdn.microsoft.com/en-us/library/dd905031(v=office.12).aspx) ;)

jvbsl
Jul 2, 2015, 03:06 PM
it is the normal CRC? God damnit I should have looked first^^

Edit: It indeed is but just 3 Bytes are actually used. But now atleast everyone knows

First post and already posted bullshit^^

Violet CLM
Jul 2, 2015, 03:36 PM
FWIW the specs on the ERE are by now more complete than the ones in this thread: <a href="http://jazz2online.com/wiki/index.php?J2L_File_Format">J2L</a> <a href="http://jazz2online.com/wiki/index.php?J2T_File_Format">J2T</a> <a href="http://jazz2online.com/wiki/index.php?J2A_File_Format">J2A</a>

PrinceOthman
Aug 3, 2015, 05:26 PM
So, how would one come around to extracting all the sprites in the game? Trying to work on a fan-game here.

Violet CLM
Aug 3, 2015, 07:35 PM
Here you go

or Spriters Resource (http://www.spriters-resource.com/pc_computer/jazzjackrabbit2thesecretfiles/) has a lot of stuff too, if you'd find that format more convenient

(although, I don't know if you can beat this) (http://djazz.se/apps/webjj2/src_dev/app.html)

djazz
Aug 4, 2015, 11:21 AM
(although, I don't know if you can beat this) (http://djazz.se/apps/webjj2/src_dev/app.html)
*hypno* :+

InfamousStar
Oct 6, 2015, 07:13 AM
I wrote a Python script to extract the sound effects from Anims.j2a. Information on the Jazz2's sample format is scarce, so I ended up relying heavily on DJazz's PHP code. Anyway, here it is:


#!/usr/bin/env python2

import wave

from os import mkdir
from StringIO import StringIO
from struct import unpack
from zlib import decompress

# These functions return the next 8/16/32 bits of a file as a little endian int.
def readInt8(f): return unpack('&lt;b', f.read(1))[0]
def readInt16(f): return unpack('&lt;h', f.read(2))[0]
def readInt32(f): return unpack('&lt;l', f.read(4))[0]


def parseAlibHeader(j2a):
"""Parses a j2a file's header, and returns a dict of the contents.

Entries in returned dict:
"magic number" -- 'ALIB'; identifies file format
"unknown1" -- 0x00beba00; purpose unknown
"header size" -- size of this header, in bytes
"version" -- 0x0200; probably means v2.0
"unknown2" -- 0x1808; purpose unknown
"file size" -- obvious
"crc32" -- CRC32 hash of file
"set count" -- number of animation sets in file
"set offsets" -- list containing the offsets for each set, in bytes
"""

j2a.seek(0)

header = {}
header["magic number"] = j2a.read(4)
header["unknown1"] = readInt32(j2a)
header["header size"] = readInt32(j2a)
header["version"] = readInt16(j2a)
header["unknown2"] = readInt16(j2a)
header["file size"] = readInt32(j2a)
header["crc32"] = readInt32(j2a)
header["set count"] = readInt32(j2a)
header["set offsets"] = [readInt32(j2a) for i in range(header["set count"])]

return header

def parseAnimHeader(j2a, offset):
"""Parses an animation set's header, and returns a dict of the contents.

Entries in returned dict:
"magic number" -- 'ANIM'; marks start of set
"animation count" -- number of animations in this set
"sample count" -- number of sound samples in set
"frame count" -- total number of frames in all animations in set
"prior sample count" -- total number of samples before this set
"animation info",
"frame info",
"image data",
"sample data" -- dicts detailing the size of each part of the set

The rest of the set is compressed with zlib, so the last 4 entries above
contain these entries:
"compressed" -- actual size in file
"uncompressed" -- size when uncompressed
"""

j2a.seek(offset)

header = {}
header["magic number"] = j2a.read(4)
header["animation count"] = readInt8(j2a)
header["sample count"] = readInt8(j2a)
header["frame count"] = readInt16(j2a)
header["prior sample count"] = readInt32(j2a)

for key in "animation info", "frame info", "image data", "sample data":
header[key] = {
"compressed": readInt32(j2a),
"uncompressed":readInt32(j2a)
}

return header

def sampleDataOffset(setOffset, animHeader):
"""Returns the offset of an animation set's sample data."""

offset = setOffset + 44 # animHeader is 44 bytes
for key in "animation info", "frame info", "image data":
offset += animHeader[key]["compressed"]

return offset

def decompressInFile(f, offset, size):
"""Decompresses portion of file, returning result as a StringIO."""

f.seek(offset)
return StringIO(decompress(f.read(size)))

def parseSample(data, offset):
"""Parses a sample, returning a dict of the contents.

Entries in returned dict:
"size" -- combined size of all entries
"chunk id" -- 'RIFF'; identifies chunk type
"chunk size" -- size of everything below this, except "padding3"
"format" -- 'AS '; identifies format contained in RIFF
"samp subchunk" -- dict containing format info
"data" -- waveform data of sample
"padding" -- 8 bytes of padding

"samp subchunk" contains these entries:
"id" -- 'SAMP'; identifies header
"padding1" -- 48 bytes of padding (probably)
"data size" -- size of data, in bytes
"padding2" -- 8 bytes of padding
"sample rate" -- samples per second
"""

data.seek(offset)

sample = {}
sample["size"] = readInt32(data)
sample["chunk id"] = data.read(4)
sample["chunk size"] = readInt32(data)
sample["format"] = data.read(4)

samp = {}
samp["id"] = data.read(4)
samp["padding1"] = data.read(48)
samp["data size"] = readInt32(data)
samp["padding2"] = data.read(8)
samp["sample rate"] = readInt32(data)

sample["samp subchunk"] = samp

sample["data"] = data.read(samp["data size"])
sample["padding3"] = data.read(8)

return sample

def parseSampleData(data, sampleCount):
"""Parses sample data, returning a list of samples.

See parseSample for info on elements.
"""

samples = []

data.seek(0)

# In theory, parsing one sample should put me at the start of the next.
# Unfortunately, there's at least one place where this fails, so I have to
# keep track of the offset, manually.
offset = 0
for i in range(sampleCount):
samples.append(parseSample(data, offset))
offset += samples[-1]["size"]

return samples

def writeWav2(filename, sample):
# todo: don't overwrite file if it already exists
wavFile = wave.open(filename, 'w')

# the 1st two are hardcoded until I find this info in the sample
wavFile.setnchannels(1)
wavFile.setsampwidth(2)
wavFile.setframerate(sample["samp subchunk"]["sample rate"])

wavFile.writeframes(sample["data"])

wavFile.close()


def main():
filename = "Anims.j2a" # I can probably just leave this hardcoded

# todo: throw error if file not found
with open(filename, 'r') as j2a:
alibHeader = parseAlibHeader(j2a) # find animation sets
# todo: verify file with checksum

print("{} found.".format(filename))
print("Total size: {} bytes".format(alibHeader["file size"]))
print("{} animation sets found.\n".format(alibHeader["set count"]))

# todo: don't crash if this exists already
mkdir("sounds")

# for each set
for setNum, offset in enumerate(alibHeader["set offsets"]):
animHeader = parseAnimHeader(j2a, offset) # find sample data

print("Decompressing data for set {}...".format(\
alibHeader["set offsets"].index(offset)))

# find samples
decompressed = decompressInFile(j2a,\
sampleDataOffset(offset, animHeader),\
animHeader["sample data"]["compressed"])
samples = parseSampleData(decompressed, animHeader["sample count"])

print("{} samples found. Writing to disk...".format(len(samples)))

# write samples
wavName = "sounds/sfx_{}_{}.wav"
for sampleNum, sample in enumerate(samples):
writeWav2(wavName.format(setNum, sampleNum), sample)

if __name__ == '__main__':
main()

Violet CLM
Oct 6, 2015, 10:26 AM
Well done!

Ðx
Dec 28, 2019, 08:44 AM
thank you now i can play my favorite game again and get back to 2001