END.CPA

Back to file listing

## The End-of-game image and animation

If you complete the game successfully, you are presented with an image of the baddies going up in flames. As was correctly deduced on the [1], this end animation is held in the END.CPA file. The graphic itself is a 288x128 EGA Colour-Palette image, which means it's limited to 16 colours. It does, however, have animation support

The above images were pulled directly from the file, not taken as a screenshot. The left one shows the complete animation but doesn't loop. That's because GIF files can only loop the whole animation but the end animation of Wasteland only loops the last four frames. The real loop is shown by the right animation.

## File Encoding

The base frame of the END.CPA file is encoded using two layers.

When it is being stored, the image is first run through the Vertical XOR encoder. This produces a file which has a large number of 0x00s and other popular bytes. This shows what the image looks like when it's prepared using this method:

This step merely prepares the image for the next stage, which is the Huffman Encoding. In theory, the Vertical XOR step should increase the number of 0x00 bytes (pixel is the same as the one above), which are huffman coded to a very short bitstring. In this form, 74,484 of the 109740 bytes are 0x00, which is about 67% of the image.

If you could reduce a 0x00 byte down to (say) a single bit, then 2/3rds of the image will be 1/8th of its former size. If you achieve no benefit from encoding the rest of the image, then you will still have managed reduced the image to 40% of its former size with no loss of definition.

The graphic itself is about 18k when it's uncompressed and displaying on-screen.

## Animation

It was quite a surprise to see that the end.cpa contains ==two== MSQ blocks and not just one as thought before. While the first block holds the base frame (normal huffman compressed pic as explained above) the second one contains the animation data.

Interesting about the second MSQ block is that it is the only MSQ block in the game which does **not** contain the "msq" identifier in the header. Instead it uses the byte sequence 0x08 0x67 0x01. Why? Don't know. Maybe a bug or a try to confuse hackers. If you replace these three bytes with "msq" then the animation still works. Looks like the game just ignores the three bytes. Before this msq identifier are the four bytes which tells us how many bytes we have to allocate to unpack the msq data and after the msq identifier is the disk id (0 in this case). And because all the bytes after the disk id is again a complete and valid huffman compressed data block it is a definite fact: It is really a MSQ block.

As already said the data of the MSQ block is huffman compressed. But it is not a PIC so no vertical XOR decoding must be used. After uncompressing the block we have the raw animation data.

The animation contains 16 frames (Frame 0 is the base frame from the first MSQ block, Frame 1-15 are the animation frames from the second MSQ block). After the game has finished displaying frame 15 it rewinds to frame 12. This animation loop is hardcoded in the game and is not a setting in the end.cpa file. Really a shame.

The animation starts with a 16 bit size value. This value just tells the game the size of the animation data. Quite useless because we already know the size from the MSQ header.

Each animation frame consists of a delay value (16 bit) and multiple update sequences. Each update sequence starts with an offset address (16 bit, tells the game where the update pixels must be drawn) and 4 update bytes. Each update byte simply contains the colors for 2 pixels so they work the same as all the bytes in the PIC file (after they have been vertical xor decoded). An animation frame ends with an update sequence containing only an offset address of 0xffff. The animations ends with a frame which just contains a delay value of 0xffff and an offset address of 0x0000.
Each animation frame is applied to the result of the previous frame. So each animation frame is responsible for restoring parts of the original picture if needed. Frame number 12 even must restore the changes made by frame 15 (because of the animation loop).

## Offset format

The offset address for update sequences is somewhat strange. The address does not fully depend on the base frame (which is 288 pixels wide) but instead depends on the screen width (320 pixels). But the starting point of the offset address is the upper left corner of the base frame and not the upper left corner of the screen. Really strange... And because an offset address of 0x0000 is invalid (it's part of the end-of-animation marker) it is not possible to animate the first 8 pixels in the upper left corner of the picture because the offset address can only address blocks of 8 pixels. So offset 0 (which is not usable) addresses position x0/y0 while offset 1 addresses position x8/y0. If an offset address is 0x0000 then the animation ends there even if the delay value is not 0xffff.

Here are three formulas to convert the offset address into x/y coordinates and vice versa:

offset = (y \* 320 + x) / 8

y = offset \* 8 / 320

x = offset \* 8 % 320

### Example

A very simple animation with two frames may look like this:

1e 00 The size of the following animation data

05 00 Before the frame is drawn wait 5 time units

d2 07 Draw next pixels to position x=20, y=50

00 00 00 00 Draw a black line of 8 pixels

fa 07 Draw next pixels to position x=20, y=51

01 23 45 67 Draw 8 pixels in various colors.

ff ff End of first frame

0a 00 Before the frame is drawn wait 10 time units

d2 07 Draw next pixels again to position x=20, y=50

ff ff ff ff Replace the black line drawn in the first frame with a white line

ff ff End of second frame

ff ff 00 00 End of animation

## Some thoughts

This animation format is really strange and it smells like a quick and dirty solution. It only allows updating 8 pixels at once. If you just want to change one pixel in a line then you still have to update 8 pixels. You can't access the first 8 pixels in the picture. The animation loop is hardcoded. The offset address is somewhat picture oriented and somewhat screen oriented. Smells like a marketing guy told the programmer five minutes before a deadline that the end picture must be animated. Maybe this is also the reason why the second MSQ block does not have a "msq" identifier. Maybe the programmer was in a hurry and thought: "What was that damn identifier? Ah, to hell with it, the game ignores it anyway so I just enter random data".

## How this was found

Each animation in the allpicsX files (containing the animated encounter pictures) had the base frame in one MSQ block and the animation frames in the next MSQ block. So I thought it would just be a logical consequence if the end.cpa had the same format. So after taking a long look at the remaing bytes of the end.cpa file after removing all the bytes belonging to the first MSQ block (the base frame) I saw that the beginning of the data looked like a MSQ block. The four size bytes and the disk number and three bytes between them. So I just tried it out. If there is a size definition before the MSQ identfier then the block must be huffman-compressed. So I just decompressed the data and it fitted perfectly. After the last byte of the allocated memory was filled I also reached the last byte of the file. And the first two uncompressed bytes looked very familiar because it was nearly the same value as the size of the allocated memory. So it was definitely huffman-compressed.

The rest was just try-and-error. I changed byte by byte and looked how the game reacted to my changes. So I first found out about the delay value, then the offset address and then the 8 update bytes and how frames were separated.