views:

113

answers:

2

I've made a simple resource packer for packing the resources for my game into one file. Everything was going fine until I began writing the unpacker. I noticed the .txt file - 26 bytes - that I had packed, came out of the resource file fine, without anyway issues, all data preserved. However when reading the .PNG file I had packed in the resource file, the first 5 bytes were intact while the rest was completely nullified.

I traced this down to the packing process, and I noticed that fread is only reading the first 5 bytes of the .PNG file and I can't for the life of me figure out why. It even triggers 'EOF' indicating that the file is only 5 bytes long, when in fact it is a 787 byte PNG of a small polygon, 100px by 100px.

I even tested this problem by making a separate application to simply read this PNG file into a buffer, I get the same results and only 5-bytes are read.

Here is the code of that small separate application:

#include <cstdio>

int main(int argc, char** argv)
{
    char buffer[1024] = { 0 };
    FILE* f = fopen("test.png", "r");
    fread(buffer, 1, sizeof(buffer), f);
    fclose(f);        //<- I use a breakpoint here to verify the buffer contents
    return 0;
}

Can somebody please point out my stupid mistake?

+13  A: 

Can somebody please point out my stupid mistake?

Windows platform, I guess?

Use this:

FILE* f = fopen("test.png", "rb");

instead of this:

FILE* f = fopen("test.png", "r");

See msdn for explanation.

SigTerm
Wow. Thank you so much.
Sam Blackburn
Note that the "rb" is in the C standard and portable. It mirrors "rt" which asks for "text" treatment of the stream, which is the default. Text treatment on any *nix is the same as binary treatment. On Windows and a few other more obscure platforms they differ. In short, if you are dealing with binary data, always specify "rb" to `fopen()`.
RBerteig
+3  A: 

Extending the correct answer from SigTerm, here is some background of why you got the effect you did for opening a PNG file in text mode:

The PNG format explains its 8-byte file header as follows:

The first eight bytes of a PNG file always contain the following values:

   (decimal)              137  80  78  71  13  10  26  10
   (hexadecimal)           89  50  4e  47  0d  0a  1a  0a
   (ASCII C notation)    \211   P   N   G  \r  \n \032 \n

This signature both identifies the file as a PNG file and provides for immediate detection of common file-transfer problems. The first two bytes distinguish PNG files on systems that expect the first two bytes to identify the file type uniquely. The first byte is chosen as a non-ASCII value to reduce the probability that a text file may be misrecognized as a PNG file; also, it catches bad file transfers that clear bit 7. Bytes two through four name the format. The CR-LF sequence catches bad file transfers that alter newline sequences. The control-Z character stops file display under MS-DOS. The final line feed checks for the inverse of the CR-LF translation problem.

I believe that in text mode, the call to fread() was terminated when it read the sixth byte which contains a Ctrl+Z character. Ctrl+Z was historically used in MSDOS (and in CPM before it) to indicate the end of a file, which was necessary because the file system stored the size of a file as a count of blocks, not a count of bytes.

By reading the file in text mode instead of binary mode, you triggered the protection against accidentally using the TYPE command to display a PNG file.

One thing you could do that would have helped diagnose this error is to use fread() slightly differently. You didn't test the return value from fread(). You should. Further, you should call it like this:

...
size_t nread;
...
nread = fread(buffer, sizeof(buffer), 1, f);

so that nread is a count of the bytes actually written to the buffer. For the PNG file in text mode, it would have told you on the first read that it only read 5 bytes. Since the file cannot be that small, you would have had a clue that something else was going on. The remaining bytes of the buffer were never modified by fread(), which would have been seen if you initialized the buffer to some other fill value.

RBerteig