Nova Format
Format type | Image |
---|---|
Hardware | VGA |
Colour depth | 8-bit (VGA) |
Minimum size (pixels) | 0×0 |
Maximum size (pixels) | 65535×65535 |
Palette | External |
Plane count | 1 |
Transparent pixels? | No |
Hitmap pixels? | No |
Games |
Nova is an image format used in the game Cover Girl Strip Poker. The images typically have extension .PPP, but not all files with that extension in the game folder are images; in fact, the extension seems to be used for all game files that are RLE-compressed.
The image files can easily be identified by the fact that each .PPP image has its own colour palette in an accompanying .PAL file of the same name.
The format is named after the fact all files start with the string "NOVA", which is most likely the signature of the game's lead programmer, Samuel Sebastian Nova.
File format
The entire file, including the header, is compressed with a flag-based RLE compression. The very first step to read the file should be to unpack it.
Compression
The compression used is a flag-based run-length encoding algorithm, with 4-byte repeat commands. Because of its size, repeating sequences that are less than 4 bytes long are not compressed.
The repeat commands have the following structure:
Data type | Name | Description |
---|---|---|
BYTE | Flag | Flag value 0xFF |
BYTE | Value | Value to repeat |
UINT16LE | Repeat | Amount of times to repeat Value |
Header
Once unpacked, the file starts with the following header:
Offset | Data type | Name | Description |
---|---|---|---|
0x00 | UINT32LE | Magic1 | Magic number: the string "NOVA". As a single UInt32, the value is 0x41564F4E |
0x04 | UINT16LE | Width | Image width. |
0x06 | UINT16LE | Height | Image height. |
Note that for identification purposes, since the "NOVA" string at the start contains no repetitions itself, a quick and accurate preliminary check before doing the full decompression process can be to see if the first three bytes are the string "NOV". The last byte can technically not be guaranteed, since the following width and height could be filled with enough 0x41 values (the letter "A") to trigger its conversion to a run-length command. Still, either the 4th byte should be 0x41, or, as a flag repeat command, the 4th and 5th byte should respectively be 0xFF and 0x41.
Image data
This header is followed by the image data, which is simple linear 8-bit VGA data, to be visualised using the colours from the 256-colour 6-bit VGA palette in the accompanying .PAL file.
Code
RLE Decompression
This code was written by Nyerguds for the Engie File Converter, and is released under the WTF Public License.
public static Byte[] DecompressPppRle(Byte[] data)
{
Int32 len = data.Length;
UInt32 expandSize = (UInt32)len * 3;
Int32 uncompressedSize = data.Length * 3;
Byte[] bufferOut = new Byte[uncompressedSize];
Int32 ptr = 0;
// Decompress flag-based RLE.
// The flag is 0xFF. It is followed by one byte for the value to fill,
// and then two bytes for the amount of repetitions.
Int32 i;
for (i = 0; i < len; i++)
{
Byte value = data[i];
if (value != 0xFF)
{
if (ptr >= bufferOut.Length)
bufferOut = ExpandBuffer(bufferOut, expandSize);
bufferOut[ptr++] = value;
}
else
{
if (i + 3 >= len)
throw new ArgumentException("Data ends on incomplete repeat command!", "data");
value = data[++i];
Int32 repeat = data[++i] + (data[++i] << 8);
Int32 endPoint = repeat + ptr;
if (endPoint > bufferOut.Length)
bufferOut = ExpandBuffer(bufferOut, Math.Max(expandSize, (UInt32)repeat));
for (; ptr < endPoint; ptr++)
bufferOut[ptr] = value;
}
}
if (ptr < bufferOut.Length)
{
Byte[] bufferSized = new Byte[ptr];
Array.Copy(bufferOut, 0, bufferSized, 0, ptr);
bufferOut = bufferSized;
}
return bufferOut;
}
private static Byte[] ExpandBuffer(Byte[] bufferOut, UInt32 expandSize)
{
Byte[] newBuf = new Byte[bufferOut.Length + expandSize];
Array.Copy(bufferOut, 0, newBuf, 0, bufferOut.Length);
return newBuf;
}
RLE Compression
This code was written by Nyerguds for the Engie File Converter, and is released under the WTF Public License.
public static Byte[] CompressPppRle(Byte[] data)
{
Int32 len = data.Length;
Int32 curBufLen = len;
// Compressed data should never exceed original size since compression only triggers on sequences of 4 or more,
// though it could in case of 0xFF bytes, since they always need to be encoded with a flag.
Byte[] bufferOut = new Byte[curBufLen];
Int32 ptr = 0;
for (Int32 i = 0; i < len; i++)
{
Byte value = data[i];
Int32 repeat = i;
for (; repeat < len && data[repeat] == value; repeat++) { }
repeat -= i;
Boolean compress = repeat >= 4 || value == 0xFF;
Int32 needed = ptr + (compress ? 3 : 0);
if (curBufLen <= needed)
{
// Expand buffer if needed.
Int32 newLen = Math.Max(curBufLen + len, needed);
Byte[] newCompressBuffer = new Byte[newLen];
Array.Copy(bufferOut, 0, newCompressBuffer, 0, curBufLen);
curBufLen = newLen;
}
if (compress)
{
i += repeat - 1; // -1 because the loop itself obviously increments it
do
{
Int32 repeat16b = repeat > 0xFFFF ? 0xFFFF : repeat;
bufferOut[ptr++] = 0xFF;
bufferOut[ptr++] = value;
bufferOut[ptr++] = (Byte)repeat16b;
bufferOut[ptr++] = (Byte)(repeat16b >> 8);
repeat -= repeat16b;
// Fix for compressing too-small leftover repeats
if (repeat < 4 && value != 0xFF)
for (; repeat > 0; repeat--)
bufferOut[ptr++] = value;
} while (repeat > 0);
}
else
bufferOut[ptr++] = value;
}
Byte[] bufferSized = new Byte[ptr];
Array.Copy(bufferOut, 0, bufferSized, 0, ptr);
return bufferSized;
}
Tools
The following tools are able to work with files in this format.
Name | Platform | View images in this format? | Convert/export to another file/format? | Import from another file/format? | Access hidden data? | Edit metadata? | Notes |
---|---|---|---|---|---|---|---|
Engie File Converter | Windows | Yes | Yes | Yes | No | N/A |