Turbo Pascal 3.0 Image Format
Both Pharaoh's Tomb and Arctic Adventure store their tile images directly inside the game's executable. The image data is stored in the same image format used by Turbo Pascal 3.0's "Extended Graphics" and "Turtlegraphics" driver, just with extra padding at the end of each image.
This driver was superseded by the BGI driver in Turbo Pascal 4.0, but the later versions generally still included the old driver in the form of the GRAPH3.TPU unit for compatibility, albeit with practically no documentation on what the procedures and functions in this unit actually do and how to use them.
Each image begins with the following 6-byte header:
0 | UINT16LE | iNumBits | bits per pixel, 1 for 640x200 mode, 2 for 320x200 mode (always 2 in Pharaoh's Tomb and Arctic Adventure) 2 | UINT16LE | iWidth | image width, in pixels 4 | UINT16LE | iHeight | image height, in pixels
The Turbo Pascal 3.0 Reference Manual lists the following formula for calculating the minimal size of a buffer for a given image in 320x200 mode:
Size = ((Width + 3) div 4)*Height*2 + 6
And the official size requirement for images in 640x200 mode is:
Size = ((Width + 7) div 8)*Height + 6
That means a 16x16 pixel image will require 134 bytes in this format when using the formula for 320x200 mode. But given the format and the way the image data is stored, 70 bytes should be enough. The formula should really have used Height + 6 instead of Height*2 + 6 at the end. Perhaps this implies that support for 16-color graphics was supposed to be implemented, or maybe it was just an error in the Turbo Pascal 3.0 documentation.
The image data is stored as raw CGA-like data, left to right, bottom to top. Unlike the memory layout of CGA graphics cards, the odd and even lines of the image are not split in any way. The first byte of image data contains the color information for the first 4 (or 8) pixels on the left of the bottom-most row of the image.
The most-significant bit(s) inside each byte of image data store the color index for the leftmost pixel covered by that byte. If iNumBits is 2, the two most significant bits store the color index for the leftmost pixel (a color index from 0 to 3) and there are 4 pixels per byte. If iNumBits is 1, the most significant bit stores the color index for the leftmost pixel (either 0 or 1) and there are 8 pixels stored in each byte.
If the width, in pixels, does not fill all bits of the last byte in each row, the extra bits will be ignored. For example, if the width of the image is 1, only the most significant bit (or the two most significant bits in 320x200 mode) of each byte will contain the color index for that one pixel and the other bits will be ignored when drawing the image.
Sample C code to "draw" an image to the screen in a context where x=0 is the left edge of the screen and y=0 is the top edge of the screen would look like this:
void DrawImage(int filehandle, int px, int py) { UINT16 iNumBits, iWidth, iHeight; UINT16 x, y; iNumBits = ReadUINT16LE(filehandle); iWidth = ReadUINT16LE(filehandle); iHeight = ReadUINT16LE(filehandle); for (y = iHeight-1; y >= 0; y--) { BYTE buffer; int bitsleft = 0; for (x = 0; x < iWidth; x++) { BYTE colorindex; if (bitsleft == 0) { buffer = ReadBYTE(filehandle); bitsleft = 8; } colorindex = buffer >> (8-iNumBits); DrawPixel(px+x, py+y, colorindex); buffer <<= iNumBits; bitsleft -= iNumBits; } } }
In Pharaoh's Tomb and Arctic Adventure, all images are 16x16 pixels, but every tile image is always 156 bytes long, not 134 or 70. This wastes quite a lot of memory, but is harmless otherwise. The tile images are stored as a single block inside the executables.
File Name | Ver. | EXE Size | Offset | Number of Tiles -----------+------+----------+---------+---------------- PTOMB1.EXE | v3.0 | 0x1F730 | 0x18250 | 160 tiles PTOMB2.EXE | v3.0 | 0x1F790 | 0x182B0 | 160 tiles PTOMB3.EXE | v3.0 | 0x1F6E0 | 0x18200 | 160 tiles PTOMB4.EXE | v3.0 | 0x1F6E0 | 0x18200 | 160 tiles AA1.EXE | v2.0 | 0x22800 | 0x1C450 | 152 tiles AA2.EXE | v2.0 | 0x22790 | 0x1C3E0 | 152 tiles AA3.EXE | v2.0 | 0x22780 | 0x1C3D0 | 152 tiles AA4.EXE | v2.0 | 0x227C0 | 0x1C410 | 152 tiles
Older versions of Pharaoh's Tomb (v2.0, v2.2, v2.3) store some of the tile images in a slightly different order than the later versions (v2.9, v3.0), but every version stores them as a sequence of 156-byte-blocks with no other data in between these blocks.
Note: The EXE Size values given in the table are the executable image sizes. The offsets in the table are relative to the start of the executable image. Since the original executables are compressed with LZEXE, the size of the uncompressed executable and the absolute position of the data in it may vary depending on which program was used to decompress the files. UNLZEXE might produce a slightly different file than UNP, for example. To calculate the absolute file position, read a UINT16LE value at position 8 in the decompressed executable and multiply that value by 16 to get the start offset of the executable image. Add that value to the offset given in the table to get the correct file position.
The following patch scripts allow you to extract the tile image data with K1n9_Duk3's Patching Utility:
%exefile ptomb1.exe 0x1F730 %dump tileset.pt1 $18250 $6180 %abort
%exefile aa1.exe 0x22800 %dump tileset.aa1 $1C450 $5CA0 %abort
The %exefile mode causes the patching utility to operate on the executable image, so you can use the EXE sizes and offsets given in the table and don't need to worry about caclulating an absolute file position.
The same tool can also be used to insert modified tile image data into the executable. Simply use a script in the following form:
%exefile ptomb1.exe 0x1F730 %patchfile $18250 tileset.pt1 0 $6180 %end
%exefile aa1.exe 0x22800 %patchfile $1C450 tileset.pt1 0 $5CA0 %end
And don't forget to save the patched file at the end.
Trivia: Since the 16x16 pixel images in Pharaoh's Tomb and Arctic Adventure really only take up 70 bytes, the remaining 86 bytes in each image contain whatever data was previously stored at that memory location when the images were grabbed. This can be literally anything, even parts of the game's source code.
The following pieces of source code can be found in Pharaoh's Tomb:
d(7 ,'x x xx x x x'); d(8 ,'xx xx '); d(11,'xxxxxx Y xxxxx'); d(12,'xxxxxxxxxxxxxxxxxxx object.frame := object.frame + 1; If object.frame = 5 Then object if (getdotcolor(man.x,man.y+x)=0) and (getdotcolor(man.x+15,man.y+x) ve to answer 10 in a row to get the bonus. By registering this game, you will receive
The last two lines definitely come from either Trivia Whiz or Next Generation Trivia and have nothing to do with Pharaoh's Tomb. The lines calling the "d" procedure at the beginning are how the levels are defined in the source code for Pharaoh's Tomb, but this particular layout does not appear to be used for any of the levels in the final game.