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... |
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! |
Aug 15, 2010, 11: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]; } 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. |
Aug 18, 2010, 12:26 AM | |
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]; } Last edited by Violet CLM; Aug 18, 2010 at 09:30 PM. |
Oct 17, 2010, 03: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.
|
Oct 18, 2010, 07:48 AM | ||
Quote:
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 |
Oct 18, 2010, 01: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. Last edited by Violet CLM; Oct 18, 2010 at 02:08 PM. |
Oct 18, 2010, 10:47 PM | ||
Quote:
PHP Code:
__________________
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 |
Oct 18, 2010, 10:58 PM | |
Oh, sorry, I meant for displaying relative to the window, not storing. They're definitely multiples of 65536.
|
Oct 18, 2010, 11:13 PM | ||
Quote:
I'm using this code for WebJCS (JavaScript): PHP Code:
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 |
Oct 20, 2010, 02: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.) Last edited by Violet CLM; Oct 20, 2010 at 03:23 PM. |
Oct 21, 2010, 03:08 PM | |||
Quote:
Quote:
Code:
$tmp.=pack("N*",bindec(strrev($tmp3))); 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> |
Oct 21, 2010, 09: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.
|
Oct 21, 2010, 10:52 PM | |
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 |
Oct 22, 2010, 08: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. I was using this code: PHP Code:
__________________
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 |
Oct 22, 2010, 03:01 PM | ||
Quote:
PHP Code:
__________________
<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 |
Oct 22, 2010, 04: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.
|
Oct 22, 2010, 05:00 PM | ||
Quote:
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 }
__________________
<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> |
Oct 23, 2010, 03:53 AM | ||
Quote:
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 |
Oct 23, 2010, 10:14 AM | ||
Quote:
__________________
|
Oct 23, 2010, 11:49 AM | |
Yes. Of course, it would be simpler just not to have a colored pixel there at all.
|
Oct 23, 2010, 11:54 AM | |
Awesome. So there is a way to use pure #000 black in JJ2 after all.
__________________
|
Nov 17, 2010, 02:49 PM | ||
Quote:
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 |
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. Last edited by Violet CLM; Jun 27, 2011 at 07:39 PM. |
Jul 9, 2011, 05: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. 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. |
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:
__________________
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 |
Dec 5, 2011, 12:20 PM | |
...is that an unused Lori hurt sound? You can definitely tell why it didn't work out.
|
Dec 5, 2011, 12:28 PM | |
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 |
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.
__________________
"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) |
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(); } } }
__________________
<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> |
Dec 5, 2011, 10:46 PM | |
Mind if I steal that?
|
Dec 5, 2011, 11:00 PM | |
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> |
Dec 6, 2011, 12:49 AM | |
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 |
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?
__________________
"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) |
Dec 6, 2011, 11:03 AM | |
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?
__________________
|
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.
|
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.
__________________
|
Dec 6, 2011, 07:03 PM | ||
Quote:
__________________
<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> |
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!
__________________
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. |
«
Previous Thread
|
Next Thread
»
Thread Tools | |
|
|
All times are GMT -8. The time now is 02:45 PM.
Jazz2Online © 1999-INFINITY (Site Credits). Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats. Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Original site design by Ovi Demetrian. DrJones is the puppet master. Eat your lima beans, Johnny.