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

JJ2 File Format Specifications

Reply
 
Thread Tools
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

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

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

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

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

The Header
Code:
struct J2V_Header {
	char Magic[8] = "CineFeed";
	int FileSize;
	int CRC32; // of the lowercase filename, e.g. "intro" or "endinglq"
	int Width;
	int Height;
	short BitsPerPixel;
	short DelayBetweenFrames; // in milliseconds
	int TotalFrames;
	int MaxUSize1;
	int MaxUSize2;
	int MaxUSize3;
	int MaxUSize4;
	int MaxCSize;
}
The header is followed by 4 interleaved sync-flushed zlib streams. How JJ2 does it is to allocate 5 memory buffers of the 5 sizes given at the end of the header. Each "compressed string of bytes" is read and then uncompressed into the corresponding uncompressed buffer. But since we're in an age with unlimited memory, our best approach would be simply be to concatenate everything into one long buffer.

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

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

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

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

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

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

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

Sound Data
This is obtained from the corresponding SoundFXList.Name from Data.J2D, where it can be read as the following struct:
Code:
struct SoundFXList {
	int Frame;
	int Sample;
	int Volume;
	int Panning;
}
which gives a list of which frames sounds are played.




Finally, here's some C# code which needs nothing but the .NET framework and compilation under unsafe code:
Code:
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

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

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

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

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

                    for (int y = 0; y < height; y++)
                    {
                        byte c;
                        int x = 0;
                        while ((c = d[0].ReadByte()) != 128)
                        {
                            if (c < 128)
                            {
                                int u = c == 0 ? d[0].ReadInt16() : c;
                                for (i = 0; i < u; i++) pixels[y * data.Stride + x++] = d[3].ReadByte();
                            }
                            else
                            {
                                int u = c == 0x81 ? d[0].ReadInt16() : c - 106;
                                int n = d[1].ReadInt16() + (d[2].ReadByte() + y - 127) * data.Stride;
                                for (i = 0; i < u; i++) pixels[y * data.Stride + x++] = copy[n++];
                            }
                        }
                    }
                }
                bmp.UnlockBits(data);
                pb.Image = bmp;
            });
            Application.Run(f);
        }
    }
}
__________________
<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; Dec 22, 2011 at 06:06 PM. Reason: typo
djazz djazz's Avatar

JCF Member

Joined: Feb 2009

Posts: 257

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

Jan 19, 2012, 02:00 PM
djazz is offline
Reply With Quote
Talking

Nice neobeo, will try that out someday!

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

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

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

#define	MAXTILES	 4096
#define	MAXANIMTILES	 256

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

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

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

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

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

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

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



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



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



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



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



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



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

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

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




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



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



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



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



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

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

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



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


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



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



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



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



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



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



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



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


#pragma pack(pop)
#endif // __FILEFORMATS_H__??
Quote:
Originally Posted by Michiel
there ya go
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
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Jan 19, 2012, 02:20 PM
Violet CLM is offline
Reply With Quote
So the null layer properties (discounting z-index/depth) are Type, Detail Level, and Wave X and Wave Y. Fascinating. I am tempted to try one last time to get them to do something.
__________________
Jerrythabest Jerrythabest's Avatar

JCF Member

Joined: Apr 2005

Posts: 2,602

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

Jan 20, 2012, 08:45 AM
Jerrythabest is offline
Reply With Quote
So really, no one has ever thought of mailing Michiel before?

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

JCF Member

Joined: Feb 2009

Posts: 257

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

Jan 20, 2012, 02:04 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Jerrythabest View Post
So really, no one has ever thought of mailing Michiel before?
indeed
Saw his name in JCS's about dialog, added him on google+ and showed him WebJCS. He was impressed. Now we keep in contact on fb chat.
If you have something you want to ask him, add him or send me a PM.
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

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

Administrator

Joined: Mar 2001

Posts: 6,936

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

Jan 20, 2012, 05:47 PM
Stijn is offline
Reply With Quote
Doesn't JJ2+ already have an option to host TSF as 1.23?

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

JCF Member

Joined: Oct 2011

Posts: 193

Toni_ is doing well so far

Jan 21, 2012, 02:24 AM
Toni_ is offline
Reply With Quote
Quote:
Originally Posted by Stijn View Post
Doesn't JJ2+ already have an option to host TSF as 1.23?
I think it does. /multiversion on lets the 1.23 players join the TSF server. I guess it's the same.

Quote:
Originally Posted by Stijn View Post
What's the actual benefit of having the source code? People seem to be able to do stuff without it just fine (f.ex. Neobeo in the recent cheating thread)
It can help a lot in making anti cheats. Also, don't give the source code to everyone if you get it. I suggest only nimrod, djazzy, and someone else who doesn't want / never made and published bad cheats should get the source code.
Neobeo

JCF Member

Joined: Sep 2002

Posts: 409

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

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

JCF Member

Joined: Sep 2004

Posts: 4,126

Grytolle is a forum legendGrytolle is a forum legendGrytolle is a forum legend

Jan 21, 2012, 06:39 PM
Grytolle is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
[*]To port to a different system. Since there's a Mac version I assume it's endian-friendly and doesn't rely on Windows-specific code. I for one would really like to see JJ2 run on the Wii.[/LIST]
They had to hire someone to port it, I think, and the MAC version is really buggy...
__________________
<center></center>
Old Jul 30, 2012, 03:57 PM
zapS
This message has been deleted by zapS.
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

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

JCF Member

Joined: Aug 2002

Posts: 1,181

minmay is immeasurably awesomeminmay is immeasurably awesomeminmay is immeasurably awesomeminmay is immeasurably awesomeminmay is immeasurably awesome

Jul 31, 2012, 04:00 PM
minmay is offline
Reply With Quote
I think he just means it probably can't reference words past 256 since it is only one byte and thus only has 256 possible values.
Old Aug 1, 2012, 08:19 AM
zapS
This message has been deleted by zapS.
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Aug 1, 2012, 01:27 PM
Violet CLM is offline
Reply With Quote
Quote:
Originally Posted by Violet CLM View Post
Could you post an example .j2l?
Not that words are traditionally listed in stream1 anyway.
__________________
Old Aug 1, 2012, 01:52 PM
zapS
This message has been deleted by zapS.
djazz djazz's Avatar

JCF Member

Joined: Feb 2009

Posts: 257

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

Oct 2, 2012, 01:54 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Ice M A N View Post
.j2d - There's very little useful stuff:
ogLogo.ColorLUT
Menu.ColorLUT
Shield.Plasma
SoundFXList.Ending
SoundFXList.Intro
SoundFXList.P2
SoundFXList.Logo
Menu.Texture.128x128
Menu.Texture.32x32
Menu.Texture.16x16
Tiles.Order
Tiles.Offset
Credits.Palette
Order.ShadeLUT
Order.Palette
Order.Newspaper
Order.Colors
ogLogo.Balls
Balls.Dump
Picture.OrderTexture128x128
Picture.Continue
Picture.CreditsEaster
Picture.Credits
Picture.Loading
Picture.Thanks
Picture.EasterTit
Picture.Title-XMas
Picture.Title
ogLogo.Palette
Std.Palette
Menu.Palette
Using this information only, I was able to make a J2D viewer, that shows some info and all the images that can be found in the 1.20/1.23 Data.j2d. I haven't bothered to figure out how the header and the first stream are structured, I'm no good at that. So please excuse my magic number constants
If you're interested in how it works, check out the source links!


Here's a link that maybe is interesting: http://djazz.mine.nu/lab/j2d/
I have only tested it in Google Chrome. Enjoy!


Some exported images:






This is actually six images:

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

JCF Member

Joined: Apr 2005

Posts: 2,602

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

Oct 2, 2012, 03:03 PM
Jerrythabest is offline
Reply With Quote
Wait, are you saying this Picture.thanks OEM screen is still in our J2D's? Or was that extracted from 1.00g/h?
__________________
Ice M A N

JCF Member

Joined: Jan 2001

Posts: 580

Ice M A N is doing well so far

Oct 2, 2012, 09:00 PM
Ice M A N is offline
Reply With Quote
Quote:
Originally Posted by DJazz View Post
Using this information only [...]
Impressive. Well, more surprising than anything. That quoted post gave me a quick 6.5+ year flashback...
djazz djazz's Avatar

JCF Member

Joined: Feb 2009

Posts: 257

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

Oct 20, 2012, 08:14 AM
djazz is offline
Reply With Quote
The J2S file format

Behold, a complete JJ2 language editor!

http://djazz.mine.nu/lab/j2s/

I made it on request from Grytolle, who wanted to improve his Swedish translation of JJ2.
Here it is, including translation of the helpstrings in the default levels:
http://www.jazz2online.com/downloads...language-file/
Some are really funny, if you speak Swedish.

Quote:
J2S File Format
From J2NSM Haxia
The Jazz Jackrabbit 2 Language file. Contains text strings and some other stuff.
Code:
struct J2S_File_Format {
	unsigned int offset_array_size; // Size of the array with string offsets for the first text array
	unsigned int text_array_size; // Size of the first text array
	unsigned char text_array[text_array_size]; // The first text array - contains null-terminated strings
	unsigned int offset_array[offset_array_size / 4]; // An array of integers, each containing the offset for a string in the first array.
	unsigned int level_entry_count; // Number of level entries
	level_entry entry[level_entry_count]; // See the level_entry struct definition below
	unsigned int text_array_2_size; // The total size of the secondary text array
	array_2 text_array_2[]; // Secondary text array - see definition below
};
Code:
struct level_entry { // 12 bytes each
	unsigned char name[8]; // Null terminated...
	char zeroByte;
	char unknown;		// Some amount maybe
	unsigned short offset; // Some offset
};
I am not sure whether is a good idea to use a variable-length struct to display the data, but it's only for informational purposes.

Code:
struct array_2 { // Secondary text array entry
	unsigned char some_index; // Probably has to do with level entries eg. which entry this text belongs to
	unsigned char t_size; // Size of the following text
	unsigned char text[t_size]; // The text itself (variable-length)
};
That's a brief definition of the J2S format. The first text array contains messages that you get to see during the game like Player entered the game, Player was roasted by Grytolle, etc. The offset array tells you where a certain text begins. Strings inside this array are null-terminated. The second half of the J2S contains some outdated level data. Probably the devs considered putting text that was to be displayed in levels into language files, but later moved it into J2L structure.
It is not a complete specification, see my code for how I did it. Also, TSF language files contains more helpstrings (in text_array_2), but TSF supports loading 1.20-1.23 language files. My program can only handle 1.20-1.23 languages. ENJOY!
__________________
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
Grytolle Grytolle's Avatar

JCF Member

Joined: Sep 2004

Posts: 4,126

Grytolle is a forum legendGrytolle is a forum legendGrytolle is a forum legend

Oct 20, 2012, 08:21 AM
Grytolle is offline
Reply With Quote
Quote:
Player was roasted by Grytolle
I applaud your realistic examples!
__________________
<center></center>
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Oct 20, 2012, 11:27 AM
Violet CLM is offline
Reply With Quote
Quote:
Originally Posted by DJazz View Post
The second half of the J2S contains some outdated level data. Probably the devs considered putting text that was to be displayed in levels into language files, but later moved it into J2L structure.
But JJ2 does use the text strings in the .j2s file, not the ones in the .j2l. That's why a hypothetical car.j2l will use the text strings from carrot1.j2l instead of whatever ones you try to specify for it internally. Unless I'm misunderstanding what you're saying here?
__________________
djazz djazz's Avatar

JCF Member

Joined: Feb 2009

Posts: 257

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

Oct 20, 2012, 11:49 AM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Violet CLM View Post
But JJ2 does use the text strings in the .j2s file, not the ones in the .j2l. That's why a hypothetical car.j2l will use the text strings from carrot1.j2l instead of whatever ones you try to specify for it internally. Unless I'm misunderstanding what you're saying here?
JJ2 uses the text from the .j2s files. I have no idea how it detect the filenames. I just copypasted that specification from J2NSM.
__________________
WebJCS 2 (new and in progress)
WebJCS 1 (old but complete)
SGIP Simple Games in Progress list
Level Packer v2 - With a GUI!
PHP Tileset Compiler
DoubleGJ DoubleGJ's Avatar

JCF Member

Joined: Sep 2002

Posts: 3,049

DoubleGJ has disabled reputation

Oct 20, 2012, 04:34 PM
DoubleGJ is offline
Reply With Quote
No knowledge here. Just experience.
Yeah JJ2 does use the J2S basing on some not entirely specified level name data that causes conflicts. That's why at first the levels of my levelpack were using filenames in Polish.
Anyway, it's a pretty neat concept that does work good on a vanilla JJ2 (entirely vanilla, meaning no custom levels etc.). What I could propose, is that given we seem to have conquered J2S as a group (lol claiming rights oh well), we could incorporate new elements like pregame or new gamemode prompts into the language files!

Because... uh... we can. AAAAND there could be people out there who use JJ2+ and yet barely know English! (just a guess but it could very well be true)

if I sound unreasonable well then I have my reasons I'm an artist and all
__________________
"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)
jvbsl

JCF Member

Joined: Nov 2014

Posts: 2

jvbsl is doing well so far

Jul 2, 2015, 03:41 PM
jvbsl is offline
Reply With Quote
Quote:
Originally Posted by Neobeo View Post
The J2L File Format
I had never really looked into the header with that much detail, so some parts here were stolen from another post, at least for the LEVL Header. This is mostly for stuff I've never thought about, like "Hide level in Home Cooked List" or passworded levels.

The LEVL Header
Ok, without further ado let's rewrite that as a more comprehensible struct:
Code:
struct LEVL_Header {
	char Copyright[180];
	char Magic[4] = "LEVL";
	char PasswordHash[3]; //never really thought about this
	char HideLevel;
	char LevelName[32];
	short Version; //I only looked at one file, had a value of 0x202
	long FileSize;
	long CRC32;
	long CData1;
	long UData1;
	long CData2;
	long UData2;
	long CData3;
	long UData3;
	long CData4;
	long UData4;
}
I extracted the Hash function for the passwords - if anyone is interested in something like that
Code:
uint32_t lookup[] = {0x00000000, 0x77073096, 0x0EE0E612C, 0x990951BA, 0x76DC419, 0x706AF48F,
 0x0E963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0x0E0D5E91E,
 0x97D2D988, 0x9B64C2B, 0x7EB17CBD, 0x0E7B82D07, 0x90BF1D91,
 0x1DB71064, 0x6AB020F2, 0x0F3B97148, 0x84BE41DE, 0x1ADAD47D,
 0x6DDDE4EB, 0x0F4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0,
 0x0FD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0x0FA0F3D63,
 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0x0D56041E4, 0x0A2677172,
 0x3C03E4D1, 0x4B04D447, 0x0D20D85FD, 0x0A50AB56B, 0x35B5A8FA,
 0x42B2986C, 0x0DBBBC9D6, 0x0ACBCF940, 0x32D86CE3, 0x45DF5C75,
 0x0DCD60DCF, 0x0ABD13D59, 0x26D930AC, 0x51DE003A, 0x0C8D75180,
 0x0BFD06116, 0x21B4F4B5, 0x56B3C423, 0x0CFBA9599, 0x0B8BDA50F,
 0x2802B89E, 0x5F058808, 0x0C60CD9B2, 0x0B10BE924, 0x2F6F7C87,
 0x58684C11, 0x0C1611DAB, 0x0B6662D3D, 0x76DC4190, 0x1DB7106,
 0x98D220BC, 0x0EFD5102A, 0x71B18589, 0x6B6B51F, 0x9FBFE4A5,
 0x0E8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0x0E10E9818,
 0x7F6A0DBB, 0x86D3D2D, 0x91646C97, 0x0E6635C01, 0x6B6B51F4,
 0x1C6C6162, 0x856530D8, 0x0F262004E, 0x6C0695ED, 0x1B01A57B,
 0x8208F4C1, 0x0F50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
 0x0FCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0x0FBD44C65,
 0x4DB26158, 0x3AB551CE, 0x0A3BC0074, 0x0D4BB30E2, 0x4ADFA541,
 0x3DD895D7, 0x0A4D1C46D, 0x0D3D6F4FB, 0x4369E96A, 0x346ED9FC,
 0x0AD678846, 0x0DA60B8D0, 0x44042D73, 0x33031DE5, 0x0AA0A4C5F,
 0x0DD0D7CC9, 0x5005713C, 0x270241AA, 0x0BE0B1010, 0x0C90C2086,
 0x5768B525, 0x206F85B3, 0x0B966D409, 0x0CE61E49F, 0x5EDEF90E,
 0x29D9C998, 0x0B0D09822, 0x0C7D7A8B4, 0x59B33D17, 0x2EB40D81,
 0x0B7BD5C3B, 0x0C0BA6CAD, 0x0EDB88320, 0x9ABFB3B6, 0x3B6E20C,
 0x74B1D29A, 0x0EAD54739, 0x9DD277AF, 0x4DB2615, 0x73DC1683,
 0x0E3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0x0E40ECF0B,
 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0x0F00F9344, 0x8708A3D2,
 0x1E01F268, 0x6906C2FE, 0x0F762575D, 0x806567CB, 0x196C3671,
 0x6E6B06E7, 0x0FED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
 0x0F9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0x0D6D6A3E8,
 0x0A1D1937E, 0x38D8C2C4, 0x4FDFF252, 0x0D1BB67F1, 0x0A6BC5767,
 0x3FB506DD, 0x48B2364B, 0x0D80D2BDA, 0x0AF0A1B4C, 0x36034AF6,
 0x41047A60, 0x0DF60EFC3, 0x0A867DF55, 0x316E8EEF, 0x4669BE79,
 0x0CB61B38C, 0x0BC66831A, 0x256FD2A0, 0x5268E236, 0x0CC0C7795,
 0x0BB0B4703, 0x220216B9, 0x5505262F, 0x0C5BA3BBE, 0x0B2BD0B28,
 0x2BB45A92, 0x5CB36A04, 0x0C2D7FFA7, 0x0B5D0CF31, 0x2CD99E8B,
 0x5BDEAE1D, 0x9B64C2B0, 0x0EC63F226, 0x756AA39C, 0x26D930A,
 0x9C0906A9, 0x0EB0E363F, 0x72076785, 0x5005713, 0x95BF4A82,
 0x0E2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0x0E5D5BE0D,
 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0x0F1D4E242, 0x68DDB3F8,
 0x1FDA836E, 0x81BE16CD, 0x0F6B9265B, 0x6FB077E1, 0x18B74777,
 0x88085AE6, 0x0FF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF,
 0x0F862AE69, 0x616BFFD3, 0x166CCF45, 0x0A00AE278, 0x0D70DD2EE,
 0x4E048354, 0x3903B3C2, 0x0A7672661, 0x0D06016F7, 0x4969474D,
 0x3E6E77DB, 0x0AED16A4A, 0x0D9D65ADC, 0x40DF0B66, 0x37D83BF0,
 0x0A9BCAE53, 0x0DEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0x0BDBDF21C,
 0x0CABAC28A, 0x53B39330, 0x24B4A3A6, 0x0BAD03605, 0x0CDD70693,
 0x54DE5729, 0x23D967BF, 0x0B3667A2E, 0x0C4614AB8, 0x5D681B02,
 0x2A6F2B94, 0x0B40BBE37, 0x0C30C8EA1, 0x5A05DF1B};
uint32_t hash(char* input)
{
	uint32_t hash = 0xFFFFFFFF;
	int count = strlen(input);

	for (int i=0;iuint32_t currentChar=input[i] & 0xFF,lookupIndex=hash;
		lookupIndex = lookupIndex & 0xFF;
		lookupIndex = lookupIndex ^ currentChar;
		hash = hash >> 8;
		hash ^= lookup[lookupIndex];
	}
	hash = ~hash;
	hash &= 0xFFFFFF;
	return hash;
}
Edit: It is actually the normal CRC-algorithm(don't use that implementation - use other ones found on the internet) - look post below. Just the lower 3 bytes are used(LE)
can probably still be optimized and written better as I translated it myself from ASM.

Last edited by jvbsl; Jul 2, 2015 at 04:13 PM.
Stijn Stijn's Avatar

Administrator

Joined: Mar 2001

Posts: 6,936

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

Jul 2, 2015, 03:54 PM
Stijn is offline
Reply With Quote
Also known as the CRC algorithm
jvbsl

JCF Member

Joined: Nov 2014

Posts: 2

jvbsl is doing well so far

Jul 2, 2015, 04:06 PM
jvbsl is offline
Reply With Quote
it is the normal CRC? God damnit I should have looked first^^

Edit: It indeed is but just 3 Bytes are actually used. But now atleast everyone knows

First post and already posted bullshit^^

Last edited by jvbsl; Jul 2, 2015 at 04:12 PM. Reason: Checked
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Jul 2, 2015, 04:36 PM
Violet CLM is offline
Reply With Quote
FWIW the specs on the ERE are by now more complete than the ones in this thread: J2L J2T J2A
__________________
PrinceOthman

JCF Member

Joined: Aug 2015

Posts: 4

PrinceOthman is doing well so far

Aug 3, 2015, 06:26 PM
PrinceOthman is offline
Reply With Quote
So, how would one come around to extracting all the sprites in the game? Trying to work on a fan-game here.
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Aug 3, 2015, 08:35 PM
Violet CLM is offline
Reply With Quote
Here you go

or Spriters Resource has a lot of stuff too, if you'd find that format more convenient

(although, I don't know if you can beat this)
__________________
djazz djazz's Avatar

JCF Member

Joined: Feb 2009

Posts: 257

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

Aug 4, 2015, 12:21 PM
djazz is offline
Reply With Quote
Quote:
Originally Posted by Violet CLM View Post
__________________
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
Old Aug 5, 2015, 02:16 AM
Gamerz31w
This message has been deleted by Stijn. Reason: does gamerz31w even posting in right thread?
InfamousStar

JCF Member

Joined: Oct 2015

Posts: 6

InfamousStar is doing well so far

Oct 6, 2015, 08:13 AM
InfamousStar is offline
Reply With Quote
I wrote a Python script to extract the sound effects from Anims.j2a. Information on the Jazz2's sample format is scarce, so I ended up relying heavily on DJazz's PHP code. Anyway, here it is:

Code:
#!/usr/bin/env python2

import wave

from os import mkdir
from StringIO import StringIO
from struct import unpack
from zlib import decompress

# These functions return the next 8/16/32 bits of a file as a little endian int.
def readInt8(f): return unpack('<b', f.read(1))[0]
def readInt16(f): return unpack('<h', f.read(2))[0]
def readInt32(f): return unpack('<l', f.read(4))[0]


def parseAlibHeader(j2a):
    """Parses a j2a file's header, and returns a dict of the contents.
    
    Entries in returned dict:
    "magic number" -- 'ALIB'; identifies file format
    "unknown1" -- 0x00beba00; purpose unknown
    "header size" -- size of this header, in bytes
    "version" -- 0x0200; probably means v2.0
    "unknown2" -- 0x1808; purpose unknown
    "file size" -- obvious
    "crc32" -- CRC32 hash of file
    "set count" -- number of animation sets in file
    "set offsets" -- list containing the offsets for each set, in bytes
    """

    j2a.seek(0)

    header = {}
    header["magic number"] = j2a.read(4)
    header["unknown1"] = readInt32(j2a)
    header["header size"] = readInt32(j2a)
    header["version"] = readInt16(j2a)
    header["unknown2"] = readInt16(j2a)
    header["file size"] = readInt32(j2a)
    header["crc32"] = readInt32(j2a)
    header["set count"] = readInt32(j2a)
    header["set offsets"] = [readInt32(j2a) for i in range(header["set count"])]

    return header

def parseAnimHeader(j2a, offset):
    """Parses an animation set's header, and returns a dict of the contents.

    Entries in returned dict:
    "magic number" -- 'ANIM'; marks start of set
    "animation count" -- number of animations in this set
    "sample count" -- number of sound samples in set
    "frame count" -- total number of frames in all animations in set
    "prior sample count" -- total number of samples before this set
    "animation info",
    "frame info",
    "image data",
    "sample data" -- dicts detailing the size of each part of the set

    The rest of the set is compressed with zlib, so the last 4 entries above
    contain these entries:
    "compressed" -- actual size in file
    "uncompressed" -- size when uncompressed
    """

    j2a.seek(offset)

    header = {}
    header["magic number"] = j2a.read(4)
    header["animation count"] = readInt8(j2a)
    header["sample count"] = readInt8(j2a)
    header["frame count"] = readInt16(j2a)
    header["prior sample count"] = readInt32(j2a)

    for key in "animation info", "frame info", "image data", "sample data":
        header[key] = {
            "compressed": readInt32(j2a),
            "uncompressed":readInt32(j2a)
        }

    return header

def sampleDataOffset(setOffset, animHeader):
    """Returns the offset of an animation set's sample data."""

    offset = setOffset + 44  # animHeader is 44 bytes
    for key in "animation info", "frame info", "image data":
        offset += animHeader[key]["compressed"]

    return offset

def decompressInFile(f, offset, size):
    """Decompresses portion of file, returning result as a StringIO."""

    f.seek(offset)
    return StringIO(decompress(f.read(size)))

def parseSample(data, offset):
    """Parses a sample, returning a dict of the contents.

    Entries in returned dict:
    "size" -- combined size of all entries
    "chunk id" -- 'RIFF'; identifies chunk type
    "chunk size" -- size of everything below this, except "padding3"
    "format" -- 'AS  '; identifies format contained in RIFF
    "samp subchunk" -- dict containing format info
    "data" -- waveform data of sample
    "padding" -- 8 bytes of padding

    "samp subchunk" contains these entries:
    "id" -- 'SAMP'; identifies header
    "padding1" -- 48 bytes of padding (probably)
    "data size" -- size of data, in bytes
    "padding2" -- 8 bytes of padding
    "sample rate" -- samples per second
    """

    data.seek(offset)

    sample = {}
    sample["size"] = readInt32(data)
    sample["chunk id"] = data.read(4)
    sample["chunk size"] = readInt32(data)
    sample["format"] = data.read(4)

    samp = {}
    samp["id"] = data.read(4)
    samp["padding1"] = data.read(48)
    samp["data size"] = readInt32(data)
    samp["padding2"] = data.read(8)
    samp["sample rate"] = readInt32(data)

    sample["samp subchunk"] = samp

    sample["data"] = data.read(samp["data size"])
    sample["padding3"] = data.read(8)

    return sample

def parseSampleData(data, sampleCount):
    """Parses sample data, returning a list of samples.

    See parseSample for info on elements.
    """
    
    samples = []

    data.seek(0)

    # In theory, parsing one sample should put me at the start of the next.
    # Unfortunately, there's at least one place where this fails, so I have to
    # keep track of the offset, manually.
    offset = 0
    for i in range(sampleCount):
        samples.append(parseSample(data, offset))
        offset += samples[-1]["size"]

    return samples

def writeWav2(filename, sample):
    # todo: don't overwrite file if it already exists
    wavFile = wave.open(filename, 'w')

    # the 1st two are hardcoded until I find this info in the sample
    wavFile.setnchannels(1)
    wavFile.setsampwidth(2)
    wavFile.setframerate(sample["samp subchunk"]["sample rate"])

    wavFile.writeframes(sample["data"])

    wavFile.close()


def main():
    filename = "Anims.j2a"  # I can probably just leave this hardcoded

    # todo: throw error if file not found
    with open(filename, 'r') as j2a:
        alibHeader = parseAlibHeader(j2a)  # find animation sets
        # todo: verify file with checksum

        print("{} found.".format(filename))
        print("Total size: {} bytes".format(alibHeader["file size"]))
        print("{} animation sets found.\n".format(alibHeader["set count"]))

        # todo: don't crash if this exists already
        mkdir("sounds")
        
        # for each set
        for setNum, offset in enumerate(alibHeader["set offsets"]):
            animHeader = parseAnimHeader(j2a, offset)  # find sample data

            print("Decompressing data for set {}...".format(\
                    alibHeader["set offsets"].index(offset)))

            # find samples
            decompressed = decompressInFile(j2a,\
                    sampleDataOffset(offset, animHeader),\
                    animHeader["sample data"]["compressed"])
            samples = parseSampleData(decompressed, animHeader["sample count"])

            print("{} samples found. Writing to disk...".format(len(samples)))

            # write samples
            wavName = "sounds/sfx_{}_{}.wav"
            for sampleNum, sample in enumerate(samples):
                writeWav2(wavName.format(setNum, sampleNum), sample)

if __name__ == '__main__':
    main()
Violet CLM Violet CLM's Avatar

JCF Éminence Grise

Joined: Mar 2001

Posts: 10,690

Violet CLM has disabled reputation

Oct 6, 2015, 11:26 AM
Violet CLM is offline
Reply With Quote
Well done!
__________________
Reply

Thread Tools

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

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

Forum Jump

All times are GMT -8. The time now is 06:50 PM.