Jets N Guns DAT

From XentaxWiki
Revision as of 10:06, 21 June 2021 by Mlgthatsme (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Jets 'n' Guns Editor

If there is still someone who happens upon this site here is something interesting: (this assumes you have the .dat files decompressed in the root folder of the game (in the DATA folder))

Add this to the Game.ini

[DEBUG]
master_of_universe = 1  //if 1 and press ctrl+e you get the editor


On a map press ctrl+e to bring up the ingame editor. (there are numerous shortcuts ctrl+a saves the map L toggles the trigger widget...)

Proof:
http://www.mediafire.com/convkey/cd15/a4lsuaz5zluq2a3zg.jpg http://www.mediafire.com/convkey/dcae/nwa4l4qddtnxfdnzg.jpg

The editor deciding byte is at 0x4CF3A8 if its not 0 the editor is shown. (it IS a byte)

Jets 'n' Guns .dat file

TOOL UPDATE

21/06/2021 - The original source code for the archive format Jets N Guns uses has been found.
The original Authors: 7gods - http://7gods.org/coding.html has the tool source available. I've gone ahead and uploaded a mirror here: https://drive.google.com/file/d/1mO0f3l259c08WCaT4X6yz9fDF6uudxnu/view?usp=sharing

Using Manager.exe you can extract and build Jng .DAT files.

TOOL

I have uploaded a zip here inside is a dll for anyone to use the functions described below and a small application for decompressing a jng.dat (just place the exe in the same folder, tested on the jng.dat from Jets n Guns Gold 1.222)

The above link is broken(It's up again.) ... OP please fix.

So recently I thought I play around with this data file I wanted to modify somethings but for that I needed to pack the archive not just unpack and considering the complexity of the compressing method it's not so easy. During testing the first scratch of my jng.dat file creator I noticed that if one file is not found in the jng.dat than the game (at least the 1.222) checks if it can be found as a normal file on the system relatively to the path of the exe. So if you unpack the original jng.dat and place the contents next to the jng.exe and delete or rename the dat file than it'll use your unpacked version (that is there is no need for a packer just the unpacker and that's there). (little hint: the unpacker creates a directory named decompress there is a DATA folder that is the one that should be in the same place as the jng.exe I'd like to point out thou that I tested it only with the v1.222)

DAT File

Format Specifications

Dat File header (at the begening of the .dat file)

  • byte[4] - unknown
  • uint32 - number of files
  • uint32 - offset to catalog
  • byte[4] - unknown


Catalog entry (one for each file)

  • uint16 - size of name
  • byte[size of name] - name
  • uint32 - decompressed size
  • uint32 - offset to filedata


Now this one above is tricky, the name is encrypted (kind of) with a xor encryption and the decompressed size concatenated with the offset is encrypted too with the same key. The decryption can be done with the code below where bufEAX is the pointer to the encrypted data, hossz is the size of the data in bytes, and key is the key.

void proc(char* bufEAX, unsigned int hossz, unsigned int key)
{
	unsigned int passzECX = key;
	unsigned int kthoszEDX = hossz >> 2;
	unsigned int i = 0;
	unsigned int ESI = 0;
	while(kthoszEDX >= 4)
	{
		*(unsigned int*)&bufEAX[i] ^= paszECX;
		ESI = *(unsigned int*)&bufEAX[i+4];
		paszECX += 0x732C2E17;
		ESI^= paszECX;
		*(unsigned int*)&bufEAX[i+4] = ESI;
		ESI= *(unsigned int*)&bufEAX[i+8];
		paszECX += 0x732C2E17;
		ESI ^= paszECX;
		*(unsigned int*)&bufEAX[i+8] = ESI;
		ESI = *(unsigned int*)&bufEAX[i+12];
		paszECX += 0x732C2E17;
		ESI ^= paszECX;
		*(unsigned int*)&bufEAX[i+12] = ESI;
		kthoszEDX -= 4;
		paszECX += 0x732C2E17;
		i += 16;
	}
	while(kthoszEDX != 0)
	{
		bufEAX[i] ^= *((unsigned char*)(&paszECX));
		i++;
		paszECX += 0x732C2E17;
		kthoszEDX--;
	}
}


For the first catalog entry the key is the offset to the catalog (defined in the Dat File header) the same key is used for both part; the file name and the size offset combo. It is quiet important that the decompressed size and the offset is treated as one data flow during decryption because the key is changing. The key used for the next catalog entry is calculated from the one used in the previous decryption by multiplying it with 0x17BC3 (that is 97219 and it's not a prime 97219 = 191 * 509 btw 0x732C2E17 = 1932275223 = 3 * 644091741).

File header

  • uint32 - offset to compressed data
  • uint32 - unknown
  • uint16 - size of the compressed data
  • byte[2] - thrown away (it's read with the size but never used)


File data (pointed to by the catalog, one for each file)

  • File header[x] - File headers to compressed/encrypted data segments
  • byte[x] - data usually (in jng.dat always) pointed to by the first File header


Just a comment: this compression is damn irritating.
Assume that we have a decompression method; buff is the compressed data, hosz is the length of the compressed data, kitomhosz is a pointer to a int32 decompressed length will be stored in, and kitomalloc is allocated memory for the decompressed data. (according to Second post it's a lzh algorithm... not sure abaout that but be it.)

void Kitom(char* buff,int hosz,unsigned int* kitomhosz,char* kitomalloc);

First of all the data isn't only compressed but encrypted too with the encryption mentioned above so it must be decrypted in order to be ready for Kitom to decompress it. The trick is that the data is split into some pieces but each segment has it's File header defined on the offset pointed to by the catalog so it's not sooo bad. "Decompression" is done by decrypting, decompressing each data segment and than concatenate the "decompressed" segments together in the order of the File headers. The length in bytes of File headers can be calculated by the function below; from the decompressed size property of the catalog entry of the corresponding file. (the number of File headers is getsecoffset(decompsize)/4/3 all File headers contains 3 uint32s wich are 4 bytes long each)

int getsecoffset(int from)
{
	int ret = from;
	ret += 0x7FFF;
	ret >>= 0x0F;
	((char*)&ret)[3] = 0;
	((char*)&ret)[1] = 0;
	((char*)&ret)[2] = 0;
	ret *= 3;
	ret <<= 2;
	return ret;
}

There are two last hmm... inconveniences; the "decompressed size" of any catalog entry and the "size of the compressed data" of any File header can be 0 (that's bad) but not in the same time (that's good). If the "decompressed size" is 0 than the number of File headers is 1 and we don't know the size of the decompressed data till the end of decompression so we have to guess it (if l is the compressed size than l+20971520 (it's 20Mb) is a suitable guess). If a compressed size is 0 than the data segment is not compressed just encrypted and the length of the encrypted data is min(0x8000,S) S is "decompressed size" - "size of decompressed data so far" (this means that if we still have 0x8000 bytes left in the decompression buffer the one that holds the real decompressed, decrypted and concatenated data than fill 0x8000 else fill the rest).