| 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, 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];
}
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 07:58 PM. |
| 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];
}
Last edited by Violet CLM; Aug 18, 2010 at 08:30 PM. |
| 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.
|
| Oct 18, 2010, 06: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, 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. Last edited by Violet CLM; Oct 18, 2010 at 01:08 PM. |
| Oct 18, 2010, 09: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, 09:58 PM | |
|
Oh, sorry, I meant for displaying relative to the window, not storing. They're definitely multiples of 65536.
|
| Oct 18, 2010, 10: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, 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.) Last edited by Violet CLM; Oct 20, 2010 at 02:23 PM. |
| Oct 21, 2010, 02: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, 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.
|
| Oct 21, 2010, 09: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, 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. ![]() 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, 02: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 02:05 PM. Reason: decreased code indent |
||
| 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.
|
| Oct 22, 2010, 04: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, 02: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, 09:14 AM | ||
|
Quote:
__________________
|
||
| Oct 23, 2010, 10:49 AM | |
|
Yes. Of course, it would be simpler just not to have a colored pixel there at all.
|
| Oct 23, 2010, 10: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 06:39 PM. |
| 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.
: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 05: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 09:20 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 - 2025, Jelsoft Enterprises Ltd.
Original site design by Ovi Demetrian. DrJones is the puppet master. Eat your lima beans, Johnny.




), 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?







:
