Register FAQ Search Today's Posts Mark Forums Read
Go Back   JazzJackrabbit Community Forums » Open Forums » General Jazz Jackrabbit Talk

JJ2 File Format Specifications

Reply
 
Thread Tools
Stijn

Administrator

Joined: Mar 2001

Posts: 6,965

Stijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to behold

Mar 8, 2010, 12:00 PM
Stijn is offline
Reply With Quote
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

JCF Member

Joined: Dec 2006

Posts: 38

Jojo is doing well so far

Mar 8, 2010, 12:21 PM
Jojo is offline
Reply With Quote
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

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Aug 15, 2010, 11:20 PM
Violet CLM is offline
Reply With Quote
Has anyone looked into the save files yet? Primitively, they start out like this:

Code:
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?
__________________

Last edited by Violet CLM; Aug 18, 2010 at 08:58 PM.
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Aug 18, 2010, 12:26 AM
Violet CLM is offline
Reply With Quote
Confirmed that the first zlib block is indeed the save image:

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:
Code:
struct object {
	long unknown;
	long original Xpos;
	long Ypos;
	long current Xpos;
	char unknown[48];
	byte Strength;
	char unknown[67];
}
ETA4: Oh, huh, these are some pretty familiar numbers right here.
__________________

Last edited by Violet CLM; Aug 18, 2010 at 09:30 PM.
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 17, 2010, 03:20 PM
Violet CLM is offline
Reply With Quote
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

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 18, 2010, 07:48 AM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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:


Source for TilesetCompiler (PHP)
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 18, 2010, 01:45 PM
Violet CLM is offline
Reply With Quote
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.
__________________

Last edited by Violet CLM; Oct 18, 2010 at 02:08 PM.
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 18, 2010, 10:47 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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:
PHP Code:
$LayerXSpeed = array(3.3752.251110.250.1250);
$LayerYSpeed = array(3.3752.251110.250.1250);
$LayerAutoXSpeed = array(00000000);
$LayerAutoYSpeed = array(00000000);

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).
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 18, 2010, 10:58 PM
Violet CLM is offline
Reply With Quote
Oh, sorry, I meant for displaying relative to the window, not storing. They're definitely multiples of 65536.
__________________
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 18, 2010, 11:13 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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):
PHP Code:
        var parallaxC.offsetWidth;
        var 
parallaxC.offsetHeight;
        var 
scrollX LayerDiv.scrollLeft;
        var 
scrollY LayerDiv.scrollTop;
        if(
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 
7>= 0l--) {
            for(var 
ii=Math.floor((scrollX*layerSpeed[l][0]+scrollXoffset)/32)*32ii <= 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+32ii+=32) {
                for(var 
jj=Math.floor((scrollY*layerSpeed[l][1]+scrollYoffset)/32)*32jj <= 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+32jj+=32) {

// Check for tiling layers etc...

// Using this drawing function
srcpos idXY(tileData[l][ii/32][jj/32] % 102410);
pcc.drawImage(tilesetImagesrcpos[0]*32srcpos[1]*323232ii-(scrollX*layerSpeed[l][0]+scrollXoffset), jj-(scrollY*layerSpeed[l][1]+scrollYoffset), 3232); 
It works, but it's not the same

Full source for parallax.js

Still, the wanted result is this:
http://jazzjackrabbit.net/DJ/LvlView/
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 20, 2010, 02:58 PM
Violet CLM is offline
Reply With Quote
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.)
__________________

Last edited by Violet CLM; Oct 20, 2010 at 03:23 PM.
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Oct 21, 2010, 03:08 PM
Neobeo is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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?

Quote:
Originally Posted by DJazz View Post
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 for TilesetCompiler (PHP)
I took a quick look at the source. Try changing line 258 from
Code:
$tmp.=pack("N*",bindec(strrev($tmp3)));
to
Code:
$tmp.=pack("V*",bindec(strrev($tmp3)));
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>
Jgke

JCF Member

Joined: Sep 2006

Posts: 974

Jgke is an asset to this forumJgke is an asset to this forum

Oct 21, 2010, 09:41 PM
Jgke is offline
Reply With Quote
Wow, a Neobeo sighting! Hi!
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 21, 2010, 09:45 PM
Violet CLM is offline
Reply With Quote
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

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 21, 2010, 10:52 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
I took a quick look at the source. Try changing line 258 from
Code:
$tmp.=pack("N*",bindec(strrev($tmp3)));
to
Code:
$tmp.=pack("V*",bindec(strrev($tmp3)));
Same result... I changed to N because V didnt work, forgot to change back
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 22, 2010, 08:12 AM
djazz is offline
Reply With Quote
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.



I was using this code:

PHP 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 
"";
    
    if(
$i%32==0) echo str_repeat("-"32)."&nbsp;&nbsp;".str_repeat("-"32).""// Each 32 row, add a line

This code is also in TilesetCompiler's updated source.
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Oct 22, 2010, 03:01 PM
Neobeo is offline
Reply With Quote
Quote:
Originally Posted by DJazz View Post
This code is also in TilesetCompiler's updated source.
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:
$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."";
    
    
$_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.
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>

Last edited by Neobeo; Oct 22, 2010 at 03:05 PM. Reason: decreased code indent
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 22, 2010, 04:48 PM
Violet CLM is offline
Reply With Quote
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

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Oct 22, 2010, 05:00 PM
Neobeo is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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:
Code:
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.
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Oct 23, 2010, 03:53 AM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
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:
//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, so you can try it out!
Works in a recent Firefox and in Chrome/Chromium.
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Jerrythabest

JCF Member

Joined: Apr 2005

Posts: 2,602

Jerrythabest is a forum legendJerrythabest is a forum legendJerrythabest is a forum legend

Oct 23, 2010, 10:14 AM
Jerrythabest is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
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:
Code:
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

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Oct 23, 2010, 11:49 AM
Violet CLM is offline
Reply With Quote
Yes. Of course, it would be simpler just not to have a colored pixel there at all.
__________________
Jerrythabest

JCF Member

Joined: Apr 2005

Posts: 2,602

Jerrythabest is a forum legendJerrythabest is a forum legendJerrythabest is a forum legend

Oct 23, 2010, 11:54 AM
Jerrythabest is offline
Reply With Quote
Awesome. So there is a way to use pure #000 black in JJ2 after all.
__________________
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Nov 17, 2010, 02:49 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Unknown Rabbit View Post
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:
Code:
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];
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Nov 17, 2010, 07:50 PM
Violet CLM is offline
Reply With Quote
If you're going with a fixed resolution, you should probably simplify the (640-320)/2-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.
__________________

Last edited by Violet CLM; Jun 27, 2011 at 07:39 PM.
Sfaizst

JCF Member

Joined: Mar 2008

Posts: 99

Sfaizst is an asset to this forum

Jul 9, 2011, 05:51 AM
Sfaizst is offline
Reply With Quote
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):
Code:
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=False;NotSetBack:Boolean=False):AnsiString;

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

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

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

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

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

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

function ReadByte(var MemoryStream:TMemorystream; const Position : Integer; UseFilestream:Boolean=False;NotReadLast:Boolean=False;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=False;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=False;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=False;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=False;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=False;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=False;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=False;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=False;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 :
Code:
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.
__________________

I'm watching you!!

Last edited by Sfaizst; Jul 9, 2011 at 06:03 AM.
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Dec 5, 2011, 12:18 PM
djazz is offline
Reply With Quote
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:


PHP Code:
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($AlibStructfread($f28));
$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($AnimStructfread($f44));
    
$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($AnimInfoStructsubstr($animStreams[$i][0], $j*88));
        
$minw 1;
        
$minh 1;
        
$AnimInfo[$i][$j]['Frames'] = array();
        for(
$k=0$k $AnimInfo[$i][$j]['FrameCount']; $k++) {
            
$AnimInfo[$i][$j]['Frames'][$k] = unpack($AnimFrameStructsubstr($animStreams[$i][1], $frameOffset*2424));
            
$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], $sampleOffset4));
        
$RiffInfo[$i][$j] = unpack($RiffStructsubstr($animStreams[$i][3], $sampleOffset+4$sfxLength-8));
        
$mul $RiffInfo[$i][$j]['mul'] / 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"
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler

Last edited by djazz; Dec 6, 2011 at 12:46 AM. Reason: Added wav output instead of raw output
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Dec 5, 2011, 12:20 PM
Violet CLM is offline
Reply With Quote
...is that an unused Lori hurt sound? You can definitely tell why it didn't work out.
__________________
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Dec 5, 2011, 12:28 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Violet CLM View Post
unused
The Anims.j2a library contains lots lots of unused sounds, and animations.
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
DoubleGJ

JCF Member

Joined: Sep 2002

Posts: 3,049

DoubleGJ has disabled reputation

Dec 5, 2011, 02:34 PM
DoubleGJ is offline
Reply With Quote
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.
__________________
"So unless I overwrote my heart with yours, I think not. But I might have." - Violet CLM

Two Games Joined releases:
Control / Splinter (twin singles)
|| Ballistic Bunny (EP)
||
Beyond (maxi-single)
|| Beyond: Remixed (remix EP)
|| Inner Monsters OST (mini-album)
||
Shadows (album)
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Dec 5, 2011, 10:12 PM
Neobeo is offline
Reply With Quote
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...

Code:
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.
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>
Violet CLM

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Dec 5, 2011, 10:46 PM
Violet CLM is offline
Reply With Quote
Mind if I steal that?
__________________
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Dec 5, 2011, 11:00 PM
Neobeo is offline
Reply With Quote
Sure, go ahead!
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Dec 6, 2011, 12:49 AM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
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:



Wtf?


Enjoy!
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
DoubleGJ

JCF Member

Joined: Sep 2002

Posts: 3,049

DoubleGJ has disabled reputation

Dec 6, 2011, 03:04 AM
DoubleGJ is offline
Reply With Quote
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?

__________________
"So unless I overwrote my heart with yours, I think not. But I might have." - Violet CLM

Two Games Joined releases:
Control / Splinter (twin singles)
|| Ballistic Bunny (EP)
||
Beyond (maxi-single)
|| Beyond: Remixed (remix EP)
|| Inner Monsters OST (mini-album)
||
Shadows (album)
Jerrythabest

JCF Member

Joined: Apr 2005

Posts: 2,602

Jerrythabest is a forum legendJerrythabest is a forum legendJerrythabest is a forum legend

Dec 6, 2011, 11:03 AM
Jerrythabest is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
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

JCF Éminence Grise

Joined: Mar 2001

Posts: 11,047

Violet CLM has disabled reputation

Dec 6, 2011, 11:48 AM
Violet CLM is offline
Reply With Quote
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

JCF Member

Joined: Apr 2005

Posts: 2,602

Jerrythabest is a forum legendJerrythabest is a forum legendJerrythabest is a forum legend

Dec 6, 2011, 12:45 PM
Jerrythabest is offline
Reply With Quote
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

JCF Member

Joined: Sep 2002

Posts: 409

Neobeo is an asset to this forumNeobeo is an asset to this forum

Dec 6, 2011, 07:03 PM
Neobeo is offline
Reply With Quote
Quote:
Originally Posted by Jerrythabest View Post
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.
__________________
<TABLE border=1><TR><TD>Facts:
Jazz Sprite Dynamite (JSD)
Tileset Extractor
Neobeo's Firetruck

</TD><TD>Myths:
Jazz Creation Station Plus (JCS+) - 10%
Coming soon - a dedicated server! - 25%
Jazz Sprite Dynamite v2 (JSDv2) - 2%
Another generic single-player level - 0%
</TD></TR></TABLE>
djazz

JCF Member

Joined: Feb 2009

Posts: 257

djazz is OFF DA CHARTdjazz is OFF DA CHARTdjazz is OFF DA CHART

Dec 10, 2011, 11:35 PM
djazz is offline
Reply With Quote
I made a JJ2 sfx soundboard 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!
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler

Last edited by djazz; Dec 11, 2011 at 02:07 AM.
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is On

Forum Jump

All times are GMT -8. The time now is 03:00 PM.