AMERICAN LASER GAMES FILES This document covers two file types commonly found in American Laser Games: LIB archives and MM videos. The documentation is provided as-is without any guarantee. 1.0 LIB ARCHIVES LIB files are archives found in American Laser Games productions (Crime Patrol, Mad Dog McCree, etc.) The format is straightforward and doesn't offer any encryption or compression. LIB files include a File Allocation Table (FAT) that only contains the names and locations of each chunk within the archive, as the actual file size is located at the offset specified by the FAT. The actual chunk data follows right after. 1.1 HEADER The header is located at the very beginning of the file and is only 6 bytes long. It starts with a magic number followed by the absolute offset to the FAT (the position from the beginning of the file.) UINT16 Magic; // Must be 0x03FC. UINT32 fatOffset; // Absolute offset to the FAT. 1.2 FILE ALLOCATION TABLE The File Allocation Table is located at fatOffset inside the LIB archive. It begins with the number of entries in the FAT, followed by each entry. UINT16 chkCount; // Number of entries in the FAT. entry_t entry[chkCount]; // Each entry. The entry_t type is 17-byte long and is structured as follows: UINT32 chkOffset; // Location of the chunk, absolute. CHAR chkName[13]; // Chunk name, NULL-terminated. Note that the last entry in the FAT is always invalid (blank name and offset located outside the file's boundaries) and should be ignored. 1.3 CHUNK Chunks can be stored anywhere in the file. The first unsigned 32-bit integer provides the length in bytes of the actual data. Thus, a chunk in the LIB file is structured as follows: UINT32 chkSize; // Length of the chunk, in bytes. UINT8 chkData[chkSize]; // Chunk data. To extract a specific chunk, read fatOffset and go to that location. Read chkCount and loop through all entries until entry[].chkName matches the targeted chunk. Go to the location specified by entry[].chkOffset and read chkSize. Reserve chkSize bytes of memory in a buffer, then fill the buffer with the data found in the archive, starting from the current location. 2.0 MM FILES MM files are video files using the IBM Photomotion format. The format itself is block-based like GIF files. When decoding a frame, always use the previously decoded frame as a base, and always place the drawing cursor in the top left corner of the frame. Encoded frames can use either of these compressions: a run-length encoding compression algorithm similar to PCX files, or a technique that involves 8-bit masks where each bit represents a pixel to replace, starting from a specific location. Compared to raw frame data, those techniques offer a compression ratio of 3:1 on average. The file also contains audio and color palette blocks. Each block starts with a 6-byte descriptor containing a block identifier and the length in bytes of the useful data. Blocks are structured as follows: UINT16 blkId; // Block type. UINT32 blkSize; // Length of blkData, in bytes. UINT8 blkData[blkSize]; // Actual block data. Here is a discussion of various fields: blkId Unsigned 16-bit integer. This field contains a value that identifies the block type. This value must be one of the following: Value Meaning 0x0000 Header 0x0002 Raw video frame 0x0005 Inter video frame; Uses a compression scheme similar to Z-Soft PCX format. 0x0008 Intra video frame; Uses a compression scheme where only certain pixels are updated. 0x000C Intra video frame Half-Horizontal. Writing operations affect a 2x1 pixel area. 0x000D Inter video frame Half-Horizontal. Writing operations affect a 2x1 pixel area. 0x000E Intra video frame Half-Horizontal and Half-Vertical. Writing operations affect a 2x2 pixel area. 0x000F Inter video frame Half-Horizontal and Half-Vertical. Writing operations affect a 2x2 pixel area. 0x0015 Audio sample (8 Khz) 0x0016 Audio sample (11 Khz) 0x0030 Color palette (full) 0x0031 Color palette (partial) blkSize Unsigned 32-bit integer. Size of the block data, in bytes. This value excludes the 6-byte descriptor. blkData[] A buffer of arbitrary data that is blkSize bytes long. The proper way to interpret the data depends on the value found in blkId field. 2.1 HEADER BLOCK (0x0000) The header block is either 22 or 24 bytes long (see blkSize) and must appear early in the file. Both the 22 and 24 bytes long header will contain the following fields: UINT16 numBlocks; // Blocks count. UINT16 frameRate; // Frames per second. UINT16 videoMode; // BIOS video mode. UINT16 videoWidth; // Frame width, in pixels. UINT16 videoHeight; // Frame height, in pixeks. UINT16 idRaw; // Identification number for raw. UINT16 idInter; // Identification number for inter. UINT16 idIntraHH; // Identification number for intraHH. UINT16 idInterHH; // Identification number for interHH. UINT16 idIntraHHV; // Identification number for intraHHV. UINT16 idInterHHV; // Identification number for interHHV. The 24-byte long version of the header block ends with this extra field: UINT16 idAudio; // Identification number for audio chunk. The header block doesn't include identification numbers for intra, color palettes (full or partial) and doesn't differentiate 8khz and 11khz audio samples. Except for idAudio, all other id??? fields should be ignored as games using this video format do not seem to care for their values. Here is a discussion of various fields: numBlocks Unsigned 16-bit integer. Number of blocks found in the MM file. frameRate Unsigned 16-bit integer. Number of frames displayed per second. This value is always 10 due to the target hardware these games were designed for (IBM-compatible machines equipped with a 286 processor and a single-speed CD-ROM drive.) videoMode Unsigned 16-bit integer. The BIOS graphic mode number the video was intended for. This value is always 0x13 for mode 13 (320x200 with 256 colors.) videoWidth Unsigned 16-bit integer. Width in pixels of a video frame. videoHeight Unsigned 16-bit integer. Height in pixels of a video frame. idRaw Unsigned 16-bit integer. Identification number for a raw video frame block. Should be ignored. idInter Unsigned 16-bit integer. Identification number for an inter video frame block. Should be ignored. idIntraHH Unsigned 16-bit integer. Identification number for an intra Half-Horizontal video frame block. Should be ignored. idInterHH Unsigned 16-bit integer. Identification number for an inter Half-Horizontal video frame block. Should be ignored. idIntraHHV Unsigned 16-bit integer. Identification number for an intra Half-Horizontal Half-Vertical video frame block. Should be ignored. idInterHHV Unsigned 16-bit integer. Identification number for an inter Half-Horizontal Half-Vertical video frame block. Should be ignored. idAudio Unsigned 16-bit integer. Identification number for an audio sample block. This field is only present in 24-byte long header blocks. 2.2 RAW VIDEO FRAME (0x0002) This block contains raw pixel data stored left to right and top to bottom. The block size must be videoWidth * videoHeight bytes long. The block is simply structured as follows: UINT8 index[blkSize]; // Each pixel. 2.3 INTRA VIDEO FRAME (0x0008, 0x000C, 0x000E) Intra video frames are encoded using a compression algorithm similar to PCX. The way the data must be decompressed is identical in all three block types, but the way pixels are rendered on the display frame is slightly different with blkId 0x000C using 2x1 pixels and blkId 0x000E using 2x2 pixels. To decode, read one byte of data from the data block: it may either be a color index or a run-length encoding marker depending on whether the most significant bit is set or clear. If the most significant bit is set: > Write the value to the display frame. If the most significant bit is not set: > Take the least significant 7 bits and add 2 to obtain a pixel count. > Read an extra byte to obtain the color index. > If the color index is 0, skip count pixels ahead without writing anything. > If the color index is not 0, write as many pixels as necessary to the display frame. Either advance the cursor to the left by 1 or 2 pixels depending on the block type. When the scanline is completed, you may either go down 1 or 2 lines depending on the block type. Keep going until the whole block has been processed. 2.4 INTER VIDEO FRAME (0x0005, 0x000D, 0x000F) Inter video frame blocks are by far the most complex ones. The way the data must be decompressed is identical in all three block types, but the way pixels are rendered on the display frame is slightly different with blkId 0x000D using 2x1 pixels and blkId 0x000F using 2x2 pixels. The block is structured as follows: UINT16 pxlPoolOfs; // Offset to the pixel pool. patch_t patch[]; // Patch descriptors. UINT8 pxlPool[]; // Pixel pool. The pxlPoolOfs field points to the location of the first pixel in the pixel pool, relative to the beginning of the block. The pixel pool itself is a large chunk of UINT8 that contains pixel indices. When one pixel is read from the pool, move to the next one in the buffer. The patch descriptors provide an absolute or relative coordinate into the display frame, a mask count, and a series of mask representing a set of 8 consecutive pixels. The patch_t type is structured as follows: UINT8 mskCnt; // Number of masks, and coord + 256. UINT8 coord; // Coordinate, relative or absolute. UINT8 mask[mskCnt & 0x7F]; // Masked pixels. Only the 7 least significant bits of the mskCnt field provide the number of masks included in the patch descriptor. The most significant bit is used to increase the following coord field. If the most significant bit is set, add 256 to coord, or add nothing if it is clear. If mskCnt is NULL, no pixels are replaced and the coord value must be added to the y coordinate of the drawing cursor. If mskCnt is greater than 0, use the coord value to replace the drawing cursor's x coordinate, then read as many masks as necessary. For each bit (from most to least significant) set in a given mask, read a color index from the pixel pool and write it at the current location, otherwise just move the drawing cursor ahead. Either advance the cursor to the left by 1 or 2 pixels depending on the block type. When the scanline is completed, you may either go down 1 or 2 lines depending on the block type. Keep going until all patch descriptors are parsed. Since there's is no way to know how many patch descriptors exist, you may either stop when the reading cursor reaches the pixel pool location, or stop when the pixel pool is exhausted (reading outside the block size.) 2.5 AUDIO SAMPLE (0x0015, 0x0016) This block contains raw unsigned 8-bit PCM audio samples at either 8,000 Hz (for blkId 0x0015) or 11,000 Hz (for blkId 0x0016.) 2.6 COLOR PALETTE - PARTIAL (0x0031) This block contains a selection of consecutive palette attributes described by two UINT16, and then the actual values as RGB triplets. UINT16 attrStart; // Starting attribute. UINT16 attrCount; // Number of attributes. attr_t attr[attrCount]; // Each attribute. The attr_t type is 3-byte long and contains the red, green, and blue intensity of the attribute. Each channel is stored on a byte (8-bit) but only the 6 least significant bits are used. The type is structured as follows: UINT8 red; // Red intensity (0 to 63). UINT8 green; // Green intensity (0 to 63). UINT8 blue; // Blue intensity (0 to 63). Here is a discussion of various fields: attrStart Unsigned 16-bit integer. First attribute to replace. attrCount Unsigned 16-bit integer. Number of attributes to replace. 2.7 COLOR PALETTE - FULL (0x0030) If blkId is 0x0030, blkSize is always 768. This block type contains the entirety of a 256-color palette and works exactly like block type 0x0031, except for the lack of the attrStart and attrCount fields (as they are assumed to be 0 and 256.) The block is simply structured as follows: attr_t attr[256]; // Each attribute.