/* png-csum-fix.c - Fix checksums in a PNG file Originally by Willem van Schaik Revised, simplified and expanded by Robert Munafo. The main purpose of this program is to facilitate simple binary editing of PNG files (examples: changing PLTE entries; changing the gamma adjustment, or changing tEXt tags). After doing the obvious direct manipulation, the PNG file is invalid because the checksum(s) of any edited chunks disagree with the new data. Use this tool to repair the PNG by re-computing all checksums. COMPILING cc -o cfixpng png-csum-fix.c ORIGINAL AUTHOR'S NOTES This source code was found at www.schaik.com/png/pngcsum.html and had this description by the original author (Willem van Schaik) CheckSum Correction with PNGCSum Each chunk in a PNG image is verified for corrupted data using a CRC32 checksum, where CRC stands for Cyclic Redundancy Checksum. Check out the PNG Specification at W3C for more details on how the checksum is constructed. The four parts of a chunk are: * a four byte length field, * the chunk name, also four bytes, * followed by the data and * at the end a four byte field with the checksum. The checksum is created from the chunk name field and the data, therefore not including the length field. When you modify a PNG image by hand, like what I did to create PngSuite, the data changes and the checksum isn't valid anymore. For that purpose I created this little tool, called pngcsum, that takes such a modified PNG, recalculates the checksums and writes it out to a now valid PNG file. The tool assumes that while the data in a chunk can be changed or currupted, the length field of the chunk is still correct. REVISION HISTORY 20150305 Clean up indentation; add check for infile == outfile; beginning of special PLTE-handling 20150308 Add prototypes 20150313 Add table of chunk-ordering rules. Build table-of-contents while reading the input. Verify that PNG complies with (most of) the chunk ordering rules. Display bytes of IHDR. 20150315 Add argument parsing and options; help; and cp_add and --change-palette option. 20150318 Second filename is optional: if not given it will simply read the first file and show the list of chunks. 20150504 You can now add more data to the PLTE, as long as the result has fewer than 256 bytes. 20150916 Fix bug in realloc(new_palette) that had earlier palette bytes being cleared to 0's or filled with data from the TOC, both of which would have caused the colours to be wrong. 20150919 Fix another bug that made it ignore --change-palette unless --show-palette was also given. 20180225 The --show-text option actually works now. Add support for Adobe 'iTXt' chunks (zTXt not supported yet). BUGS and TO-DO Show table-of-contents of output file (where some chunks can have changed lengths and positions). BACKGROUND PNG file format specification: http://www.w3.org/TR/PNG/ */ #include #include #include #define STATIC_C32TAB typedef char s8; typedef unsigned char u8; typedef short s16; typedef unsigned short u16; typedef int s32; typedef unsigned int u32; typedef long long int s64; typedef unsigned long long int u64; /* ------------------------------ PROTOTYPES ------------------------------ */ u32 crc32 (u32 crc, unsigned char *buf, int len); char * safe_strncpy(char * s1, const char * s2, size_t n); void toc_init(void); void toc_add(u32 p, u32 l, char * t); void cp_add(char * data); int verify_precedence(char * cannot_follow, char * cannot_precede); void verify_all(void); unsigned char inout (FILE *fp_in, FILE *fp_out); unsigned char inonly (FILE *fp_in); unsigned char outonly (unsigned char c, FILE *fp_out); void usage(void); int main (int argc, char * argv[]); #ifdef STATIC_C32TAB # include "png-crc32-table.h" #else u32 crc32_table[256]; void adler_init(void); /* Not copyrighted 1990 Mark Adler */ /* Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. Polynomials over GF(2) are represented in binary, one bit per coefficient, with the lowest powers in the most significant bit. Then adding polynomials is just exclusive-or, and multiplying a polynomial by x is a right shift by one. If we call the above polynomial p, and represent a byte as the polynomial q, also with the lowest power in the most significant bit (so the byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, where a mod b means the remainder after dividing a by b. This calculation is done using the shift-register method of multiplying and taking the remainder. The register is initialized to zero, and for each incoming bit, x^32 is added mod p to the register if the bit is a one (where x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by x (which is shifting right by one and adding x^32 mod p if the bit shifted out is a one). We start with the highest power (least significant bit) of q and repeat for all eight bits of q. The table is simply the CRC of all possible eight bit values. This is all the information needed to generate CRC's on data a byte at a time for all combinations of CRC register values and incoming bytes. */ void adler_init() { u32 c; /* crc shift register */ u32 e; /* polynomial exclusive-or pattern */ unsigned int i; /* counter for all possible eight bit values */ int k; /* byte being shifted into crc apparatus */ /* terms of polynomial defining this crc (except x^32): */ static int p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; /* Make exclusive-or pattern from polynomial (0xedb88320) */ e = 0; for (i = 0; i < sizeof(p)/sizeof(int); i++) { e |= 1L << (31 - p[i]); } /* Compute table of CRC's */ for (i = 1; i < 256; i++) { c = i; /* The idea to initialize the register with the byte instead of * zero was stolen from Haruhiko Okumura's ar002 */ for (k = 8; k; k--) { c = c & 1 ? (c >> 1) ^ e : c >> 1; } crc32_table[i] = c; } } #endif u32 crc32 (u32 crc, unsigned char *buf, int len) { unsigned char *end; crc = ~crc; for (end = buf + len; buf < end; ++buf) { crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); } return ~crc; } /* End of crc32 */ #define STRINGSIZE 256 static char prog[STRINGSIZE]; static char file_in[STRINGSIZE]; static char file_out[STRINGSIZE]; static unsigned char png_sig[8] = /* 137 'P' 'N' 'G' 13 10 26 10 */ { 0x89, 0x50 , 0x4e , 0x47 , 0x0d , 0x0a , 0x1a , 0x0a }; /* Like strncpy(3), but always puts a null byte at the end */ char * safe_strncpy(char * s1, const char * s2, size_t n) { if (n <= 0) { return 0; } if (s1 == 0) { return 0; } if (s2 == 0) { return 0; } if (n == 1) { s1[0] = 0; return s1; } strncpy(s1, s2, n); s1[n-1] = 0; return s1; } /* Table of chunk-ordering rules ("precedence") Each entry is a pair of chunk types. Within each pair, all chunks of the first type must appear before any chunks of the second type. For example, one of the pairs is ("PLTE", "bKGD"). This means that all PLTE chunks must appear before any bKGD chunks, and all bKGD chunks must appear after any PLTE chunks. Equivalently, it is an error if any PLTE chunk appears after any bKGD chunk, or if any bKGD chunk appears before any PLTE chunk. */ static char * prec[] = { /* First block of 4 is only constrained by the IHDR/IEND requirement */ /* Next block of 2 */ "pHYs", "IDAT", "sPLT", "IDAT", /* Upper-right block of 4, for files with a PLTE */ "iCCP", "PLTE", "sRGB", "PLTE", "sBIT", "PLTE", "gAMA", "PLTE", "cHRM", "PLTE", /* Middle-right block of 3, for files with a PLTE */ "PLTE", "tRNS", "PLTE", "hIST", "PLTE", "bKGD", "PLTE", "IDAT", "tRNS", "IDAT", "hIST", "IDAT", "bKGD", "IDAT", /* Rightmost 6, for files without a PLTE */ "iCCP", "IDAT", "sRGB", "IDAT", "sBIT", "IDAT", "gAMA", "IDAT", "cHRM", "IDAT", "tRNS", "IDAT", "bKGD", "IDAT", 0, 0 }; /* Table-of-contents management */ typedef struct tocitem { u32 pos; /* Position in the file of this chunk */ u32 len; /* Length in bytes of this chunk */ char type[5]; /* Chunk type, a string of at most 4 characters */ /* We don't even bother storing the checksum because we recompute all checksums anyway */ } tocitem; tocitem * toc; u32 toc_alloc; u32 ntoc; void toc_init(void) { toc_alloc = 16; toc = (tocitem *) malloc((size_t) (toc_alloc * sizeof(tocitem))); if (toc == 0) { fflush(stdout); fprintf (stderr, "%s: toc_init failed to allocate %d items\n", prog, ((int) toc_alloc)); exit (1); } ntoc = 0; } void toc_add(u32 p, u32 l, char * t) { if (ntoc >= toc_alloc) { toc_alloc *= 2; toc = (tocitem *) realloc(((void *) toc), (size_t) (toc_alloc * sizeof(tocitem))); if (toc == 0) { fflush(stdout); fprintf (stderr, "%s: toc_add failed to reallocate %d items\n", prog, ((int) toc_alloc)); exit (1); } } toc[ntoc].pos = p; toc[ntoc].len = l; safe_strncpy(toc[ntoc].type, t, 5); ntoc++; } long int cp_alloc = 0; long int g_cpnum; unsigned char * new_palette = 0; void cp_add(char * data) { int gg; char * d; unsigned char c; /* printf("cp_add('%s')\n", data); */ if (cp_alloc == 0) { /* First time */ cp_alloc = 48; /* Enough for a 16-entry, 8-bit-per-component RGB palette */ new_palette = (unsigned char *) malloc((size_t) (cp_alloc * sizeof(char))); if (new_palette == 0) { fflush(stdout); fprintf (stderr, "%s: cp_add failed to allocate %d chars\n", prog, ((int) cp_alloc)); exit (1); } g_cpnum = 0; } /* Parse the data we were given as hexadecimal-encoded bytes */ gg = 1; d = data; while(gg) { if (sscanf(d, "%2hhx", &c) == 1) { /* Got one */ if (g_cpnum >= cp_alloc) { cp_alloc *= 2; new_palette = (unsigned char *) realloc(((void *) new_palette), (size_t) (cp_alloc * sizeof(char))); if (new_palette == 0) { fflush(stdout); fprintf (stderr, "%s: cp_add failed to reallocate %d chars\n", prog, ((int) cp_alloc)); exit (1); } } /* printf("Got new palette byte '%02x'\n", c); */ new_palette[g_cpnum++] = c; d++; if (*d) { d++; } } else { gg = 0; } } } /* End of cp.add */ int verify_precedence(char * cannot_follow, char * cannot_precede) { int i, j; char * itype; char * jtype; for(i=0; i []\n", prog); exit (1); } if ((file_out == 0) || (*file_out == 0)) { o_writing = 0; } else { o_writing = 1; } if (o_writing && (strcmp(file_in, file_out) == 0)) { fflush(stdout); fprintf (stderr, "%s: input and output may not be the same\n", prog); exit (1); } if ((fp_in = fopen(file_in, "rb")) == NULL) { fflush(stdout); fprintf (stderr, "%s: %s not found\n", prog, file_in); exit (1); } if (o_writing) { if ((fp_out = fopen(file_out, "wb")) == NULL) { fflush(stdout); fprintf (stderr, "%s: %s can't be created\n", prog, file_out); exit (1); } } toc_init(); g_palette_mode = 0; g_text_mode = 0; g_cp_byte = 0; // copy PNG header for (i = 0; i < 8; i++) { if ((val = fgetc(fp_in)) == EOF) { fflush(stdout); fprintf (stderr, "%s: %s header too short\n", prog, file_in); exit (1); } else { if (o_writing) { fputc(val, fp_out); } if (val != (int) png_sig[i]) { fflush(stdout); fprintf(stderr, "%s: PNG signature error in %s (byte %d)\n", prog, file_in, i); exit (1); } } } // process chunks strcpy (chnk, ""); do { /* Note current file position */ pos = ftell(fp_in); // get chunk size l = 0; for (i = 0; i < 4; i++) { c = inonly(fp_in); csz[i] = c; l = (l << 8) + c; } lm = l; // modified length (not used for anything, yet) // get chunk name crc = 0; strcpy (chnk, ""); for (i = 0; i < 4; i++) { c = inonly(fp_in); cnam[i] = c; crc = crc32 (crc, &c, 1); chnk[i] = (char) c; } chnk[4] = '\0'; printf ("%s (%4d bytes)", chnk, l); g_palette_mode = 0; if (0) { } else if (o_show_header && (strcmp(chnk, "IHDR") == 0)) { /* We'll show the header */ g_palette_mode = 1; rgb_ix = 0; printf("\n "); } else if ( ( (g_cpnum > 0) // We want to change palette || o_show_palette) // or we want to display palette && (strcmp(chnk, "PLTE") == 0) // and this is a PLTE chunk ) { /* We'll show the palette */ g_palette_mode = 2; rgb_ix = 0; if (o_show_palette) { printf("\n "); } } if (strcmp(chnk, "tEXt") == 0) { /* Text in ISO/IEC 8859-1 (Latin-1) encoding */ g_text_mode = 1; } else if (strcmp(chnk, "zTXt") == 0) { /* Text in ISO/IEC 8859-1 (Latin-1) encoding, and compressed in zlib format ("deflate/inflate") See ftp://ftp.info-zip.org/pub/infozip/ and www.libpng.org/pub/png/spec/1.2/PNG-Compression.html */ g_text_mode = 0; /* %%% try to find a zlib library */ } else if (strcmp(chnk, "iTXt") == 0) { /* Text with UTF-8 encoding, possible with compression. Seen in e.g. Adobe Photoshop */ g_text_mode = 1; } else { g_text_mode = 0; } if ((l < g_cpnum) && (g_cpnum < 256) && (g_palette_mode == 2)) { lm = g_cpnum; csz[3] = g_cpnum; } toc_add(pos, l, chnk); // write out the length for (i = 0; i < 4; i++) { outonly(csz[i], fp_out); } // write out the name for (i = 0; i < 4; i++) { outonly(cnam[i], fp_out); } // copy chunk data for (i = 0; i < l; i++) { if (g_palette_mode == 1) { /* Show a byte of the header */ c = inonly (fp_in); printf("%02x", c); rgb_ix++; if ((rgb_ix == 4) || (rgb_ix >= 8)) { printf(" "); } outonly(c, fp_out); } else if (g_palette_mode == 2) { /* Show a byte of the palette */ c = inonly (fp_in); if (o_show_palette) { printf("%02x", c); } if (g_cp_byte < g_cpnum) { c = new_palette[g_cp_byte++]; if (o_show_palette) { printf(">%02x.", c); } } rgb_ix = (rgb_ix + 1) % 3; if (rgb_ix == 0) { if (o_show_palette) { printf(" "); } } outonly(c, fp_out); } else if (g_text_mode && o_show_text) { /* Show a character of a tEXt chunk */ c = inonly (fp_in); if (c == 0) { printf(": "); } else if (c >= 32 || c < 127) { printf("%c", c); } else { printf(" "); } outonly(c, fp_out); } else { c = inout(fp_in, fp_out); } crc = crc32 (crc, &c, 1); } // Add additional palette bytes, if any if ((g_cp_byte < g_cpnum) && (g_cpnum < 256) && (g_palette_mode == 2)) { for (i=g_cp_byte; i < g_cpnum; i++) { c = new_palette[g_cp_byte++]; if (o_show_palette) { printf(">%02x.", c); } rgb_ix = (rgb_ix + 1) % 3; if (rgb_ix == 0) { if (o_show_palette) { printf(" "); } } outonly(c, fp_out); crc = crc32 (crc, &c, 1); } } // copy checksum csum = 0; for (i = 0; i < 4; i++) { c = inonly (fp_in); csum = (csum << 8) + (int) c; b = (unsigned char) ((crc >> 8 * (3 - i)) & 0xFF); // printf ("b = %02x\n", b); outonly(b, fp_out); } if (g_palette_mode) { if (o_show_palette) { /* Send a newline to prepare for the csum output */ printf("\n "); } } if (crc == csum) { printf (" - csum = %08x\n", crc); } else { printf (" - csum = %08x -> %08x\n", csum, crc); } } while (strcmp (chnk, "IEND") != 0); fclose(fp_in); if (o_writing) { fclose(fp_out); } if (o_show_contents) { printf("PNG file has %d chunks:\n", ntoc); printf(" i pos len type\n"); for(i=0; i