diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e741af8 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +OBJS = edccchk.o +CC = gcc +DEBUG = -g +CFLAGS = -Wall -O0 -W -D_FILE_OFFSET_BITS=64 -std=c99 -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +LIBS = + +edccchk : $(OBJS) + $(CC) $(LFLAGS) $(LIBS) $(OBJS) -o edccchk + +edccchk.o : edccchk.c common.h banner.h version.h + $(CC) $(CFLAGS) edccchk.c + +clean: + \rm *.o edccchk + +tar: + tar Jcfv edccchk-src.txz banner.h common.h edccchk.c LICENSE Makefile README.md version.h + +bindist : edccchk + tar Jcvf edccchk-bin.txz edccchk LICENSE README.md diff --git a/Makefile.mingw32 b/Makefile.mingw32 new file mode 100644 index 0000000..318b54d --- /dev/null +++ b/Makefile.mingw32 @@ -0,0 +1,21 @@ +OBJS = edccchk.o +CC = i686-pc-mingw32-gcc +DEBUG = -g +CFLAGS = -Wall -O0 -W -D_FILE_OFFSET_BITS=64 -std=c99 -static -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +LIBS = + +edccchk : $(OBJS) + $(CC) $(LFLAGS) $(LIBS) $(OBJS) -o edccchk.exe + +edccchk.o : edccchk.c common.h banner.h version.h + $(CC) $(CFLAGS) edccchk.c + +clean: + \rm *.o edccchk.exe + +tar: + zip -9 edccchk-src.zip banner.h common.h edccchk.c LICENSE Makefile README.md version.h + +bindist : edccchk + zip -9 edccchk-bin-win32.zip edccchk.exe LICENSE README.md diff --git a/Makefile.mingw64 b/Makefile.mingw64 new file mode 100644 index 0000000..e6ff14f --- /dev/null +++ b/Makefile.mingw64 @@ -0,0 +1,21 @@ +OBJS = edccchk.o +CC = x86_64-pc-mingw64-gcc +DEBUG = -g +CFLAGS = -Wall -O0 -W -D_FILE_OFFSET_BITS=64 -std=c99 -static -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +LIBS = + +edccchk : $(OBJS) + $(CC) $(LFLAGS) $(LIBS) $(OBJS) -o edccchk.exe + +edccchk.o : edccchk.c common.h banner.h version.h + $(CC) $(CFLAGS) edccchk.c + +clean: + \rm *.o edccchk.exe + +tar: + zip -9 edccchk-src.zip banner.h common.h edccchk.c LICENSE Makefile README.md version.h + +bindist : edccchk + zip -9 edccchk-bin-win64.zip edccchk.exe LICENSE README.md diff --git a/README.md b/README.md index df5e780..c99ec67 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,41 @@ -edccchk -======= +edccchk v1.0 +============ EDC/ECC checker for RAW (2352 bytes/sector) CD images + +Copyright © 2013 Natalia Portillo +Based on ECM v1.03 Copyright © 2002-2011 Neill Corlett + +Usage +===== + +edccchk + + RAW 2352 bytes/sector image of a CD. + +Features +======== + +Checks EDC and ECC fields consistency of CD sectors. +Supports Mode 0, Mode 1 and Mode 2 data sectors, ignores Audio sectors. +Shows failing sectors as MSF. + +Known bugs +========== + +Mode 2 form-less sectors all appear as errors. Mode 2 form 1 and form 2 sectors are processed correctly. + +Changelog +========= + +2013/12/08 v1.00 Converted ECM code to only check sectors. + Added support for mode 0 sectors. + +To-Do +===== + +* Support Mode 2 form-less sectors +* Support RAW+SUB images (2448 bytes/sector) +* Check Q-subchannel CRCs +* Check CD+G CRCs +* Check consistency of P and Q subchannels with sector headers diff --git a/banner.h b/banner.h new file mode 100644 index 0000000..c7f671f --- /dev/null +++ b/banner.h @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////////// + +void banner_ok(void) { + printf(TITLE "\n" +#include "version.h" + " (%d-bit " + +#if defined(__CYGWIN__) + "Windows, Cygwin" +#elif defined(__MINGW32__) + "Windows, MinGW" +#elif defined(_WIN32) && defined(_MSC_VER) && (defined(__alpha) || defined(__ALPHA) || defined(__Alpha_AXP)) + "Windows, Digital AXP C" +#elif defined(_WIN32) && defined(_MSC_VER) && defined(_M_ALPHA) + "Windows, Microsoft C, Alpha" +#elif defined(_WIN32) && defined(_MSC_VER) && defined(_M_MRX000) + "Windows, Microsoft C, MIPS" +#elif defined(_WIN32) && defined(_MSC_VER) + "Windows, Microsoft C" +#elif defined(__WIN32__) || defined(_WIN32) + "Windows" +#elif defined(__DJGPP__) + "DOS, DJGPP" +#elif defined(__MSDOS__) && defined(__TURBOC__) + "DOS, Turbo C" +#elif defined(_DOS) && defined(__WATCOMC__) + "DOS, Watcom" +#elif defined(__MSDOS__) || defined(MSDOS) || defined(_DOS) + "DOS" +#elif defined(__APPLE__) + "Mac OS" +#elif defined(__linux) || defined(__linux__) || defined(__gnu_linux__) || defined(linux) + "Linux" +#elif defined(__OpenBSD__) + "OpenBSD" +#elif defined(BSD) + "BSD" +#elif defined(human68k) || defined(HUMAN68K) || defined(__human68k) || defined(__HUMAN68K) || defined(__human68k__) || defined(__HUMAN68K__) + "Human68k" +#elif defined(__unix__) || defined(__unix) || defined(unix) + "unknown Unix" +#else + "unknown platform" +#endif + + "%s)\n" + " " COPYR "\n" + " http://www.claunia.com/\n" + " based on ecm v1.03\n" + " Copyright (C) 2002-2011 Neill Corlett" + "\n", + (int)(sizeof(size_t) * 8), + (sizeof(off_t) > 4 && sizeof(off_t) > sizeof(size_t)) ? ", large file support" : "" + ); +} + +void banner_error(void) { + printf("Configuration error\n"); + exit(1); +} + +static void banner(void) { + ((sizeof(off_t) >= sizeof(size_t)) ? banner_ok : banner_error)(); + // + // If we've displayed the banner, we'll also want to warn that this is a + // command-line app when we exit + // + atexit(commandlinewarning); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/common.h b/common.h new file mode 100644 index 0000000..65b34f6 --- /dev/null +++ b/common.h @@ -0,0 +1,641 @@ +#ifndef __CMDPACK_COMMON_H__ +#define __CMDPACK_COMMON_H__ + +//////////////////////////////////////////////////////////////////////////////// +// +// Common headers for Command-Line Pack programs +// +// 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 . +// +//////////////////////////////////////////////////////////////////////////////// + +// Disable fopen() warnings on VC++. It means well... +#define _CRT_SECURE_NO_WARNINGS + +// Try to enable 64-bit file offsets on platforms where it's optional +#define _LARGEFILE64_SOURCE 1 +#define __USE_FILE_OFFSET64 1 +#define __USE_LARGEFILE64 1 +#define _FILE_OFFSET_BITS 64 + +// Try to enable long filename support on Watcom +#define __WATCOM_LFN__ 1 + +// Convince MinGW that we want to glob arguments +#ifdef __MINGW32__ +int _dowildcard = -1; +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// MSC toolchains use sys/utime.h; everything else uses utime.h +#if defined(_MSC_VER) +#include +#else +#include +#endif + +// Try to bring in unistd.h if possible +#if !defined(__TURBOC__) && !defined(_MSC_VER) +#include +#endif + +// Bring in direct.h if we need to; sometimes mkdir/rmdir is defined here +#if defined(__WATCOMC__) || defined(_MSC_VER) +#include +#endif + +// Fill in S_ISDIR +#if !defined(_POSIX_VERSION) && !defined(S_ISDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + +#if defined(__TURBOC__) || defined(__WATCOMC__) || defined(__MINGW32__) || defined(_MSC_VER) +// +// Already have a single-argument mkdir() +// +#else +// +// Provide a single-argument mkdir() +// +#define mkdir(a) mkdir(a, S_IRWXU | S_IRWXG | S_IRWXO) +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// Enforce large memory model for 16-bit DOS targets +// +#if defined(__MSDOS__) || defined(MSDOS) +#if defined(__TURBOC__) || defined(__WATCOMC__) +#if !defined(__LARGE__) +#error This is not the memory model we should be using! +#endif +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// Try to figure out integer types +// +#if defined(_STDINT_H) || defined(_EXACT_WIDTH_INTS) + +// _STDINT_H_ - presume stdint.h has already been included +// _EXACT_WIDTH_INTS - OpenWatcom already provides int*_t in sys/types.h + +#elif defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L + +// Assume C99 compliance when the compiler specifically tells us it is +#include + +#elif defined(_MSC_VER) + +// On Visual Studio, use its integral types +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; + +#else + +// Guess integer sizes from limits.h + +// +// int8_t +// +#ifndef __int8_t_defined +#if SCHAR_MIN == -128 && SCHAR_MAX == 127 && UCHAR_MAX == 255 +typedef signed char int8_t; +#else +#error Unknown how to define int8_t! +#endif +#endif + +// +// uint8_t +// +#ifndef __uint8_t_defined +#if SCHAR_MIN == -128 && SCHAR_MAX == 127 && UCHAR_MAX == 255 +typedef unsigned char uint8_t; +#else +#error Unknown how to define uint8_t! +#endif +#endif + +// +// int16_t +// +#ifndef __int16_t_defined +#if SHRT_MIN == -32768 && SHRT_MAX == 32767 && USHRT_MAX == 65535 +typedef signed short int16_t; +#else +#error Unknown how to define int16_t! +#endif +#endif + +// +// uint16_t +// +#ifndef __uint16_t_defined +#if SHRT_MIN == -32768 && SHRT_MAX == 32767 && USHRT_MAX == 65535 +typedef unsigned short uint16_t; +#else +#error Unknown how to define uint16_t! +#endif +#endif + +// +// int32_t +// +#ifndef __int32_t_defined +#if INT_MIN == -2147483648 && INT_MAX == 2147483647 && UINT_MAX == 4294967295 +typedef signed int int32_t; +#elif LONG_MIN == -2147483648 && LONG_MAX == 2147483647 && ULONG_MAX == 4294967295 +typedef signed long int32_t; +#else +#error Unknown how to define int32_t! +#endif +#endif + +// +// uint32_t +// +#ifndef __uint32_t_defined +#if INT_MIN == -2147483648 && INT_MAX == 2147483647 && UINT_MAX == 4294967295 +typedef unsigned int uint32_t; +#elif LONG_MIN == -2147483648 && LONG_MAX == 2147483647 && ULONG_MAX == 4294967295 +typedef unsigned long uint32_t; +#else +#error Unknown how to define uint32_t! +#endif +#endif + +#endif + +// +// There are some places in the code where it's assumed 'long' can hold at least +// 32 bits. Verify that here: +// +#if LONG_MAX < 2147483647 || ULONG_MAX < 4294967295 +#error long type must be at least 32 bits! +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// Figure out how big file offsets should be +// +#if defined(_OFF64_T_) || defined(_OFF64_T_DEFINED) || defined(__off64_t_defined) +// +// We have off64_t +// Regular off_t may be smaller, so check this first +// + +#ifdef off_t +#undef off_t +#endif +#ifdef fseeko +#undef fseeko +#endif +#ifdef ftello +#undef ftello +#endif + +#define off_t off64_t +#define fseeko fseeko64 +#define ftello ftello64 + +#elif defined(_OFF_T) || defined(__OFF_T_TYPE) || defined(__off_t_defined) || defined(_OFF_T_DEFINED_) +// +// We have off_t +// + +#else +// +// Assume offsets are just 'long' +// +#ifdef off_t +#undef off_t +#endif +#ifdef fseeko +#undef fseeko +#endif +#ifdef ftello +#undef ftello +#endif + +#define off_t long +#define fseeko fseek +#define ftello ftell + +#endif + +// +// Add the ability to read off_t +// (assumes off_t is a signed type) +// +off_t strtoofft(const char* s_start, char** endptr, int base) { + off_t max = + ((((off_t)1) << ((sizeof(off_t)*8)-2)) - 1) + + ((((off_t)1) << ((sizeof(off_t)*8)-2)) ); + off_t min = ((-1) - max); + const char* s = s_start; + off_t accumulator; + off_t limit_tens; + off_t limit_ones; + int c; + int negative = 0; + int anyinput; + do { + c = *s++; + } while(isspace(c)); + if(c == '-') { + negative = 1; + c = *s++; + } else if (c == '+') { + c = *s++; + } + if( + (base == 0 || base == 16) && + c == '0' && (*s == 'x' || *s == 'X') + ) { + c = s[1]; + s += 2; + base = 16; + } + if(!base) { + base = (c == '0') ? 8 : 10; + } + limit_ones = max % ((off_t)base); + limit_tens = max / ((off_t)base); + if(negative) { + limit_ones++; + if(limit_ones >= base) { limit_ones = 0; limit_tens++; } + } + for(accumulator = 0, anyinput = 0;; c = *s++) { + if(isdigit(c)) { + c -= '0'; + } else if(isalpha(c)) { + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + } else { + break; + } + if(c >= base) { break; } + if( + (anyinput < 0) || + (accumulator < 0) || + (accumulator > limit_tens) || + (accumulator == limit_tens && c > limit_ones) + ) { + anyinput = -1; + } else { + anyinput = 1; + accumulator *= base; + accumulator += c; + } + } + if(anyinput < 0) { + accumulator = negative ? min : max; + errno = ERANGE; + } else if(negative) { + accumulator = -accumulator; + } + if(endptr) { + *endptr = (char*)(anyinput ? (char*)s - 1 : s_start); + } + return accumulator; +} + +// +// Add the ability to print off_t +// +void fprinthex(FILE* f, off_t off, int min_digits) { + unsigned anydigit = 0; + int place; + for(place = 2 * sizeof(off_t) - 1; place >= 0; place--) { + if(sizeof(off_t) > (((size_t)(place)) / 2)) { + unsigned digit = (off >> (4 * place)) & 0xF; + anydigit |= digit; + if(anydigit || place < min_digits) { + fputc("0123456789ABCDEF"[digit], f); + } + } + } +} + +static void fprintdec_digit(FILE* f, off_t off) { + if(off == 0) { return; } + if(off >= 10) { + fprintdec_digit(f, off / ((off_t)10)); + off %= ((off_t)10); + } + fputc('0' + off, f); +} + +void fprintdec(FILE* f, off_t off) { + if(off == 0) { + fputc('0', f); + return; + } + if(off < 0) { + fputc('-', f); + off = -off; + if(off < 0) { + off_t ones = off % ((off_t)10); + off /= ((off_t)10); + off = -off; + fprintdec_digit(f, off); + fputc('0' - ones, f); + return; + } + } + fprintdec_digit(f, off); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Define truncate() for systems that don't have it +// +#if !defined(_POSIX_VERSION) + +#if (defined(__MSDOS__) || defined(MSDOS)) && (defined(__TURBOC__) || defined(__WATCOMC__)) + +#include +#include +#include +int truncate(const char *filename, off_t size) { + if(size < 0) { + errno = EINVAL; + return -1; + } + // + // Extend (or do nothing) if necessary + // + { off_t end; + FILE* f = fopen(filename, "rb"); + if(!f) { + return -1; + } + if(fseeko(f, 0, SEEK_END) != 0) { + fclose(f); + return -1; + } + end = ftello(f); + if(end <= size) { + for(; end < size; end++) { + if(fputc(0, f) == EOF) { + fclose(f); + return -1; + } + } + fclose(f); + return 0; + } + fclose(f); + } + // + // Shrink if necessary (DOS-specific call) + // + { int doshandle = 0; + unsigned nwritten = 0; + if(_dos_open(filename, O_WRONLY, &doshandle)) { + return -1; + } + if(lseek(doshandle, size, SEEK_SET) == -1L) { + _dos_close(doshandle); + return -1; + } + if(_dos_write(doshandle, &doshandle, 0, &nwritten)) { + _dos_close(doshandle); + return -1; + } + _dos_close(doshandle); + } + // + // Success + // + return 0; +} + +#elif (defined(_WIN32) && defined(_MSC_VER)) + +#if defined(_MSC_VER) +// Disable extension warnings for and friends +#pragma warning (disable: 4226) +#endif + +#include + +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)(-1)) +#endif + +int truncate(const char *filename, off_t size) { + if(size < 0) { + errno = EINVAL; + return -1; + } + // + // Extend (or do nothing) if necessary + // + { off_t end; + FILE* f = fopen(filename, "rb"); + if(!f) { + return -1; + } + if(fseeko(f, 0, SEEK_END) != 0) { + fclose(f); + return -1; + } + end = ftello(f); + if(end <= size) { + for(; end < size; end++) { + if(fputc(0, f) == EOF) { + fclose(f); + return -1; + } + } + fclose(f); + return 0; + } + fclose(f); + } + // + // Shrink if necessary (Windows-specific call) + // + { HANDLE f = CreateFile( + filename, + GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if(f == INVALID_HANDLE_VALUE) { + return -1; + } + if(size > ((off_t)0x7FFFFFFFL)) { + // use fancy 64-bit SetFilePointer + LONG lo = size; + LONG hi = size >> 32; + if(SetFilePointer(f, lo, &hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + CloseHandle(f); + return -1; + } + } else { + // use plain 32-bit SetFilePointer + if(SetFilePointer(f, size, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + CloseHandle(f); + return -1; + } + } + if(!SetEndOfFile(f)) { + CloseHandle(f); + return -1; + } + } + // + // Success + // + return 0; +} + +#endif + +#endif // !defined(_POSIX_VERSION) + +//////////////////////////////////////////////////////////////////////////////// +// +// Normalize argv[0] +// +void normalize_argv0(char* argv0) { + size_t i; + size_t start = 0; + int c; + for(i = 0; argv0[i]; i++) { + if(argv0[i] == '/' || argv0[i] == '\\') { + start = i + 1; + } + } + i = 0; + do { + c = ((unsigned char)(argv0[start + i])); + if(c == '.') { c = 0; } + if(c != 0) { c = tolower(c); } + argv0[i++] = c; + } while(c != 0); +} + +//////////////////////////////////////////////////////////////////////////////// + +void printfileerror(FILE* f, const char* name) { + int e = errno; + printf("Error: "); + if(name) { printf("%s: ", name); } + printf("%s\n", f && feof(f) ? "Unexpected end-of-file" : strerror(e)); +} + +//////////////////////////////////////////////////////////////////////////////// + +#if defined(_WIN32) + +// +// Detect if the user double-clicked on the .exe rather than executing this from +// the command line, and if so, display a warning and wait for input before +// exiting +// +#include + +static HWND getconsolewindow(void) { + HWND hConsoleWindow = NULL; + HANDLE k32; + // + // See if GetConsoleWindow is available (Windows 2000 or later) + // + k32 = GetModuleHandle(TEXT("kernel32.dll")); + if(k32) { + typedef HWND (* WINAPI gcw_t)(void); + gcw_t gcw = (gcw_t)GetProcAddress(k32, TEXT("GetConsoleWindow")); + if(gcw) { + hConsoleWindow = gcw(); + } + } + // + // There is an alternative method that involves FindWindow, but it's too + // cumbersome for just printing a warning. + // + return hConsoleWindow; +} + +void commandlinewarning(void) { + HWND hConsoleWindow; + DWORD processId = 0; + // + // This trick doesn't work in Win9x + // + if(GetVersion() >= ((DWORD)0x80000000LU)) { return; } + // + // See if the console window belongs to my own process + // + hConsoleWindow = getconsolewindow(); + if(!hConsoleWindow) { return; } + GetWindowThreadProcessId(hConsoleWindow, &processId); + if(GetCurrentProcessId() == processId) { + printf( + "\n" + "Note: This is a command-line application.\n" + "It was meant to run from a Windows command prompt.\n\n" + "Press ENTER to close this window..." + ); + fflush(stdout); + fgetc(stdin); + } +} + +#else + +void commandlinewarning(void) {} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// Work around some problems with the Mariko CC toolchain +// +#ifdef MARIKO_CC + +// 32-bit signed and unsigned mod seem buggy; this solves it +unsigned long __umodsi3(unsigned long a, unsigned long b) { return a - (a / b) * b; } +signed long __modsi3(signed long a, signed long b) { return a - (a / b) * b; } + +// Some kind of soft float linkage issue? +void __cmpdf2(void) {} + +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/edccchk.c b/edccchk.c new file mode 100644 index 0000000..8e62c95 --- /dev/null +++ b/edccchk.c @@ -0,0 +1,636 @@ +//////////////////////////////////////////////////////////////////////////////// +// +#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 "common.h" +#include "banner.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 +// + +//////////////////////////////////////////////////////////////////////////////// + +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 mode2f2sectors; +static uint32_t mode2f2errors; +static uint32_t totalsectors; +static uint32_t totalerrors; + +//////////////////////////////////////////////////////////////////////////////// +// +// 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) { + 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 +) { + 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 +) { + 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 +) { + 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}; + +//////////////////////////////////////////////////////////////////////////////// +// +// Check if this is a sector we can compress +// +// Sector types: +// 0: Literal bytes (not a sector) +// 1: 2352 mode 1 predict sync, mode, reserved, edc, ecc +// 2: 2336 mode 2 form 1 predict redundant flags, edc, ecc +// 3: 2336 mode 2 form 2 predict redundant flags, edc +// +static int8_t detect_sector(const uint8_t* sector, size_t size_available) { + if( + size_available >= 2352 && + 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 && + sector[0x00F] == 0x01 && // mode (1 byte) + 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 + ) { + // + // Might be Mode 1 + // + if( + ecc_checksector( + sector + 0xC, + sector + 0x10, + sector + 0x81C + ) && + edc_compute(0, sector, 0x810) == get32lsb(sector + 0x810) + ) { + return 1; // Mode 1 + } + + } else if( + size_available >= 2336 && + sector[0] == sector[4] && // flags (4 bytes) + sector[1] == sector[5] && // versus redundant copy + sector[2] == sector[6] && + sector[3] == sector[7] + ) { + // + // Might be Mode 2, Form 1 or 2 + // + if( + ecc_checksector( + zeroaddress, + sector, + sector + 0x80C + ) && + edc_compute(0, sector, 0x808) == get32lsb(sector + 0x808) + ) { + return 2; // Mode 2, Form 1 + } + // + // Might be Mode 2, Form 2 + // + if( + edc_compute(0, sector, 0x91C) == get32lsb(sector + 0x91C) + ) { + return 3; // Mode 2, Form 2 + } + } + + // + // Nothing + // + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +static uint8_t sector_buffer[2352]; + +//////////////////////////////////////////////////////////////////////////////// + +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(); } +} + +static void setcounter_encode(off_t n) { + int8_t p = ((n >> 20) != (mycounter_encode >> 20)); + mycounter_encode = n; + if(p) { encode_progress(); } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Returns nonzero on error +// +static int8_t ecmify( + const char* infilename +) { + int8_t returncode = 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; + + // + // Current sector type (run) + // + int8_t curtype = -1; // not a valid type + uint32_t curtype_count = 0; + off_t curtype_in_start = 0; + + uint32_t literal_skip = 0; + + off_t input_file_length; + off_t input_bytes_checked = 0; + off_t input_bytes_queued = 0; + + off_t typetally[4] = {0,0,0,0}; + + size_t queue_size = ((size_t)(-1)) - 4095; + if((unsigned long)queue_size > 0x40000lu) { + queue_size = (size_t)0x40000lu; + } + + // + // Allocate space for queue + // + queue = malloc(queue_size); + if(!queue) { + printf("Out of memory\n"); + goto error; + } + + // + // Open both files + // + in = fopen(infilename, "rb"); + if(!in) { goto error_in; } + + printf("Checking %s...\n", infilename); + + // + // Get the length of the input file + // + if(fseeko(in, 0, SEEK_END) != 0) { goto error_in; } + input_file_length = ftello(in); + 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; + mode2f2sectors= 0; + mode2f2errors= 0; + totalsectors= 0; + totalerrors= 0; + + for(;;) { + int8_t detecttype; + + // + // Refill queue if necessary + // + if( + (queue_bytes_available < 2352) && + (((off_t)queue_bytes_available) < (input_file_length - input_bytes_queued)) + ) { + // + // 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) { + 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) { + // + // No data left to read -> quit + // + detecttype = -1; + + } + + 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 + ) + { + // Just for debug +// fprintf(stderr, "Address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); + if(sector[0x00F] == 0x00) // mode (1 byte) + { + 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) + { + 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]); + } + } + else if(sector[0x00F] == 0x02) // mode (1 byte) + { + uint8_t* m2sec = sector + 0x10; + + if((sector[0x012] & 0x20) == 0x20) // mode 2 form 2 + { + mode2f2sectors++; + if(edc_compute(0, m2sec, 0x91C) != get32lsb(m2sec + 0x91C)) + { + fprintf(stderr, "Mode 2 form 2 sector with error at address: %02X:%02X:%02X\n", sector[0x00C], sector[0x00D], sector[0x00E]); + mode2f2errors++; + } + } + 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]); + mode2f1errors++; + } + } + } + else // Unknown sector mode!!! + { + nondatasectors++; + } + } + else // Non data sector + { + nondatasectors++; + } + + + // + // Current type is negative ==> quit + // + if(detecttype < 0) { break; } + + // + // Advance to the next sector + // + totalsectors++; + input_bytes_checked += 2352; + queue_start_ofs += 2352; + queue_bytes_available -= 2352; + } + + // + // Show report + // + totalsectors++; + 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("Mode 2 form 2 sectors... %d\n", mode2f2sectors); + printf("\twith errors..... %d\n", mode2f2errors); + printf("Total sectors........... %d\n", totalsectors); + printf("Total errors............ %d\n", totalerrors); + // + // 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) { + int returncode = 0; + char* infilename = NULL; + + normalize_argv0(argv[0]); + + 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; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/version.h b/version.h new file mode 100644 index 0000000..799d398 --- /dev/null +++ b/version.h @@ -0,0 +1 @@ +"v1.00"