Saturday, November 29, 2014

Parsing Creatures 1 .spr files (Extracting/Manipulating any of the game's graphics)

In a previous article I showed how to read Creatures 2 .s16 files to extract any of the game image data.
In this obvious follow up I will show you how to read out Creatures 1.spr files for the same result.

The Creatures 1 .spr file format is only marginally harder to parse than the .s16 format.
This is because the .spr files do not directly contain color information for each pixel, but rather an index number, to be used as a lookup index inside an external palette file to get the actual color data.
Thankfully, Python magic can once again save us most of the hard work and makes extracting an manipulating C1 images files a breeze.

The .spr file format

In C1, all images are stored inside .spr files ("spr" standing for "Sprite").
Each spr file contains one or more images.
The file format for those is quite simple and explained on various places like on the Creatures Wiki.
It also contains many similarities with the .s16 format.

The general format for an spr file is as such:

Where each sprite header is of the following format (and exactly the same as for .s16 files)

Where "Offset for data" is the position from the beginning of the whole file where data for the corresponding image is found.
The size of the data field can be easily computed with "image width x image height"
This makes indexing of the file easy, and contrary to the other file formats studied so far, we can read out single pictures out of spr files without needing to parse the whole file first.

Obviously, if you want to extract all images contained within the file, the process can be as simple as:
- Read File count
- Read as much file headers as the file count specifies.
- Read image data in chunks of sizes obtained my multiplying the given image width by it's height.

Interpreting the image data


In .spr files, the data section for a given image is just a list of pixels.
Each pixel is encoded on a singe byte.

This byte acts as a reference to an color definition in a palette.
The palette is stored externally in the  "Gamepath\Palettes\palette.dta" file.

Therefore, for each pixel read out from the image data section, we need to take that pixel value, and read out color information stored in the palette at the position given by the pixel data.

The format for the palette.dta file is quite simple.
It consists of 256 entries (the number of colors) each made up from a set of 3 bytes, representing Blue, Green, and Red values for that color.
It bears no header whatsoever, so you simply need to read the file data in 3 byte series,256 times to get the RVB values for each color of the palette.

There's only one catch to it.
For whatever reason, the color definitions bytes stored in this palette are only in the 0-63 range.
Before applying the palette to the picture, we will need to multiply every single byte of the palette by 4 to obtain the original picture.
Failure to do so will result in dark and poorly contrasted images.

Image with a default 256 grayscale palette

Image with the raw palette as read from the palette.dta file

Image with each byte of the palette multiplied by 4 as required

Putting it all together

Once again, here is a small POC showing how to extract all pictures from a .spr file using Python.
This time too we're lucky, as the amazing "PIL" Library supports all the modes we need to get to the image data, making it extraction a one line process.

import struct
import Image

def readLong( readfromfile ):
    return struct.unpack("L","L")))[0]

def readWord( readfromfile ):
    return struct.unpack("H","H")))[0]

def readSpriteHeader(readfromfile):
    return header

# Open a sample file

#Read the palette file as a list of bytes.
palette=[ord(byte) for byte in list(open("Palette.dta","rb").read())]


# Read the first 32 bits corresponding to number of images to expect
print "The file contains %d images" % (TheFile["nbImg"])

#Read out as many image headers as there are files
for i in range(1,TheFile["nbImg"]+1):
    print "Image N° %d starts at %d and is %d x %d" % (i,TheFile[i]["Offset"],TheFile[i]["Width"],TheFile[i]["Height"])

# For each of the expected images:
for i in range(1,TheFile["nbImg"]+1):
    #Read the image data
    #Create PIL image from the data, "P" means "Palette mode"
    im=Image.fromstring("P", (TheFile[i]["Width"], TheFile[i]["Height"]),TheFile[i]["data"], "raw","P")
    # Apply the game palette ( Remember that according to doc, all bytes of the palette should be multiplied by 4 before usage
    # Not doing so will result in dark images

    im.putpalette([color * 4 for color in palette])
    # Output each sprite to a readable format."Image_"+str(i)+".png")

And there you have it, all images extracted from the .spr file in a commonly usable format:
(Remember that thanks to python magic , you only have to change the file extension in the last line of code above to output the image into any arbitrary file format.)

The corresponding code is stored on the LoneShee github along with all other code examples.

See you next time for some reversing of other C1 and C2 files that unfortunately were not not documented yet.

No comments:

Post a Comment