III. Project Nomads NTX
Welcome to the third episode of "Figuring out File Formats". In this lessen we'll finally get some colorful pictures.
We will convert NTX textures that we extracted from last lesson's NPK file.
First we have to get files with which we can work. When looking through the NTX files that we extracted, we notice that almost all
of them either end on "none.ntx" or "alpha.ntx". We'll take this as an indicator that the format can hold images with and
without an alpha channel (transparency). To be able to properly work with them, we will use one file of each type. I'm using
"mauerwerk_detailmapnone.ntx", which should resamble a wall according to the name, and "johnjohn_alpha.ntx", which should
have something to do with the game character John.
Notice, that throughout the lesson I will often assume that something is always a certain way in those files. I have of course
checked that with other NTX files from the game, but that's not very interesting and doesn't need much explaination, so I don't
show you the extensive double checking of every detail.
Now let's get to our first step. We'll start with mauerwerk_detailmapnone.ntx. The file starts with "1XTN", which has to be read
backwards, as we know from the other tutorials. This identifier can also be found in johnjohn_alpha.ntx, so we'll just accept it
as identifier for the NTX format in general. next we can see that there's a table and with a little trying we find that the with is 32 bytes:
We can see that the last part of the table reaches 8 bytes into the following raw data, so additionally to the 4 bytes identifier there are also
4 more bytes outside the table. In our case this is the byte value "09 00 00 00", to be read as 9. Since it's directly infront of the table and we
still need a length or number of entries, we check if it by coincidence is one of those. Luckily it happens to be the number of entries. The same
works for johnjohn_alpha.ntx, so we are still evaluating common structures.
Next we have a look at the header table itself. By removing the first 8 bytes of the file we can fix the offset, so the lines are aligned properly.
I've also switched the byte grouping size of my hex editor to 4 to get more structure:
Let's first identifiy it step by step:
- In column 10 we can see a row index beginning with 0.
- Column 4 is always 2, column 10 is always 1. You can verify that with other NTX files.
- Column 18 contains "ugly" numbers in ascending order. These are typical indicators for offsets. It's always a good idea to check if there are
values that correlate with offsets nearby. Experience and logic thinking tell you that it would make sense if the size of a block plus its offset
define the offset of the next block. Taking column 1C as blocksize exactly fulfills this property, so we have now found its meaning, too.
- Column 1 seems to be the difference between None and Alpha: In "None" files it's 3, in "Alpha" files it's 4. This probably refers to the number
of color channels that each format uses. Images without alpha channel typicall have 3 channels (RGB) and images with an alpha channel have 4 (RGBA).
- Column 8 and C are identical (though I wouldn't take it for granted). Each row has exactly half the value of the preceding row. Knowing that we
are dealing with textures that sounds like width and height for squarish images with mipmaps. These mipmaps are precomputed downsized versions of the
original image to save computation time during the game. Instead of using the full image, the game can directly access a smaller version when needed
To conclude, our table entries have the following structure (each entry 4 bytes long):
[number of channels]  [width?] [height?]  [mipmap index] [offset] [block size]
Now we have to find out more about or raw data. A good start is to look at the relation between width, height and image size in bytes. Our first
block has width and height of 0x100 (how your say "100 in base 16) and a byte size of 0x20000. Assuming that the image has no compression (based
on the fact that the block size is not an ugly number), we can calculate the number of bytes per pixel as blocksize/(width*height) = 0x20000/(0x100*0x100) = 2.
A size of 2 bytes per pixel is quite ugly, but not too uncommon. one byte has 8 bits, so we have to distribute 2*8 bits onto 3 color channels (We are still looking at the image without an alpha channel).
There are typically two ways to do that. Either you assign 5 bits to each channel and drop the last one (or use it for binary transparency) or
you assign 5 bit to red and blue and 6 bits to green. Green normally gets more bits, because the human eye is better at distinguishing between
green shades than green or blue shades. Quite early in our file we can see FF appearing which means that one pixel has bitvalues of 1111 1111. This means to us (assuming that there is no binary transparency),
that every single bit is used and that we can hope it is the 5x6x5 RGB color model.
To test if our 2 byte 5x6x5 RGB assumtion is right, I propose a very lazy method: We use the raw image function of IrfanView. To do that efficiently, we copy our NTX file, change the file extension to ".raw" and remove the entire header in our hex editor. When opening the file with IrfanView we get this dialog:
We have to set the width and height to 0x100, which is 256 in base 10, the color depth to 16 bit, the bit pattern to 5x6x5, header size to 0 (we already remove the header manually) and make sure the iamge will not be grayscale. Next we get this fancy image:
That looks like a wall that we can accept. With this we declare the "none" format as decrypted. Any details regarding offets or anything aren't interesting. Also, we already found a way to convert the files by removing the header and opening them with IfranView. I admit that by this point I have no idea if the two bytes of each pixel have to be swapped before reading the colors, but that could again be tested easily when you write your decoding program (if you wish to and can do so).
Next we have to look at "alpha" files. The color depth can again be found to be 16 bit. Fitting 4 channels (RGBA) into 16 bits is easy as pie and gives us 4 bits for each channel. Writing a little program and swapping around the 4bit channels untill colors and transparency are correct, gives us this image:
I know that this time I left out many details. If needed, I can do the next lesson about going more into these details, so the NTX format would be defined precisely like the formats of lesson I and II.
This tells us that the image is stored upside down and (since we now know what ingame image we are looking for) is not flipped left to right. And now we are also done with the "alpha" images.
Thanks for reading. If you have any questions or want the tutorial about said details, please write to
hallfiry ät gmx döt de