//////////////////////////////////////////////////////////////////////////////// // #define TITLE "edccchk - CD image EDC/ECC Checker" #define COPYR "Copyright (C) 2013 Natalia Portillo" // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // //////////////////////////////////////////////////////////////////////////////// #include "banner.h" #include "common.h" //////////////////////////////////////////////////////////////////////////////// // // Sector types // // Mode 1 // ----------------------------------------------------- // 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 01 // 0010h [---DATA... // ... // 0800h ...DATA---] // 0810h [---EDC---] 00 00 00 00 00 00 00 00 [---ECC... // ... // 0920h ...ECC---] // ----------------------------------------------------- // // Mode 2 (XA), form 1 // ----------------------------------------------------- // 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 02 // 0010h [--FLAGS--] [--FLAGS--] [---DATA... // ... // 0810h ...DATA---] [---EDC---] [---ECC... // ... // 0920h ...ECC---] // ----------------------------------------------------- // // Mode 2 (XA), form 2 // ----------------------------------------------------- // 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 02 // 0010h [--FLAGS--] [--FLAGS--] [---DATA... // ... // 0920h ...DATA---] [---EDC---] // ----------------------------------------------------- // // ADDR: Sector address, encoded as minutes:seconds:frames in BCD // FLAGS: Used in Mode 2 (XA) sectors describing the type of sector; repeated // twice for redundancy // DATA: Area of the sector which contains the actual data itself // EDC: Error Detection Code // ECC: Error Correction Code // //////////////////////////////////////////////////////////////////////////////// #ifdef DEBUG #define DPRINTF(fmt, ...) \ do \ { \ printf("edccchk-debug: " fmt, ##__VA_ARGS__); \ } while(0) #else #define DPRINTF(fmt, ...) \ do \ { \ } while(0) #endif static uint32_t get32lsb(const uint8_t *src) { return (((uint32_t)(src[0])) << 0) | (((uint32_t)(src[1])) << 8) | (((uint32_t)(src[2])) << 16) | (((uint32_t)(src[3])) << 24); } static uint32_t nondatasectors; static uint32_t mode0sectors; static uint32_t mode0errors; static uint32_t mode1sectors; static uint32_t mode1errors; static uint32_t mode2f1sectors; static uint32_t mode2f1errors; static uint32_t mode2f1warnings; static uint32_t mode2f2sectors; static uint32_t mode2f2errors; static uint32_t mode2f2warnings; static uint32_t totalsectors; static uint32_t totalerrors; static uint32_t totalwarnings; static uint32_t filledsectors; //////////////////////////////////////////////////////////////////////////////// // // LUTs used for computing ECC/EDC // static uint8_t ecc_f_lut[256]; static uint8_t ecc_b_lut[256]; static uint32_t edc_lut[256]; static void eccedc_init(void) { DPRINTF("Entering eccedc_init().\n"); size_t i; for(i = 0; i < 256; i++) { uint32_t edc = i; size_t j = (i << 1) ^ (i & 0x80 ? 0x11D : 0); ecc_f_lut[i] = j; ecc_b_lut[i ^ j] = i; for(j = 0; j < 8; j++) { edc = (edc >> 1) ^ (edc & 1 ? 0xD8018001 : 0); } edc_lut[i] = edc; } } //////////////////////////////////////////////////////////////////////////////// // // Compute EDC for a block // static uint32_t edc_compute(uint32_t edc, const uint8_t *src, size_t size) { DPRINTF("Entering edc_compute(%d, *%d, %d).\n"); for(; size; size--) { edc = (edc >> 8) ^ edc_lut[(edc ^ (*src++)) & 0xFF]; } return edc; } //////////////////////////////////////////////////////////////////////////////// // // Check ECC block (either P or Q) // Returns true if the ECC data is an exact match // static int8_t ecc_checkpq(const uint8_t *address, const uint8_t *data, size_t major_count, size_t minor_count, size_t major_mult, size_t minor_inc, const uint8_t *ecc) { DPRINTF("Entering ecc_checkpq(*%d, *%d, %d, %d, %d, %d, *%d).\n"); size_t size = major_count * minor_count; size_t major; for(major = 0; major < major_count; major++) { size_t index = (major >> 1) * major_mult + (major & 1); uint8_t ecc_a = 0; uint8_t ecc_b = 0; size_t minor; for(minor = 0; minor < minor_count; minor++) { uint8_t temp; if(index < 4) { temp = address[index]; } else { temp = data[index - 4]; } index += minor_inc; if(index >= size) { index -= size; } ecc_a ^= temp; ecc_b ^= temp; ecc_a = ecc_f_lut[ecc_a]; } ecc_a = ecc_b_lut[ecc_f_lut[ecc_a] ^ ecc_b]; if(ecc[major] != (ecc_a) || ecc[major + major_count] != (ecc_a ^ ecc_b)) { return 0; } } return 1; } // // Check ECC P and Q codes for a sector // Returns true if the ECC data is an exact match // static int8_t ecc_checksector(const uint8_t *address, const uint8_t *data, const uint8_t *ecc) { DPRINTF("Entering ecc_checksector(*%d, *%d, *%d).\n"); return ecc_checkpq(address, data, 86, 24, 2, 86, ecc) && // P ecc_checkpq(address, data, 52, 43, 86, 88, ecc + 0xAC); // Q } //////////////////////////////////////////////////////////////////////////////// static const uint8_t zeroaddress[4] = {0, 0, 0, 0}; //////////////////////////////////////////////////////////////////////////////// static off_t mycounter_analyze = (off_t)-1; static off_t mycounter_encode = (off_t)-1; static off_t mycounter_decode = (off_t)-1; static off_t mycounter_total = 0; static void resetcounter(off_t total) { mycounter_analyze = (off_t)-1; mycounter_encode = (off_t)-1; mycounter_decode = (off_t)-1; mycounter_total = total; } static void encode_progress(void) { off_t a = (mycounter_analyze + 64) / 128; off_t t = (mycounter_total + 64) / 128; if(!t) { t = 1; } fprintf(stderr, "Analyze(%02u%%)\r", (unsigned)((((off_t)100) * a) / t)); } static void setcounter_analyze(off_t n) { int8_t p = ((n >> 20) != (mycounter_analyze >> 20)); mycounter_analyze = n; if(p) { encode_progress(); } } //////////////////////////////////////////////////////////////////////////////// // // Returns nonzero on error // static int8_t ecmify(const char *infilename) { DPRINTF("Entering ecmify(\"%s\").\n", infilename); int8_t returncode = 0; int8_t filled = 0; FILE *in = NULL; uint8_t *queue = NULL; size_t queue_start_ofs = 0; size_t queue_bytes_available = 0; uint32_t input_edc = 0; off_t input_file_length; off_t input_bytes_checked = 0; off_t input_bytes_queued = 0; size_t queue_size = ((size_t)(-1)) - 4095; if((unsigned long)queue_size > 0x40000lu) { queue_size = (size_t)0x40000lu; } // // Allocate space for queue // DPRINTF("ecmify(): Allocation memory for queue.\n"); queue = malloc(queue_size); if(!queue) { printf("Out of memory\n"); goto error; } // // Open both files // DPRINTF("ecmify(): Opening file \"%s\".\n", infilename); in = fopen(infilename, "rb"); if(!in) { goto error_in; } printf("Checking %s...\n", infilename); // // Get the length of the input file // DPRINTF("ecmify(): Seeking to end of file.\n"); if(fseeko(in, 0, SEEK_END) != 0) { goto error_in; } input_file_length = ftello(in); DPRINTF("ecmify(): Got file length %d.\n", input_file_length); if(input_file_length < 0) { goto error_in; } resetcounter(input_file_length); nondatasectors = 0; mode0sectors = 0; mode0errors = 0; mode1sectors = 0; mode1errors = 0; mode2f1sectors = 0; mode2f1errors = 0; mode2f1warnings = 0; mode2f2sectors = 0; mode2f2errors = 0; mode2f2warnings = 0; totalsectors = 0; totalerrors = 0; filledsectors = 0; DPRINTF("ecmify(): Entering main loop.\n"); for(;;) { // // Refill queue if necessary // if((queue_bytes_available < 2352) && (((off_t)queue_bytes_available) < (input_file_length - input_bytes_queued))) { DPRINTF("ecmify(): Refilling queue.\n"); // // We need to read more data // off_t willread = input_file_length - input_bytes_queued; off_t maxread = queue_size - queue_bytes_available; if(willread > maxread) { DPRINTF("Will read maximum.\n"); willread = maxread; } if(queue_start_ofs > 0) { memmove(queue, queue + queue_start_ofs, queue_bytes_available); queue_start_ofs = 0; } if(willread) { setcounter_analyze(input_bytes_queued); if(fseeko(in, input_bytes_queued, SEEK_SET) != 0) { goto error_in; } if(fread(queue + queue_bytes_available, 1, willread, in) != (size_t)willread) { goto error_in; } input_edc = edc_compute(input_edc, queue + queue_bytes_available, willread); input_bytes_queued += willread; queue_bytes_available += willread; } } if(queue_bytes_available == 0) { DPRINTF("ecmify(): No data left in queue.\n"); // // No data left to read -> quit // break; } uint8_t *sector = queue + queue_start_ofs; // Data sector if(sector[0x000] == 0x00 && // sync (12 bytes) sector[0x001] == 0xFF && sector[0x002] == 0xFF && sector[0x003] == 0xFF && sector[0x004] == 0xFF && sector[0x005] == 0xFF && sector[0x006] == 0xFF && sector[0x007] == 0xFF && sector[0x008] == 0xFF && sector[0x009] == 0xFF && sector[0x00A] == 0xFF && sector[0x00B] == 0x00) { DPRINTF("ecmify(): Data sector, address %02X:%02X:%02X.\n", sector[0x00C], sector[0x00D], sector[0x00E]); // Just for debug // fprintf(stderr, "Address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(sector[0x00F] == 0x00) // mode (1 byte) { DPRINTF("ecmify(): Mode 0 sector at address %02X:%02X:%02X.\n", sector[0x00C], sector[0x00D], sector[0x00E]); mode0sectors++; for(int i = 0x010; i < 0x930; i++) { if(sector[i] != 0x00) { mode0errors++; totalerrors++; fprintf(stderr, "Mode 0 sector with error at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); break; } } } else if(sector[0x00F] == 0x01) // mode (1 byte) { DPRINTF("ecmify(): Mode 1 sector at address %02X:%02X:%02X.\n", sector[0x00C], sector[0x00D], sector[0x00E]); mode1sectors++; if(!ecc_checksector(sector + 0xC, sector + 0x10, sector + 0x81C) || edc_compute(0, sector, 0x810) != get32lsb(sector + 0x810) || sector[0x814] != 0x00 || // reserved (8 bytes) sector[0x815] != 0x00 || sector[0x816] != 0x00 || sector[0x817] != 0x00 || sector[0x818] != 0x00 || sector[0x819] != 0x00 || sector[0x81A] != 0x00 || sector[0x81B] != 0x00) { mode1errors++; totalerrors++; fprintf(stderr, "Mode 1 sector with error at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(edc_compute(0, sector, 0x810) != get32lsb(sector + 0x810)) fprintf(stderr, "%02X:%02X:%02X: Failed EDC\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(!ecc_checkpq(sector + 0xC, sector + 0x10, 86, 24, 2, 86, sector + 0x81C)) fprintf(stderr, "%02X:%02X:%02X: Failed ECC P\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(!ecc_checkpq(sector + 0xC, sector + 0x10, 52, 43, 86, 88, sector + 0x81C + 0xAC)) fprintf(stderr, "%02X:%02X:%02X: Failed ECC Q\n", sector[0x00C], sector[0x00D], sector[0x00E]); } filled = 1; for(int i = 0x010; i < 0x810; i++) { if(sector[i] != 0x55) { filled = 0; break; } } if(filled) { filledsectors++; fprintf(stderr, "Mode 1 sector at address: %02X:%02X:%02X is filled with 55h\n", sector[0x00C], sector[0x00D], sector[0x00E]); } } else if(sector[0x00F] == 0x02) // mode (1 byte) { DPRINTF("ecmify(): Mode 2 sector at address %02X:%02X:%02X.\n", sector[0x00C], sector[0x00D], sector[0x00E]); uint8_t *m2sec = sector + 0x10; if((sector[0x012] & 0x20) == 0x20) // mode 2 form 2 { mode2f2sectors++; if(edc_compute(0, m2sec, 0x91C) != get32lsb(m2sec + 0x91C) && get32lsb(m2sec + 0x91C) != 0) { fprintf(stderr, "Mode 2 form 2 sector with error at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(edc_compute(0, m2sec, 0x91C) != get32lsb(m2sec + 0x91C)) fprintf( stderr, "%02X:%02X:%02X: Failed EDC\n", sector[0x00C], sector[0x00D], sector[0x00E]); mode2f2errors++; totalerrors++; } if(sector[0x010] != sector[0x014] || sector[0x011] != sector[0x015] || sector[0x012] != sector[0x016] || sector[0x013] != sector[0x017]) { mode2f2warnings++; totalwarnings++; fprintf(stderr, "Subheader copies differ in mode 2 form 2 sector at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); } filled = 1; for(int i = 0x018; i < 0x91C; i++) { if(sector[i] != 0x55) { filled = 0; break; } } if(filled) { filledsectors++; fprintf(stderr, "Mode 2 form 2 sector at address: %02X:%02X:%02X is filled with 55h\n", sector[0x00C], sector[0x00D], sector[0x00E]); } } else { mode2f1sectors++; if(!ecc_checksector(zeroaddress, m2sec, m2sec + 0x80C) || edc_compute(0, m2sec, 0x808) != get32lsb(m2sec + 0x808)) { fprintf(stderr, "Mode 2 form 1 sector with error at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(edc_compute(0, m2sec, 0x808) != get32lsb(m2sec + 0x808)) fprintf( stderr, "%02X:%02X:%02X: Failed EDC\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(!ecc_checkpq(zeroaddress, m2sec, 86, 24, 2, 86, m2sec + 0x80C)) fprintf( stderr, "%02X:%02X:%02X: Failed ECC P\n", sector[0x00C], sector[0x00D], sector[0x00E]); if(!ecc_checkpq(zeroaddress, m2sec, 52, 43, 86, 88, m2sec + 0x80C)) fprintf( stderr, "%02X:%02X:%02X: Failed ECC Q\n", sector[0x00C], sector[0x00D], sector[0x00E]); mode2f1errors++; totalerrors++; } if(sector[0x010] != sector[0x014] || sector[0x011] != sector[0x015] || sector[0x012] != sector[0x016] || sector[0x013] != sector[0x017]) { mode2f1warnings++; totalwarnings++; fprintf(stderr, "Subheader copies differ in mode 2 form 1 sector at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); } filled = 1; for(int i = 0x018; i < 0x818; i++) { if(sector[i] != 0x55) { filled = 0; break; } } if(filled) { filledsectors++; fprintf(stderr, "Mode 2 form 1 sector at address: %02X:%02X:%02X is filled with 55h\n", sector[0x00C], sector[0x00D], sector[0x00E]); } } } else // Unknown sector mode!!! { DPRINTF("ecmify(): Unknown data sector with mode %d at address %02X:%02X:%02X.\n", sector[0x00F], sector[0x00C], sector[0x00D], sector[0x00E]); nondatasectors++; } } else // Non data sector { DPRINTF("ecmify(): Non-data sector.\n"); nondatasectors++; } // // Advance to the next sector // totalsectors++; input_bytes_checked += 2352; queue_start_ofs += 2352; queue_bytes_available -= 2352; DPRINTF("ecmify.totalsectors = %d\n", totalsectors); DPRINTF("ecmify.input_bytes_checked = %d\n", input_bytes_checked); DPRINTF("ecmify.queue_start_ofs = %d\n", queue_start_ofs); DPRINTF("ecmify.queue_bytes_available = %d\n", queue_bytes_available); } // // Show report // printf("Non-data sectors........ %d\n", nondatasectors); printf("Mode 0 sectors.......... %d\n", mode0sectors); printf("\twith errors..... %d\n", mode0errors); printf("Mode 1 sectors.......... %d\n", mode1sectors); printf("\twith errors..... %d\n", mode1errors); printf("Mode 2 form 1 sectors... %d\n", mode2f1sectors); printf("\twith errors..... %d\n", mode2f1errors); printf("\twith warnings... %d\n", mode2f1warnings); printf("Mode 2 form 2 sectors... %d\n", mode2f2sectors); printf("\twith errors..... %d\n", mode2f2errors); printf("\twith warnings... %d\n", mode2f2warnings); printf("Filled sectors.......... %d\n", filledsectors); printf("Total sectors........... %d\n", totalsectors); printf("Total errors............ %d\n", totalerrors); printf("Total warnings.......... %d\n", totalwarnings); printf("Total errors+warnings... %d\n", totalerrors + totalwarnings); // // Success // printf("Done\n"); returncode = 0; goto done; error_in: printfileerror(in, infilename); goto error; error: returncode = 1; goto done; done: if(queue != NULL) { free(queue); } if(in != NULL) { fclose(in); } return returncode; } int main(int argc, char **argv) { DPRINTF("Entering main().\n"); int returncode = 0; char *infilename = NULL; DPRINTF("Normalizing argv[0].\n"); normalize_argv0(argv[0]); DPRINTF("Showing banner.\n"); banner(); // // Check command line // switch(argc) { case 2: infilename = argv[1]; // // Initialize the ECC/EDC tables // eccedc_init(); if(ecmify(infilename)) { goto error; } break; default: goto usage; } // // Success // returncode = 0; goto done; usage: printf("Usage:\n" "\n" " edccchk cdimagefile\n"); error: returncode = 1; goto done; done: return returncode; } ////////////////////////////////////////////////////////////////////////////////