JazzJackrabbit Community Forums

JazzJackrabbit Community Forums (http://www.jazz2online.com/jcf/index.php)
-   General Jazz Jackrabbit Talk (http://www.jazz2online.com/jcf/forumdisplay.php?f=4)
-   -   JJ2 File Format Specifications (http://www.jazz2online.com/jcf/showthread.php?t=15059)

Stijn Mar 8, 2010 12:00 PM

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 12:21 PM

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:

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?

Violet CLM Aug 17, 2010 11:26 PM

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. :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

Quote:

Originally Posted by Unknown Rabbit (Post 462346)
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:
[IMG]http://jazzjackrabbit.net/DJ/JJ2_screens/J2Tdata3.png[/IMG]

Source 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

Quote:

Originally Posted by Unknown Rabbit (Post 462424)
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.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

Quote:

Originally Posted by Unknown Rabbit (Post 462438)
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 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

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

Quote:

Originally Posted by Unknown Rabbit (Post 462346)
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 (Post 462394)
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)));

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

Quote:

Originally Posted by Neobeo (Post 462544)
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

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.

[IMG]http://jazzjackrabbit.net/DJ/JJ2_screens/data3.png[/IMG]

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.

Neobeo Oct 22, 2010 02:01 PM

Quote:

Originally Posted by DJazz (Post 462580)
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.

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

Quote:

Originally Posted by Unknown Rabbit (Post 462610)
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.

djazz Oct 23, 2010 02:53 AM

Quote:

Originally Posted by Neobeo (Post 462604)
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.

Jerrythabest Oct 23, 2010 09:14 AM

Quote:

Originally Posted by Neobeo (Post 462611)
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 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 02:49 PM

Quote:

Originally Posted by Unknown Rabbit (Post 462499)
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];

Violet CLM Nov 17, 2010 07:50 PM

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.

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):
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 :D:
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.


djazz Dec 5, 2011 12:18 PM

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($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 12:20 PM

...is that an unused Lori hurt sound? You can definitely tell why it didn't work out.

djazz Dec 5, 2011 12:28 PM

Quote:

Originally Posted by Violet CLM (Post 472357)
unused

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

DoubleGJ Dec 5, 2011 02: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 10: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...

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.

Violet CLM Dec 5, 2011 10:46 PM

Mind if I steal that?

Neobeo Dec 5, 2011 11:00 PM

Sure, go ahead!

djazz Dec 6, 2011 12:49 AM

Quote:

Originally Posted by Neobeo (Post 472384)
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!

DoubleGJ Dec 6, 2011 03: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


Jerrythabest Dec 6, 2011 11:03 AM

Quote:

Originally Posted by Neobeo (Post 472384)
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 11: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 12:45 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.

Neobeo Dec 6, 2011 07:03 PM

Quote:

Originally Posted by Jerrythabest (Post 472432)
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 11:35 PM

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!


All times are GMT -8. The time now is 10:23 AM.

Powered by vBulletin® Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
JCF © 2001-Infinity Jazz2Online