1 /* cabinfo -- dumps useful information from cabinets
2 * (C) 2000 Stuart Caie <kyzer@4u.net>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 /* structure offsets */
24 #define cfhead_Signature (0x00)
25 #define cfhead_CabinetSize (0x08)
26 #define cfhead_FileOffset (0x10)
27 #define cfhead_MinorVersion (0x18)
28 #define cfhead_MajorVersion (0x19)
29 #define cfhead_NumFolders (0x1A)
30 #define cfhead_NumFiles (0x1C)
31 #define cfhead_Flags (0x1E)
32 #define cfhead_SetID (0x20)
33 #define cfhead_CabinetIndex (0x22)
34 #define cfhead_SIZEOF (0x24)
35 #define cfheadext_HeaderReserved (0x00)
36 #define cfheadext_FolderReserved (0x02)
37 #define cfheadext_DataReserved (0x03)
38 #define cfheadext_SIZEOF (0x04)
39 #define cffold_DataOffset (0x00)
40 #define cffold_NumBlocks (0x04)
41 #define cffold_CompType (0x06)
42 #define cffold_SIZEOF (0x08)
43 #define cffile_UncompressedSize (0x00)
44 #define cffile_FolderOffset (0x04)
45 #define cffile_FolderIndex (0x08)
46 #define cffile_Date (0x0A)
47 #define cffile_Time (0x0C)
48 #define cffile_Attribs (0x0E)
49 #define cffile_SIZEOF (0x10)
50 #define cfdata_CheckSum (0x00)
51 #define cfdata_CompressedSize (0x04)
52 #define cfdata_UncompressedSize (0x06)
53 #define cfdata_SIZEOF (0x08)
56 #define cffoldCOMPTYPE_MASK (0x000f)
57 #define cffoldCOMPTYPE_NONE (0x0000)
58 #define cffoldCOMPTYPE_MSZIP (0x0001)
59 #define cffoldCOMPTYPE_QUANTUM (0x0002)
60 #define cffoldCOMPTYPE_LZX (0x0003)
61 #define cfheadPREV_CABINET (0x0001)
62 #define cfheadNEXT_CABINET (0x0002)
63 #define cfheadRESERVE_PRESENT (0x0004)
64 #define cffileCONTINUED_FROM_PREV (0xFFFD)
65 #define cffileCONTINUED_TO_NEXT (0xFFFE)
66 #define cffileCONTINUED_PREV_AND_NEXT (0xFFFF)
67 #define cffile_A_RDONLY (0x01)
68 #define cffile_A_HIDDEN (0x02)
69 #define cffile_A_SYSTEM (0x04)
70 #define cffile_A_ARCH (0x20)
71 #define cffile_A_EXEC (0x40)
72 #define cffile_A_NAME_IS_UTF (0x80)
77 unsigned long filelength;
81 #define EndGetI32(a) ((((a)[3])<<24)|(((a)[2])<<16)|(((a)[1])<<8)|((a)[0]))
82 #define EndGetI16(a) ((((a)[1])<<8)|((a)[0]))
83 #define GETLONG(n) EndGetI32(&buf[n])
84 #define GETWORD(n) EndGetI16(&buf[n])
85 #define GETBYTE(n) ((int)buf[n])
87 #define GETOFFSET (ftell(fh))
88 #define READ(buf,len) if (myread((void *)(buf),(len))) return
89 #define SKIP(offset) if (myseek((offset),SEEK_CUR)) return
90 #define SEEK(offset) if (myseek((offset),SEEK_SET)) return
94 int myread(void *buf, int length) {
95 int remain = filelength - GETOFFSET;
96 if (length > remain) length = remain;
97 if (fread(buf, 1, length, fh) != length) {
104 int myseek(unsigned long offset, int mode) {
105 if (fseek(fh, offset, mode) != 0) {
112 int main(int argc, char *argv[]) {
113 printf("Cabinet information dumper by Stuart Caie <kyzer@4u.net>\n");
116 printf("Usage: %s <file.cab>\n", argv[0]);
120 if (!(fh = fopen((filename = argv[1]), "rb"))) {
125 if (fseek(fh, 0, SEEK_END) != 0) {
131 filelength = (unsigned long) ftell(fh);
133 if (fseek(fh, 0, SEEK_SET) != 0) {
139 printf("Examining file \"%s\" (%u bytes)...\n", filename, filelength);
146 #define SEARCH_SIZE (32*1024)
147 unsigned char search_buf[SEARCH_SIZE];
150 unsigned char *pstart = &search_buf[0], *pend, *p;
151 unsigned long offset, caboff, cablen, foffset;
155 for (offset = 0; offset < filelength; offset += length) {
156 /* search length is either the full length of the search buffer,
157 * or the amount of data remaining to the end of the file,
160 length = filelength - offset;
161 if (length > SEARCH_SIZE) length = SEARCH_SIZE;
163 /* fill the search buffer with data from disk */
165 READ(&search_buf[0], length);
166 /* read through the entire buffer. */
168 pend = &search_buf[length];
173 /* we spend most of our time in this while loop, looking for
174 * a leading 'M' of the 'MSCF' signature
176 while (*p++ != 0x4D && p < pend);
177 if (p < pend) state = 1; /* if we found tht 'M', advance state */
180 /* verify that the next 3 bytes are 'S', 'C' and 'F' */
181 case 1: state = (*p++ == 0x53) ? 2 : 0; break;
182 case 2: state = (*p++ == 0x43) ? 3 : 0; break;
183 case 3: state = (*p++ == 0x46) ? 4 : 0; break;
185 /* we don't care about bytes 4-7 */
186 /* bytes 8-11 are the overall length of the cabinet */
187 case 8: cablen = *p++; state++; break;
188 case 9: cablen |= *p++ << 8; state++; break;
189 case 10: cablen |= *p++ << 16; state++; break;
190 case 11: cablen |= *p++ << 24; state++; break;
192 /* we don't care about bytes 12-15 */
193 /* bytes 16-19 are the offset within the cabinet of the filedata */
194 case 16: foffset = *p++; state++; break;
195 case 17: foffset |= *p++ << 8; state++; break;
196 case 18: foffset |= *p++ << 16; state++; break;
197 case 19: foffset |= *p++ << 24;
198 /* now we have recieved 20 bytes of potential cab header. */
199 /* work out the offset in the file of this potential cabinet */
200 caboff = offset + (p-pstart) - 20;
201 /* check that the files offset is less than the alleged length
202 * of the cabinet, and that the offset + the alleged length are
203 * 'roughly' within the end of overall file length
205 if ((foffset < cablen) &&
206 ((caboff + foffset) < (filelength + 32)) &&
207 ((caboff + cablen) < (filelength + 32)) )
209 /* found a potential result - try loading it */
210 printf("Found cabinet header at offset %u\n", caboff);
213 offset = caboff + cablen;
223 } /* while p < pend */
224 } /* while offset < filelength */
231 #define CAB_NAMEMAX (1024)
233 /* UTF translates two-byte unicode characters into 1, 2 or 3 bytes.
234 * %000000000xxxxxxx -> %0xxxxxxx
235 * %00000xxxxxyyyyyy -> %110xxxxx %10yyyyyy
236 * %xxxxyyyyyyzzzzzz -> %1110xxxx %10yyyyyy %10zzzzzz
238 * Therefore, the inverse is as follows:
240 * 0x00 - 0x7F = one byte char
241 * 0x80 - 0xBF = invalid
242 * 0xC0 - 0xDF = 2 byte char (next char only 0x80-0xBF is valid)
243 * 0xE0 - 0xEF = 3 byte char (next 2 chars only 0x80-0xBF is valid)
244 * 0xF0 - 0xFF = invalid
247 /* translate UTF -> ASCII */
248 int convertUTF(unsigned char *in) {
249 unsigned char c, *out = in, *end = in + strlen(in) + 1;
253 /* read unicode character */
254 if ((c = *in++) < 0x80) x = c;
256 if (c < 0xC0) return 0;
259 if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F);
263 if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F)<<6;
264 if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F);
269 /* terrible unicode -> ASCII conversion */
270 if (x > 127) x = '_';
272 if (in > end) return 0; /* just in case */
273 } while ((*out++ = (unsigned char) x));
280 unsigned char buf[64];
281 unsigned char namebuf[CAB_NAMEMAX];
284 int num_folders, num_files, num_blocks = 0;
285 int header_res = 0, folder_res = 0, data_res = 0;
286 int i, x, offset, base_offset, files_offset, base;
288 base_offset = GETOFFSET;
290 READ(&buf, cfhead_SIZEOF);
292 x = GETWORD(cfhead_Flags);
295 "\n*** HEADER SECTION ***\n\n"
296 "Cabinet signature = '%4.4s'\n"
297 "Cabinet size = %u bytes\n"
298 "Offset of files = %u\n"
299 "Cabinet format version = %d.%d\n"
300 "Number of folders = %u\n"
301 "Number of files = %u\n"
302 "Header flags = 0x%04x%s%s%s\n"
304 "Cabinet set index = %u\n",
307 GETLONG(cfhead_CabinetSize),
308 files_offset = (GETLONG(cfhead_FileOffset) + base_offset),
309 GETBYTE(cfhead_MajorVersion),
310 GETBYTE(cfhead_MinorVersion),
311 num_folders = GETWORD(cfhead_NumFolders),
312 num_files = GETWORD(cfhead_NumFiles),
314 ((x & cfheadPREV_CABINET) ? " PREV_CABINET" : ""),
315 ((x & cfheadNEXT_CABINET) ? " NEXT_CABINET" : ""),
316 ((x & cfheadRESERVE_PRESENT) ? " RESERVE_PRESENT" : ""),
317 GETWORD(cfhead_SetID),
318 GETWORD(cfhead_CabinetIndex)
321 if (num_folders == 0) { printf("ERROR: no folders\n"); return; }
322 if (num_files == 0) { printf("ERROR: no files\n"); return; }
324 if (buf[0]!='M' || buf[1]!='S' || buf[2]!='C' || buf[3]!='F')
325 printf("WARNING: cabinet doesn't start with MSCF signature\n");
327 if (GETBYTE(cfhead_MajorVersion) > 1
328 || GETBYTE(cfhead_MinorVersion) > 3)
329 printf("WARNING: format version > 1.3\n");
333 if (x & cfheadRESERVE_PRESENT) {
334 READ(&buf, cfheadext_SIZEOF);
335 header_res = GETWORD(cfheadext_HeaderReserved);
336 folder_res = GETBYTE(cfheadext_FolderReserved);
337 data_res = GETBYTE(cfheadext_DataReserved);
340 printf("Reserved header space = %u\n", header_res);
341 printf("Reserved folder space = %u\n", folder_res);
342 printf("Reserved datablk space = %u\n", data_res);
344 if (header_res > 60000)
345 printf("WARNING: header reserved space > 60000\n");
348 printf("[Reserved header: offset %lu, size %u]\n", GETOFFSET, header_res);
352 if (x & cfheadPREV_CABINET) {
354 READ(&namebuf, CAB_NAMEMAX);
355 SEEK(base + strlen(namebuf) + 1);
356 printf("Previous cabinet file = %s\n", namebuf);
357 if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
360 READ(&namebuf, CAB_NAMEMAX);
361 SEEK(base + strlen(namebuf) + 1);
362 printf("Previous disk name = %s\n", namebuf);
363 if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
366 if (x & cfheadNEXT_CABINET) {
368 READ(&namebuf, CAB_NAMEMAX);
369 SEEK(base + strlen(namebuf) + 1);
370 printf("Next cabinet file = %s\n", namebuf);
371 if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
374 READ(&namebuf, CAB_NAMEMAX);
375 SEEK(base + strlen(namebuf) + 1);
376 printf("Next disk name = %s\n", namebuf);
377 if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
380 printf("\n*** FOLDERS SECTION ***\n");
382 for (i = 0; i < num_folders; i++) {
384 READ(&buf, cffold_SIZEOF);
386 switch(GETWORD(cffold_CompType) & cffoldCOMPTYPE_MASK) {
387 case cffoldCOMPTYPE_NONE: name = "stored"; break;
388 case cffoldCOMPTYPE_MSZIP: name = "MSZIP"; break;
389 case cffoldCOMPTYPE_QUANTUM: name = "Quantum"; break;
390 case cffoldCOMPTYPE_LZX: name = "LZX"; break;
391 default: name = "unknown"; break;
395 "\n[New folder at offset %u]\n"
396 "Offset of folder = %u\n"
397 "Num. blocks in folder = %u\n"
398 "Compression type = 0x%04x [%s]\n",
401 base_offset + GETLONG(cffold_DataOffset),
402 GETWORD(cffold_NumBlocks),
403 GETWORD(cffold_CompType),
407 num_blocks += GETWORD(cffold_NumBlocks);
410 printf("[Reserved folder: offset %lu, size %u]\n", GETOFFSET, folder_res);
415 printf("\n*** FILES SECTION ***\n");
417 if (GETOFFSET != files_offset) {
418 printf("WARNING: weird file offset in header\n");
423 for (i = 0; i < num_files; i++) {
425 READ(&buf, cffile_SIZEOF);
427 switch (GETWORD(cffile_FolderIndex)) {
428 case cffileCONTINUED_PREV_AND_NEXT:
429 name = "continued from previous and to next cabinet";
431 case cffileCONTINUED_FROM_PREV:
432 name = "continued from previous cabinet";
434 case cffileCONTINUED_TO_NEXT:
435 name = "continued to next cabinet";
438 name = "normal folder";
442 x = GETWORD(cffile_Attribs);
445 READ(&namebuf, CAB_NAMEMAX);
446 SEEK(base + strlen(namebuf) + 1);
447 if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
449 /* convert filename */
450 if (x & cffile_A_NAME_IS_UTF) {
451 if (!convertUTF(namebuf)) printf("WARNING: invalid UTF filename");
455 "\n[New file at offset %u]\n"
457 "File size = %u bytes\n"
458 "Offset within folder = %u\n"
459 "Folder index = 0x%04x [%s]\n"
460 "Date / time = %02d/%02d/%4d %02d:%02d:%02d\n"
461 "File attributes = 0x%02x %s%s%s%s%s\n",
464 GETLONG(cffile_UncompressedSize),
465 GETLONG(cffile_FolderOffset),
466 GETWORD(cffile_FolderIndex),
468 GETWORD(cffile_Date) & 0x1f,
469 (GETWORD(cffile_Date)>>5) & 0xf,
470 (GETWORD(cffile_Date)>>9) + 1980,
471 GETWORD(cffile_Time) >> 11,
472 (GETWORD(cffile_Time)>>5) & 0x3f,
473 (GETWORD(cffile_Time) << 1) & 0x3e,
475 (x & cffile_A_RDONLY) ? "RDONLY " : "",
476 (x & cffile_A_HIDDEN) ? "HIDDEN " : "",
477 (x & cffile_A_SYSTEM) ? "SYSTEM " : "",
478 (x & cffile_A_ARCH) ? "ARCH " : "",
479 (x & cffile_A_EXEC) ? "EXEC " : ""
483 printf("\n*** DATABLOCKS SECTION ***\n");
484 printf("*** Note: offset is BLOCK offset. Add 8 for DATA offset! ***\n\n");
486 for (i = 0; i < num_blocks; i++) {
488 READ(&buf, cfdata_SIZEOF);
489 printf("Block %5d: offset %10d / csum %08x / c=%5d / u=%5d\n",
490 i, offset, GETLONG(cfdata_CheckSum),
491 x = GETWORD(cfdata_CompressedSize),
492 GETWORD(cfdata_UncompressedSize)