I've managed to extract some graphics files from an archive of an old game. Haven't found anything about it online, so I'm trying my luck here, prehaps someone knows a similar format that can help me. It's a somehow encrypted/compressed graphics format that uses an external palette. I've managed to display a different graphics format from the game, .raw files, that were very similar to NetPBM files. This format however isn't as straight forward.
Here's what I know:
File extension: .cgf
File magic: CGFF
binwalk -E: 0.5 or 0.75, depends on "compression type", pretty much uniform across file
binwalk -X: DEFLATE streams, sometimes only 1 bit long (?)
Header structure is as follows. Names are an educated guess on what the field could be. Every field is 4 bytes long, numbers are signed ints stored in little endian.
Each line is 4 bytes in the file:
String CGFF (file magic)
Compression type, either "1" or "9" across all files
Number of layers
Number of layers, multiplied by 24
File size. If my guess is right, this is probably an unsigned int
"0" across all files but one, where it is "250"
"0" across all files
X Position |
Y Position |
"38" across all files |
Offset into the file starting after the headers |
The last 6 entries seem to make up a unit that repeats once for every layer.
Here's how I got to my guesses:
- Compression type: 1-files have a different entropy and structure than 9-files.
- Number of layers: Observarions like this: A file called "Ballammo" is loaded in a game where you have 10 snowballs to throw at targets. The header contains a "10" in this field. This works for other files too.
- Filesize: I had to guess this since the archive I extracted the files from stored incorrect file lengths, but in most cases the numbers are similar enough
- Position/Size: Files called "Background" always have the values 0 0 640 480. In some cases they were 640 480 -640 -480, hence my guess with the position. The size makes sense for a game of that time running in 4:3
- Layer offset: Guess, since the first entry is always 0 and the subsequent are bigger numbers. Also it lines up nicely like this.
Regarding the file structure, 1-files have higher entropy and don't really show any pattern. In 9-files however, most of the time a byte is followed by 0xff, rarely by something else. It's not RLE, I've tried that.
As requested, here's two hexdumps. One 9-compressed, one 1-compressed.
Oh right, I should say how these are supposed to look like. "Cursor.cgf" should contain a two hand-like cursors, one pointing and one grabbing. Regarding "Kid.cgf", the data says it contains 8 layers. Given the minigame the file loads in, this should contain 4 grinning mouths and 4 pairs of eyes.
I also forgot to mention that both images have transparency, this could however be implemented using the palette the game loads.
NOTE: I can't guarantee that these files are complete. They may contain garbage at the end or be incomplete. As I said, chances are good that the archive's stored file lengths are wrong, as I found a palette that contained the .wav header of the next file after extraction.
Here's how some of the layers of the files given should look like when correctly loaded. Three "Kid.cgf"-layers are lined in red, while one of the "Cursor.cgf"-layers is lined in green. Boxes are not exact.
I managed to point the game to an extracted file instead of an archived file and it loads just fine. After also writing a program that fully parses the header, extracting the layer data into seperate files and some minor experiments, here's my results on a type 1 image:
- The game doesn't mind files being too long.
- The data isn't just a bitmap. Assuming I correctly found the fields for width, height and layer offset, the lengths of the fields (= diff between two layer offsets) is always smaller than width*height. Also, randomly changing some bytes in the image section crashes that game on load.
- The x and y pos isn't relative to the screen but to something else. This was revealed by swapping two images; both were still drawn in the correct location.
- @pythonpython found the value 0x26 to be interesting. This value also is stored in the layer entries (I wrote '"38" in all files', see above).
- The image data contains info on where a "pixel line" ends. Example: Let a layer be 32x64. Swapping the header values for the image to be 64x32 results in an image that is cut off in the middle but gets displayed correctly.
I'll keep experimenting and update accordingly.