Pharaoh's Tomb & Arctic Adventure Fullscreen Image Format
Format type | Image |
---|---|
Hardware | CGA |
Colour depth | 2-bit (CGA) |
Minimum size (pixels) | 320×200 |
Maximum size (pixels) | 320×200 |
Palette | Standard CGA |
Plane count | 1 |
Transparent pixels? | No |
Hitmap pixels? | No |
Games |
This format is used by Pharaoh's Tomb and Arctic Adventure to store full-screen (320 × 200) images. Known extensions are *.mnu, *.map, and *.pix, with the latter two being unique to Arctic Adventure. It's not currently known if the format is able to store images of different sizes, since there are no other known examples of files in this format yet other than those found in the aforementioned two games.
Pharaoh's Tomb only stores the title screen background graphic using this file format. Arctic Adventure uses it for the title screen (AA?.MNU), map/overworld background screen (AA?.MAP), and a few pieces of large text (AA?.PIX), like "Please Wait...", combined into a single image.
Image
Files start with a 128-byte header, whose format and purpose is currently unknown, followed by variable length RLE-compressed image data. When uncompressed, the data is a raw CGA graphics mode screen, exactly 16000 bytes in size. It consists of 200 rows of 80 bytes, with each byte describing 4 pixels. To correctly interpret the data, CGA palette 0 (green/red/brown) should be used for Pharaoh's Tomb, and palette 1 (cyan/magenta/light gray) for Arctic Adventure.
Interestingly, the headers for Arctic Adventure contain mostly 0s, whereas the files for Pharaoh's Tomb have the same bytes filled with repeating sequences of 0xC1 0xFF 0xBF. Other bytes are mostly identical between the headers of both games.
RLE Compression
The compression scheme is fairly simple. Any byte value less than or equal to 0xC0 should be copied verbatim. When encountering a value greater than 0xC0, the byte following afterwards should be repeated value - 0xC0 times. For example, the byte sequence 0xC2 0xFF 0x0F expands to 0xFF 0xFF 0x0F. There is no specific end marker, so data should be read until reaching end of file. As a safe-guard against corrupt or unexpected files, it's probably a good idea to also stop reading as soon as 16000 bytes of unpacked data have been produced.
Source Code
The following C# class can load files in this format and convert them into System.Drawing.Bitmap objects, which can be saved using standard formats.
public static class AaPtImageParser
{
// Loads an image in Arctic Adventure/Pharaoh's Tomb format into a Bitmap object.
// usePalette1 should be set to true for Arctic Adventure, false for Pharaoh's Tomb.
public static Bitmap LoadImage(string filename, bool usePalette1)
{
var cgaBytes = UnpackImage(filename);
var image = new Bitmap(320, 200);
var palette = usePalette1 ? CGA_PALETTE_1 : CGA_PALETTE_0;
// Convert CGA image data to RGBA
for (var row = 0; row < 200; ++row)
{
for (var col = 0; col < 80; ++col)
{
for (var pixel = 0; pixel < 4; ++pixel)
{
var shift = (3 - pixel) * 2;
var mask = 0b11 << shift;
var bits = (cgaBytes[col + row * 80] & mask) >> shift;
image.SetPixel(col * 4 + pixel, row, palette[bits]);
}
}
}
return image;
}
private static Color[] CGA_PALETTE_0 = [
Color.FromArgb(255, 0, 0, 0),
Color.FromArgb(255, 0, 0xAA, 0),
Color.FromArgb(255, 0xAA, 0, 0),
Color.FromArgb(255, 0xAA, 0x55, 0),
];
private static Color[] CGA_PALETTE_1 = [
Color.FromArgb(255, 0, 0, 0),
Color.FromArgb(255, 0, 0xAA, 0xAA),
Color.FromArgb(255, 0xAA, 0, 0xAA),
Color.FromArgb(255, 0xAA, 0xAA, 0xAA),
];
private static byte[] UnpackImage(string filename)
{
byte[] cgaBytes = new byte[16000];
int dstIndex = 0;
using (var stream = File.Open(filename, FileMode.Open))
{
// Skip over file header
stream.Seek(0x80, SeekOrigin.Begin);
// Decompress RLE data
var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
var marker = reader.ReadByte();
if (marker > 0xC0)
{
// Repeating sequence
var value = reader.ReadByte();
for (var i = 0; i < marker - 0xC0; ++i)
{
cgaBytes[dstIndex] = value;
dstIndex++;
}
}
else
{
// Single byte
cgaBytes[dstIndex] = marker;
dstIndex++;
}
}
}
return cgaBytes;
}
}
Credits
This graphic format was reverse engineered by Lethal_guitar. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)