Ouch my eye!

Do not look at LASER with remaining eye!


PIC: The lost version

As I’ve been slowly working on getting the PIC file format code into a releasable state, another title popped up. Well not so much another title, but a rare, and nearly forgotten, version of a title. In this case it’s a 16 colour EGA specific release, while the version we’ve previously looked at was targeted for 256 colour VGA. The title in question? “Gunship 2000” (GS2K) from 1991. The title is interesting in the fact that, like “F117A”, it relies on the original PIC format and not the later ones MicroProse was already using with other titles at the time. Likely due to being based on older game engine code. As such, I didn’t expect much, why would it be any different than the version we already looked at? Guess someone at MicroProse didn’t get the memo. 🤣

Gunship 2000 – Title Screen (EGA)
Gunship 2000 – Title Screen (VGA)

Just like the VGA version, the EGA version of GS2K only exposes a small number of PIC assets in the install directory, the rest are contained within a CAT file. The exposed assets mostly appear to be cockpit overlay images.

-rw-rw-rw-  6733   20 Jan  1992 APA_CPIT.PIC
-rw-rw-rw-  5218    6 Nov  1991 BLK_CPIT.PIC
-rw-rw-rw-  5845    6 Nov  1991 COB_CPIT.PIC
-rw-rw-rw-  5633    6 Nov  1991 COM_CPIT.PIC
-rw-rw-rw-  4405   20 Jan  1992 DEF_CPIT.PIC
-rw-rw-rw-  5458    6 Nov  1991 KIA_CPIT.PIC
-rw-rw-rw-  2733    4 Nov  1991 REPLAY.PIC
-rw-rw-rw-  424172 15 Nov  1991 GS2000.CAT

The astute among you might have noticed the two assets with 1992 dates, despite me saying this was a 1991 release. The game had a patch released in early 1992 that appears to have updated a few of the assets. With that said, we’ll start with the images in the open before extracting anything from the CAT file.


First Look

We’ve spent a fair amount of time developing code for the PIC format, we might as well dive right in and send it to see what we get. It’s not like we’re expecting anything new at this point… right?

% pic2png APA_CPIT.PIC

MicroProse PIC to PNG File Converter
Opening: 'APA_CPIT.PIC' (6733 bytes)
Attempting to determine size of PicV1 image
Unable to determine size of PicV1 image, Please include size in command line

Well that was unexpected, we’ve developed code to determine the decompressed size and use that to set the image dimensions for PicV1 images, as they don’t include dimension data. We’ve always seen the PicV1 images be full-screen images, so it should have found a fit. Maybe they’re using a non-standard size here? Let’s try forcing it.

% pic2png APA_CPIT.PIC -r320x200

MicroProse PIC to PNG File Converter
Opening: 'APA_CPIT.PIC' (6733 bytes)
Image is: 320x200 
Decompressing image
Error: PIC failure 04
done

That’s very strange! What’s going on here? An error code of 04 indicates that the image buffer ran out of space before the compressed stream ended. So the image is more that 320×200? We know the game itself runs at 320×200, maybe there is some extra data appended to the end of the image. The program won’t complain if the image buffer has extra space, only if there’s not enough. Let’s try double height.

% pic2png APA_CPIT.PIC -r320x400

MicroProse PIC to PNG File Converter
Opening: 'APA_CPIT.PIC' (6733 bytes)
Image is: 320x400 
Decompressing image
256 Colour image
done

Now that seems to have done the trick. Though odd it is being determined to be 256 colour. The code range-checks the colours, as this is EGA it should only be 16 colour, but maybe that’s in the extra data. With that said, let’s see what we got, as we have an image now.

APA_CPIT.PIC (forced to 320×400)

It appears the encoding is as we expect for PicV1, but odd that our sizing code failed as we don’t see anything below the image and the image is clearly 320×200. Maybe I have a bug, though it has worked fine for all the other images I’ve passed in. Let’s try with 201 lines, maybe there is a one-off error somewhere?

APA_CPIT.PIC (forced to 320×201)

Well that is certainly better at first glance. Though there is the black line at the bottom, to allow for the “extra data”, but we don’t see anything there. There is also the bright magenta pixel about halfway down, not sure what that’s about.

After looking at it more, I noticed some more weird pixels at the top left corner, and the image on the left edge seems off, there’s a weird warp near the bottom, almost like everything is shifted over a few pixels. More investigation is needed I think.


Digging Deeper

After adding a few debug prints to the code, and running a few of the images through it, it appears we are always getting exactly 4 extra bytes than we expect for a 320×200 image. Time to dig into the hex of the decoded data to see what’s there. Initially I was thinking the extra data was appended to the end of the image, but given we are seeing what looks like a shift in the image data, and we have several garbage pixels in the top left, it looks like we may have prefixed data in this case.

          00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  40 01 C8 00 05 05 05 05  05 05 05 05 05 05 05 05  @...............
00000010  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 08  ................
00000020  08 00 00 00 00 00 08 00  00 00 00 00 07 07 05 05  ................
00000030  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000040  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000050  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000060  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................

We know we have 4 extra bytes, and if we ignore those 4, the rest of the data looks to be what we would expect for a 16 colour image. As for the four bytes, those values look strikingly familiar!

          00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  40 01 C8 00 05 05 05 05  05 05 05 05 05 05 05 05  @...............
00000010  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 08  ................
00000020  08 00 00 00 00 00 08 00  00 00 00 00 07 07 05 05  ................
00000030  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000040  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000050  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................
00000060  05 05 05 05 05 05 05 05  05 05 05 05 05 05 05 05  ................

40 01: 0x0140 (320) Image Width
C8 00: 0x00c8 (200) Image Height

If we take those four bytes and split them into two 16 bit values, we can see that we get the image dimensions. That’s new! We’ve not seen MicroProse put the header inside of the compressed stream before, at least not with PIC images. It looks like we have a new sub-variant of PicV1 here. I guess we’ll call it PicV1.5 as it is clearly a PicV1 based image, we don’t see any of the other artifacts we see with other versions of the format. We’ll need to somehow account for this in our code now, as we have extra data within the compressed stream itself, not something I had anticipated. We’ve accounted for all four bytes of extra data we’re seeing, so this should be the only hurdle left for this variant.


Adapt and Overcome

The first challenge here is that the format is indistinguishable from a normal PicV1 file, until the data has been decompressed. Now one of the things that I had been working on with my code was to make the code able to determine the image size (of a PicV1 image) without the user having to provide it. To do this I would run the decompression engine, discarding the output, only tracking the number of bytes decompressed. I then compared the size to the expected sizes for standard full-screen resolutions. This always worked, as the images were always full-screen. Now we potentially have some prefix data, so with a simple modification we can preserve the first four decompressed bytes, and then look at those if the size is not of a standard full-screen image. We can then calculate the expected size based on the dimension data and compare that to the actual decompressed size. If it matches we are PicV1.5, otherwise we are still unknown.

// NULL destination stream writer, that only counts bytes
// will capture up to the specified size of bytes, and just count after
int null_putc(int val, memstream_buf_t *buf) {
    if(buf->pos < buf->len) buf->data[buf->pos] = val;
    buf->pos++;
    return 0;
}

A single line added above manages our partial capture in the null_putc()routine. Now in our actual size calculation code we need a small buffer and add that into our null stream.

    // allocate our image buffer for counting only, and potential dimension data
    uint16_t dims[2];
    memstream_buf_t dst = {sizeof(dims), 0, (uint8_t *)dims};

Next is to adjust our sizing code to use the data, if it’s there, and change the type if necessary.

// apply dimensions here based on calculated size
    rval = set_dims_from_size(dst.pos, pic);
    if((PIC_NOERROR == rval) || (PICV1 != pic->version)) {
        return rval;
    }
    // try to see if we might be a V1.5
    size_t expect = dims[0] * dims[1] + 4; // calculate the size plus the "header"
    if(expect != dst.pos) { // not a match, then this must not be PicV1.5
        return rval;
    } 
    // otherwise if we got here it looks to be PicV1.5
    pic->width  = dims[0]; // 1st 16 bit value is the width
    pic->height = dims[1]; // 2nd 16 bit value is the height
    pic->version = PICV15; // update the version to 1.5
    return PIC_NOERROR;

The next challenge is in the actual decompression. Currently the code expects that the image is the only thing in the compression unit, and as such just uses the image buffer as-is. The problem now is the image buffer is not large enough on its own. We could try making some sort of special case to ignore the first four bytes, but that feels like too much of a hack. Instead I think it’s better to adjust our underlying image buffer to allow for extra metadata. Should be a pretty simple change. The other option would be to use an extra intermediary buffer, which I’d rather not do.

/// @brief neutral container for storing paletted images
/// default arrangement is [pixel data][extra data][palette data]
/// pointers may be re-ordered if desired
/// code should use the pointers and not assume any arrangement in 
typedef struct {
    unsigned width;       // width of image in pixels
    unsigned height;      // height of image in pixels
    unsigned colours;     // number of palette entries
    int      transparent; // index of transparent colour, -1 if not used
    size_t   image_size;  // size of image in bytes. 
    size_t   extra_size;  // size of additional data allocation space
    img_pal_entry_t  *pal;// pointer to the palette RGB data in buf[] 
    uint8_t  *extra;      // pointer to the extra data in buf[]
    uint8_t  *pixels;     // pointer to the pixel data in buf[]
    uint8_t  buf[];       // our allocated data buffer
} pal_image_t;

Originally we had the pixel data as the buffer itself, and the palette would just be tacked onto the end. Now we’ve set up a separate buffer var, and then have pointers into it for each of the data sections, this allows for the flexibility of re-ordering things as necessary, instead of having to move the data around. This changed required a few minimal changes to any code using the pal_image_t type, mainly for allocation, to allow for the extra parameter for the extra data amount. With that done, let’s see if it works.

% pic2png APA_CPIT.PIC

MicroProse PIC to PNG File Converter
Opening: 'APA_CPIT.PIC' (6733 bytes)
Attempting to determine size of PicV1 image
File appears to be PicV1.5
Image is: 320x200 
Decompressing image
16 Colour image
done
APA_CPIT.PIC

Well that seems to have worked, we have the correct type identification, number of colours, and size, and the decode looks good now too, the shift is gone. We still have that one bright magenta pixel (now on the right), though that looks to be in the data itself. All the cockpit images seem to have that dot around the halfway mark. It looks to define the parting line of the canopy section of the image, and the lower cockpit panels section.


Can you see the difference?

Now that we have it decoding correctly, let’s take a look at the differences between the patched and unpatched versions of the two cockpits that changed. I’ll render using transparency set to 5 (the magenta fill we see)

APA_CPIT.PIC (Unpatched)
APA_CPIT.PIC (Patched)
DEF_CPIT.PIC (Unpatched)
DEF_CPIT.PIC (Patched)

I don’t see any obvious changes in any of the corresponding images, so whatever it is it must be pretty subtle. Might as well look at the remaining ones while we’re at it.

BLK_CPIT.PIC
COB_CPIT.PIC
COM_CPIT.PIC

Checking under the bed

Now the question is are there any more surprises hiding in the PIC assets inside the CAT file? Good thing we have already decoded the CAT format, so checking is pretty easy, and it looks like the CAT file is not some new variant here.

Opening: 'GS2000.CAT'	File Size: 424172 bytes
Catalogue contains 70 items
 1:      ADS.PIC   8633  15 Nov 1991  08:34:12 [@00000692]
 2:     BASE.PIC   7247  15 Nov 1991  08:34:18 [@0000284b]
 3:  BASE_AN.PIC  11650  15 Nov 1991  08:34:22 [@0000449a]
 4: BASE_AN1.PIC   8410  15 Nov 1991  08:34:28 [@0000721c]
 5: BASE_AN2.PIC   5279  15 Nov 1991  08:34:32 [@000092f6]
 6: BPATCHES.PIC  12842  15 Nov 1991  08:34:38 [@0000a795]
 7: COMMISSN.PIC   8302  15 Nov 1991  08:34:42 [@0000d9bf]
 8:  COMM_AN.PIC   4346  15 Nov 1991  08:34:46 [@0000fa2d]
 9: COMMANDR.PIC   6989  15 Nov 1991  08:34:50 [@00010b27]
10: COMMAND2.PIC   6302  15 Nov 1991  08:34:56 [@00012674]
11: EURO6MAP.PIC   4537  15 Nov 1991  08:35:00 [@00013f12]
12: EURO7MAP.PIC   4920  15 Nov 1991  08:35:04 [@000150cb]
13: EURO8MAP.PIC   5121  15 Nov 1991  08:35:08 [@00016403]
14:    FIELD.PIC   7330  15 Nov 1991  08:35:12 [@00017804]
15:   FIELD2.PIC  11402  15 Nov 1991  08:35:18 [@000194a6]
16: FIELD_AN.PIC   3885  15 Nov 1991  08:35:22 [@0001c130]
17: FIELD_A2.PIC   3564  15 Nov 1991  08:35:26 [@0001d05d]
18: GULF3MAP.PIC   4584  15 Nov 1991  08:35:30 [@0001de49]
19: GULF4MAP.PIC   4112  15 Nov 1991  08:35:34 [@0001f031]
20: GULF5MAP.PIC   4009  15 Nov 1991  08:35:38 [@00020041]
21:    HANDS.PIC   2110  15 Nov 1991  08:35:42 [@00020fea]
22:    HAPPY.PIC  15349  15 Nov 1991  09:07:30 [@00021828]
23:  HAPPY_2.PIC  13673  15 Nov 1991  09:07:36 [@0002541d]
24: HAPPY_AN.PIC   4188  15 Nov 1991  09:07:40 [@00028986]
25:     JUMP.PIC   7728  15 Nov 1991  08:36:00 [@000299e2]
26: JUMPCMOH.PIC   9793  15 Nov 1991  08:36:06 [@0002b812]
27:      KIA.PIC   9039  15 Nov 1991  08:36:10 [@0002de53]
28:   KIA_AN.PIC   1597  15 Nov 1991  08:36:14 [@000301a2]
29:     LOSE.PIC  14376  15 Nov 1991  08:36:20 [@000307df]
30:    MEDAL.PIC   3628  15 Nov 1991  08:36:24 [@00034007]
31:     MEMO.PIC   1525  15 Nov 1991  08:36:28 [@00034e33]
32: MENU0021.PIC   4023  15 Nov 1991  08:36:32 [@00035428]
33: MENU3037.PIC   2277  15 Nov 1991  08:36:36 [@000363df]
34: MENU5057.PIC   1778  15 Nov 1991  08:36:40 [@00036cc4]
35: MENU8085.PIC   1479  15 Nov 1991  08:36:44 [@000373b6]
36:     PAGE.PIC   3007  15 Nov 1991  08:36:48 [@0003797d]
37:    PAGE2.PIC   3524  15 Nov 1991  08:36:52 [@0003853c]
38:  PATCHES.PIC  10330  15 Nov 1991  08:36:56 [@00039300]
39:    PILOT.PIC  14927  15 Nov 1991  08:37:02 [@0003bb5a]
40:  POINTER.PIC    652  15 Nov 1991  08:37:06 [@0003f5a9]
41:    RANKS.PIC  10184  15 Nov 1991  08:37:12 [@0003f835]
42:  REALITY.PIC   5254  15 Nov 1991  08:37:16 [@00041ffd]
43: REALI_AN.PIC   4173  15 Nov 1991  08:37:20 [@00043483]
44: REAL_PCH.PIC   6315  15 Nov 1991  08:37:24 [@000444d0]
45:   REPLAY.PIC   3178  15 Nov 1991  08:37:28 [@00045d7b]
46:   RESCUE.PIC   5897  15 Nov 1991  08:37:32 [@000469e5]
47:  RESC_AN.PIC   5048  15 Nov 1991  08:37:36 [@000480ee]
48: RESC_AN1.PIC    517  15 Nov 1991  08:37:40 [@000494a6]
49:   ROSTER.PIC   1533  15 Nov 1991  08:37:44 [@000496ab]
50:      SAD.PIC   8791  15 Nov 1991  08:37:50 [@00049ca8]
51:   SAD_AN.PIC   1053  15 Nov 1991  08:37:52 [@0004beff]
52:    SAD_2.PIC   9243  15 Nov 1991  08:37:58 [@0004c31c]
53:     SHIP.PIC   7663  15 Nov 1991  08:38:02 [@0004e737]
54:  SHIP_00.PIC   6463  15 Nov 1991  08:38:06 [@00050526]
55:  SHIP_01.PIC   6738  15 Nov 1991  08:38:12 [@00051e65]
56:  SHIP_02.PIC   4015  15 Nov 1991  08:38:16 [@000538b7]
57:  SHIP_03.PIC   4235  15 Nov 1991  08:38:20 [@00054866]
58:  SHIP_04.PIC   3955  15 Nov 1991  08:38:24 [@000558f1]
59:  SHIP_05.PIC   5419  15 Nov 1991  08:38:28 [@00056864]
60:  SHIP_06.PIC   4222  15 Nov 1991  08:38:32 [@00057d8f]
61:  SHIP_07.PIC   2523  15 Nov 1991  08:38:36 [@00058e0d]
62: TARMC_00.PIC   3011  15 Nov 1991  08:38:40 [@000597e8]
63: TARMC_01.PIC   2262  15 Nov 1991  08:38:44 [@0005a3ab]
64: TARMC_02.PIC   2680  15 Nov 1991  08:38:48 [@0005ac81]
65:     TENT.PIC  14997  15 Nov 1991  08:38:54 [@0005b6f9]
66:  THEATER.PIC   6206  15 Nov 1991  08:38:58 [@0005f18e]
67:   TITLE1.PIC   1449  15 Nov 1991  08:39:02 [@000609cc]
68:   TITLE2.PIC   3052  15 Nov 1991  08:39:06 [@00060f75]
69:   TITLE3.PIC   3788  15 Nov 1991  08:39:10 [@00061b61]
70:      WIN.PIC  20159  15 Nov 1991  08:39:16 [@00062a2d]
% pic2png LOSE.PIC

MicroProse PIC to PNG File Converter
Opening: 'LOSE.PIC' (14376 bytes)
Type is: PicV1
Image is: 320x200 Packed
Decompressing image
16 Colour image
done
LOSE.PIC

Now that is interesting, we seem to have reverted back to the original format, and not using the PicV1.5 version we just identified. A little strange to use two versions of the format in the same title, but we have seen this at least once before. Let’s look at a few more.

Fairly typical good quality art we’ve come to expect from MicroProse, especially considering the limited palette here. They are all in the original PicV1 format, so no more surprises.


I think that concludes this little adventure, and surprise variant. Best as I’ve been able to determine the PicV1.5 format is only used with the cockpit images of the EGA version of Gunship 2000. I have a little more work to do on the PIC code before I feel ready to release it, but it should be out fairly soon now. I hope to have everything wrapped up before the end of the year, so we can start on new adventures in the new year.

ADS.PIC

By Thread



5 responses to “PIC: The lost version”

  1. Great research!! Another one bites the dust ;D

  2. Good to see you still plugging away at this!

  3. Well done! Re: the “Can you see the difference?” section – the chaff and flare “CH” and “FL” indicator labels were swapped between the patched and unpatched versions. Maybe there was a misprint in the manual, or a difference between the game and real hardware layouts was identified and they decided to fix it?

    1. Good eye! No idea why they would have swapped. Maybe it was a coding error with the indicators swapped, and the easiest fix was to change the graphic asset to match, rather than update the binary.

      1. Out of curiosity I did an image compare with p4merge, the FL/CH switch-around is the only diff. @johnbear2015, really good eyes to spot that!

Leave a reply to Bobblen Cancel reply