/* cabinfo -- dumps useful information from cabinets * (C) 2000 Stuart Caie * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include /* structure offsets */ #define cfhead_Signature (0x00) #define cfhead_CabinetSize (0x08) #define cfhead_FileOffset (0x10) #define cfhead_MinorVersion (0x18) #define cfhead_MajorVersion (0x19) #define cfhead_NumFolders (0x1A) #define cfhead_NumFiles (0x1C) #define cfhead_Flags (0x1E) #define cfhead_SetID (0x20) #define cfhead_CabinetIndex (0x22) #define cfhead_SIZEOF (0x24) #define cfheadext_HeaderReserved (0x00) #define cfheadext_FolderReserved (0x02) #define cfheadext_DataReserved (0x03) #define cfheadext_SIZEOF (0x04) #define cffold_DataOffset (0x00) #define cffold_NumBlocks (0x04) #define cffold_CompType (0x06) #define cffold_SIZEOF (0x08) #define cffile_UncompressedSize (0x00) #define cffile_FolderOffset (0x04) #define cffile_FolderIndex (0x08) #define cffile_Date (0x0A) #define cffile_Time (0x0C) #define cffile_Attribs (0x0E) #define cffile_SIZEOF (0x10) #define cfdata_CheckSum (0x00) #define cfdata_CompressedSize (0x04) #define cfdata_UncompressedSize (0x06) #define cfdata_SIZEOF (0x08) /* flags */ #define cffoldCOMPTYPE_MASK (0x000f) #define cffoldCOMPTYPE_NONE (0x0000) #define cffoldCOMPTYPE_MSZIP (0x0001) #define cffoldCOMPTYPE_QUANTUM (0x0002) #define cffoldCOMPTYPE_LZX (0x0003) #define cfheadPREV_CABINET (0x0001) #define cfheadNEXT_CABINET (0x0002) #define cfheadRESERVE_PRESENT (0x0004) #define cffileCONTINUED_FROM_PREV (0xFFFD) #define cffileCONTINUED_TO_NEXT (0xFFFE) #define cffileCONTINUED_PREV_AND_NEXT (0xFFFF) #define cffile_A_RDONLY (0x01) #define cffile_A_HIDDEN (0x02) #define cffile_A_SYSTEM (0x04) #define cffile_A_ARCH (0x20) #define cffile_A_EXEC (0x40) #define cffile_A_NAME_IS_UTF (0x80) FILE *fh; char *filename; unsigned long filelength; void search(); void getinfo(); #define EndGetI32(a) ((((a)[3])<<24)|(((a)[2])<<16)|(((a)[1])<<8)|((a)[0])) #define EndGetI16(a) ((((a)[1])<<8)|((a)[0])) #define GETLONG(n) EndGetI32(&buf[n]) #define GETWORD(n) EndGetI16(&buf[n]) #define GETBYTE(n) ((int)buf[n]) #define GETOFFSET (ftell(fh)) #define READ(buf,len) if (myread((void *)(buf),(len))) return #define SKIP(offset) if (myseek((offset),SEEK_CUR)) return #define SEEK(offset) if (myseek((offset),SEEK_SET)) return int myread(void *buf, int length) { int remain = filelength - GETOFFSET; if (length > remain) length = remain; if (fread(buf, 1, length, fh) != length) { perror(filename); return 1; } return 0; } int myseek(unsigned long offset, int mode) { if (fseek(fh, offset, mode) != 0) { perror(filename); return 1; } return 0; } int main(int argc, char *argv[]) { printf("Cabinet information dumper by Stuart Caie \n"); if (argc <= 1) { printf("Usage: %s \n", argv[0]); return 1; } if (!(fh = fopen((filename = argv[1]), "rb"))) { perror(filename); return 1; } if (fseek(fh, 0, SEEK_END) != 0) { perror(filename); fclose(fh); return 1; } filelength = (unsigned long) ftell(fh); if (fseek(fh, 0, SEEK_SET) != 0) { perror(filename); fclose(fh); return 1; } printf("Examining file \"%s\" (%u bytes)...\n", filename, filelength); search(); fclose(fh); return 0; } #define SEARCH_SIZE (32*1024) unsigned char search_buf[SEARCH_SIZE]; void search() { unsigned char *pstart = &search_buf[0], *pend, *p; unsigned long offset, caboff, cablen, foffset; size_t length; int state = 0; for (offset = 0; offset < filelength; offset += length) { /* search length is either the full length of the search buffer, * or the amount of data remaining to the end of the file, * whichever is less. */ length = filelength - offset; if (length > SEARCH_SIZE) length = SEARCH_SIZE; /* fill the search buffer with data from disk */ SEEK(offset); READ(&search_buf[0], length); /* read through the entire buffer. */ p = pstart; pend = &search_buf[length]; while (p < pend) { switch (state) { /* starting state */ case 0: /* we spend most of our time in this while loop, looking for * a leading 'M' of the 'MSCF' signature */ while (*p++ != 0x4D && p < pend); if (p < pend) state = 1; /* if we found tht 'M', advance state */ break; /* verify that the next 3 bytes are 'S', 'C' and 'F' */ case 1: state = (*p++ == 0x53) ? 2 : 0; break; case 2: state = (*p++ == 0x43) ? 3 : 0; break; case 3: state = (*p++ == 0x46) ? 4 : 0; break; /* we don't care about bytes 4-7 */ /* bytes 8-11 are the overall length of the cabinet */ case 8: cablen = *p++; state++; break; case 9: cablen |= *p++ << 8; state++; break; case 10: cablen |= *p++ << 16; state++; break; case 11: cablen |= *p++ << 24; state++; break; /* we don't care about bytes 12-15 */ /* bytes 16-19 are the offset within the cabinet of the filedata */ case 16: foffset = *p++; state++; break; case 17: foffset |= *p++ << 8; state++; break; case 18: foffset |= *p++ << 16; state++; break; case 19: foffset |= *p++ << 24; /* now we have recieved 20 bytes of potential cab header. */ /* work out the offset in the file of this potential cabinet */ caboff = offset + (p-pstart) - 20; /* check that the files offset is less than the alleged length * of the cabinet, and that the offset + the alleged length are * 'roughly' within the end of overall file length */ if ((foffset < cablen) && ((caboff + foffset) < (filelength + 32)) && ((caboff + cablen) < (filelength + 32)) ) { /* found a potential result - try loading it */ printf("Found cabinet header at offset %u\n", caboff); SEEK(caboff); getinfo(); offset = caboff + cablen; length = 0; p = pend; } state = 0; break; default: p++, state++; break; } /* switch state */ } /* while p < pend */ } /* while offset < filelength */ } #define CAB_NAMEMAX (1024) /* UTF translates two-byte unicode characters into 1, 2 or 3 bytes. * %000000000xxxxxxx -> %0xxxxxxx * %00000xxxxxyyyyyy -> %110xxxxx %10yyyyyy * %xxxxyyyyyyzzzzzz -> %1110xxxx %10yyyyyy %10zzzzzz * * Therefore, the inverse is as follows: * First char: * 0x00 - 0x7F = one byte char * 0x80 - 0xBF = invalid * 0xC0 - 0xDF = 2 byte char (next char only 0x80-0xBF is valid) * 0xE0 - 0xEF = 3 byte char (next 2 chars only 0x80-0xBF is valid) * 0xF0 - 0xFF = invalid */ /* translate UTF -> ASCII */ int convertUTF(unsigned char *in) { unsigned char c, *out = in, *end = in + strlen(in) + 1; unsigned int x; do { /* read unicode character */ if ((c = *in++) < 0x80) x = c; else { if (c < 0xC0) return 0; else if (c < 0xE0) { x = (c & 0x1F) << 6; if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F); } else if (c < 0xF0) { x = (c & 0xF) << 12; if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F)<<6; if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F); } else return 0; } /* terrible unicode -> ASCII conversion */ if (x > 127) x = '_'; if (in > end) return 0; /* just in case */ } while ((*out++ = (unsigned char) x)); return 1; } void getinfo() { unsigned char buf[64]; unsigned char namebuf[CAB_NAMEMAX]; char *name; int num_folders, num_files, num_blocks = 0; int header_res = 0, folder_res = 0, data_res = 0; int i, x, offset, base_offset, files_offset, base; base_offset = GETOFFSET; READ(&buf, cfhead_SIZEOF); x = GETWORD(cfhead_Flags); printf( "\n*** HEADER SECTION ***\n\n" "Cabinet signature = '%4.4s'\n" "Cabinet size = %u bytes\n" "Offset of files = %u\n" "Cabinet format version = %d.%d\n" "Number of folders = %u\n" "Number of files = %u\n" "Header flags = 0x%04x%s%s%s\n" "Set ID = %u\n" "Cabinet set index = %u\n", buf, GETLONG(cfhead_CabinetSize), files_offset = (GETLONG(cfhead_FileOffset) + base_offset), GETBYTE(cfhead_MajorVersion), GETBYTE(cfhead_MinorVersion), num_folders = GETWORD(cfhead_NumFolders), num_files = GETWORD(cfhead_NumFiles), x, ((x & cfheadPREV_CABINET) ? " PREV_CABINET" : ""), ((x & cfheadNEXT_CABINET) ? " NEXT_CABINET" : ""), ((x & cfheadRESERVE_PRESENT) ? " RESERVE_PRESENT" : ""), GETWORD(cfhead_SetID), GETWORD(cfhead_CabinetIndex) ); if (num_folders == 0) { printf("ERROR: no folders\n"); return; } if (num_files == 0) { printf("ERROR: no files\n"); return; } if (buf[0]!='M' || buf[1]!='S' || buf[2]!='C' || buf[3]!='F') printf("WARNING: cabinet doesn't start with MSCF signature\n"); if (GETBYTE(cfhead_MajorVersion) > 1 || GETBYTE(cfhead_MinorVersion) > 3) printf("WARNING: format version > 1.3\n"); if (x & cfheadRESERVE_PRESENT) { READ(&buf, cfheadext_SIZEOF); header_res = GETWORD(cfheadext_HeaderReserved); folder_res = GETBYTE(cfheadext_FolderReserved); data_res = GETBYTE(cfheadext_DataReserved); } printf("Reserved header space = %u\n", header_res); printf("Reserved folder space = %u\n", folder_res); printf("Reserved datablk space = %u\n", data_res); if (header_res > 60000) printf("WARNING: header reserved space > 60000\n"); if (header_res) { printf("[Reserved header: offset %lu, size %u]\n", GETOFFSET, header_res); SKIP(header_res); } if (x & cfheadPREV_CABINET) { base = GETOFFSET; READ(&namebuf, CAB_NAMEMAX); SEEK(base + strlen(namebuf) + 1); printf("Previous cabinet file = %s\n", namebuf); if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n"); base = GETOFFSET; READ(&namebuf, CAB_NAMEMAX); SEEK(base + strlen(namebuf) + 1); printf("Previous disk name = %s\n", namebuf); if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n"); } if (x & cfheadNEXT_CABINET) { base = GETOFFSET; READ(&namebuf, CAB_NAMEMAX); SEEK(base + strlen(namebuf) + 1); printf("Next cabinet file = %s\n", namebuf); if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n"); base = GETOFFSET; READ(&namebuf, CAB_NAMEMAX); SEEK(base + strlen(namebuf) + 1); printf("Next disk name = %s\n", namebuf); if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n"); } printf("\n*** FOLDERS SECTION ***\n"); for (i = 0; i < num_folders; i++) { offset = GETOFFSET; READ(&buf, cffold_SIZEOF); switch(GETWORD(cffold_CompType) & cffoldCOMPTYPE_MASK) { case cffoldCOMPTYPE_NONE: name = "stored"; break; case cffoldCOMPTYPE_MSZIP: name = "MSZIP"; break; case cffoldCOMPTYPE_QUANTUM: name = "Quantum"; break; case cffoldCOMPTYPE_LZX: name = "LZX"; break; default: name = "unknown"; break; } printf( "\n[New folder at offset %u]\n" "Offset of folder = %u\n" "Num. blocks in folder = %u\n" "Compression type = 0x%04x [%s]\n", offset, base_offset + GETLONG(cffold_DataOffset), GETWORD(cffold_NumBlocks), GETWORD(cffold_CompType), name ); num_blocks += GETWORD(cffold_NumBlocks); if (folder_res) { printf("[Reserved folder: offset %lu, size %u]\n", GETOFFSET, folder_res); SKIP(folder_res); } } printf("\n*** FILES SECTION ***\n"); if (GETOFFSET != files_offset) { printf("WARNING: weird file offset in header\n"); SEEK(files_offset); } for (i = 0; i < num_files; i++) { offset = GETOFFSET; READ(&buf, cffile_SIZEOF); switch (GETWORD(cffile_FolderIndex)) { case cffileCONTINUED_PREV_AND_NEXT: name = "continued from previous and to next cabinet"; break; case cffileCONTINUED_FROM_PREV: name = "continued from previous cabinet"; break; case cffileCONTINUED_TO_NEXT: name = "continued to next cabinet"; break; default: name = "normal folder"; break; } x = GETWORD(cffile_Attribs); base = GETOFFSET; READ(&namebuf, CAB_NAMEMAX); SEEK(base + strlen(namebuf) + 1); if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n"); /* convert filename */ if (x & cffile_A_NAME_IS_UTF) { if (!convertUTF(namebuf)) printf("WARNING: invalid UTF filename"); } printf( "\n[New file at offset %u]\n" "File name = %s\n" "File size = %u bytes\n" "Offset within folder = %u\n" "Folder index = 0x%04x [%s]\n" "Date / time = %02d/%02d/%4d %02d:%02d:%02d\n" "File attributes = 0x%02x %s%s%s%s%s\n", offset, namebuf, GETLONG(cffile_UncompressedSize), GETLONG(cffile_FolderOffset), GETWORD(cffile_FolderIndex), name, GETWORD(cffile_Date) & 0x1f, (GETWORD(cffile_Date)>>5) & 0xf, (GETWORD(cffile_Date)>>9) + 1980, GETWORD(cffile_Time) >> 11, (GETWORD(cffile_Time)>>5) & 0x3f, (GETWORD(cffile_Time) << 1) & 0x3e, x, (x & cffile_A_RDONLY) ? "RDONLY " : "", (x & cffile_A_HIDDEN) ? "HIDDEN " : "", (x & cffile_A_SYSTEM) ? "SYSTEM " : "", (x & cffile_A_ARCH) ? "ARCH " : "", (x & cffile_A_EXEC) ? "EXEC " : "" ); } printf("\n*** DATABLOCKS SECTION ***\n"); printf("*** Note: offset is BLOCK offset. Add 8 for DATA offset! ***\n\n"); for (i = 0; i < num_blocks; i++) { offset = GETOFFSET; READ(&buf, cfdata_SIZEOF); printf("Block %5d: offset %10d / csum %08x / c=%5d / u=%5d\n", i, offset, GETLONG(cfdata_CheckSum), x = GETWORD(cfdata_CompressedSize), GETWORD(cfdata_UncompressedSize) ); SKIP(x); } }