bool unshield_read_common_header(uint8_t** buffer, CommonHeader* common) { uint8_t* p = *buffer; common->signature = READ_UINT32(p); p += 4; if (CAB_SIGNATURE != common->signature) { unshield_error("Invalid file signature"); if (MSCF_SIGNATURE == common->signature) unshield_warning("Found Microsoft Cabinet header. Use cabextract (http://www.kyz.uklinux.net/cabextract.php) to unpack this file."); return false; } common->version = READ_UINT32(p); p += 4; common->volume_info = READ_UINT32(p); p += 4; common->cab_descriptor_offset = READ_UINT32(p); p += 4; common->cab_descriptor_size = READ_UINT32(p); p += 4; #if VERBOSE unshield_trace("Common header: %08x %08x %08x %08x", common->version, common->volume_info, common->cab_descriptor_offset, common->cab_descriptor_size); #endif *buffer = p; return true; }
static UnshieldReader* unshield_reader_create(/*{{{*/ Unshield* unshield, int index, FileDescriptor* file_descriptor) { bool success = false; UnshieldReader* reader = NEW1(UnshieldReader); if (!reader) return NULL; reader->unshield = unshield; reader->index = index; reader->file_descriptor = file_descriptor; for (;;) { if (!unshield_reader_open_volume(reader, file_descriptor->volume)) { unshield_error("Failed to open volume %i", file_descriptor->volume); goto exit; } /* Start with the correct volume for IS5 cabinets */ if (reader->unshield->header_list->major_version == 5 && index > (int)reader->volume_header.last_file_index) { unshield_trace("Trying next volume..."); file_descriptor->volume++; continue; } break; }; success = true; exit: if (success) return reader; FREE(reader); return NULL; }/*}}}*/
bool unshield_file_save_old(Unshield* unshield, int index, const char* filename)/*{{{*/ { /* XXX: Thou Shalt Not Cut & Paste... */ bool success = false; FILE* output = NULL; size_t input_buffer_size = BUFFER_SIZE; unsigned char* input_buffer = (unsigned char*)malloc(BUFFER_SIZE); unsigned char* output_buffer = (unsigned char*)malloc(BUFFER_SIZE); int bytes_left; uLong total_written = 0; UnshieldReader* reader = NULL; FileDescriptor* file_descriptor; if (!unshield) goto exit; if (!(file_descriptor = unshield_get_file_descriptor(unshield, index))) { unshield_error("Failed to get file descriptor for file %i", index); goto exit; } if ((file_descriptor->flags & FILE_INVALID) || 0 == file_descriptor->data_offset) { /* invalid file */ goto exit; } if (file_descriptor->link_flags & LINK_PREV) { success = unshield_file_save(unshield, file_descriptor->link_previous, filename); goto exit; } reader = unshield_reader_create(unshield, index, file_descriptor); if (!reader) { unshield_error("Failed to create data reader for file %i", index); goto exit; } if (unshield_fsize(reader->volume_file) == (long)file_descriptor->data_offset) { unshield_error("File %i is not inside the cabinet.", index); goto exit; } if (filename) { output = fopen(filename, "w"); if (!output) { unshield_error("Failed to open output file '%s'", filename); goto exit; } } if (file_descriptor->flags & FILE_COMPRESSED) bytes_left = file_descriptor->compressed_size; else bytes_left = file_descriptor->expanded_size; /*unshield_trace("Bytes to read: %i", bytes_left);*/ while (bytes_left > 0) { uLong bytes_to_write = 0; int result; if (file_descriptor->flags & FILE_COMPRESSED) { static const uint8_t END_OF_CHUNK[4] = { 0x00, 0x00, 0xff, 0xff }; uLong read_bytes; size_t input_size = reader->volume_bytes_left; uint8_t* chunk_buffer; while (input_size > input_buffer_size) { input_buffer_size *= 2; #if VERBOSE >= 3 unshield_trace("increased input_buffer_size to 0x%x", input_buffer_size); #endif input_buffer = realloc(input_buffer, input_buffer_size); assert(input_buffer); } if (!unshield_reader_read(reader, input_buffer, input_size)) { #if VERBOSE unshield_error("Failed to read 0x%x bytes of file %i (%s) from input cabinet file %i", input_size, index, unshield_file_name(unshield, index), file_descriptor->volume); #endif goto exit; } bytes_left -= input_size; for (chunk_buffer = input_buffer; input_size; ) { size_t chunk_size; uint8_t* match = find_bytes(chunk_buffer, input_size, END_OF_CHUNK, sizeof(END_OF_CHUNK)); if (!match) { unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", index, unshield_file_name(unshield, index), file_descriptor->volume); goto exit; } chunk_size = match - chunk_buffer; /* Detect when the chunk actually contains the end of chunk marker. Needed by Qtime.smk from "The Feeble Files - spanish version". The first bit of a compressed block is always zero, so we apply this workaround if it's a one. A possibly more proper fix for this would be to have unshield_uncompress_old eat compressed data and discard chunk markers inbetween. */ while ((chunk_size + sizeof(END_OF_CHUNK)) < input_size && chunk_buffer[chunk_size + sizeof(END_OF_CHUNK)] & 1) { unshield_warning("It seems like we have an end of chunk marker inside of a chunk."); chunk_size += sizeof(END_OF_CHUNK); match = find_bytes(chunk_buffer + chunk_size, input_size - chunk_size, END_OF_CHUNK, sizeof(END_OF_CHUNK)); if (!match) { unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", index, unshield_file_name(unshield, index), file_descriptor->volume); goto exit; } chunk_size = match - chunk_buffer; } #if VERBOSE >= 3 unshield_trace("chunk_size = 0x%x", chunk_size); #endif /* add a null byte to make inflate happy */ chunk_buffer[chunk_size] = 0; bytes_to_write = BUFFER_SIZE; read_bytes = chunk_size; result = unshield_uncompress_old(output_buffer, &bytes_to_write, chunk_buffer, &read_bytes); if (Z_OK != result) { unshield_error("Decompression failed with code %i. input_size=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", result, input_size, reader->volume_bytes_left, file_descriptor->volume, read_bytes); goto exit; } #if VERBOSE >= 3 unshield_trace("read_bytes = 0x%x", read_bytes); #endif chunk_buffer += chunk_size; chunk_buffer += sizeof(END_OF_CHUNK); input_size -= chunk_size; input_size -= sizeof(END_OF_CHUNK); if (output) if (bytes_to_write != fwrite(output_buffer, 1, bytes_to_write, output)) { unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); goto exit; } total_written += bytes_to_write; } } else { bytes_to_write = MIN(bytes_left, BUFFER_SIZE); if (!unshield_reader_read(reader, output_buffer, bytes_to_write)) { #if VERBOSE unshield_error("Failed to read %i bytes from input cabinet file %i", bytes_to_write, file_descriptor->volume); #endif goto exit; } bytes_left -= bytes_to_write; if (output) if (bytes_to_write != fwrite(output_buffer, 1, bytes_to_write, output)) { unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); goto exit; } total_written += bytes_to_write; } } if (file_descriptor->expanded_size != total_written) { unshield_error("Expanded size expected to be %i, but was %i", file_descriptor->expanded_size, total_written); goto exit; } success = true; exit: unshield_reader_destroy(reader); FCLOSE(output); FREE(input_buffer); FREE(output_buffer); return success; }/*}}}*/
/* * If filename is NULL, just throw away the result */ bool unshield_file_save (Unshield* unshield, int index, const char* filename)/*{{{*/ { bool success = false; FILE* output = NULL; unsigned char* input_buffer = (unsigned char*)malloc(BUFFER_SIZE+1); unsigned char* output_buffer = (unsigned char*)malloc(BUFFER_SIZE); int bytes_left; uLong total_written = 0; UnshieldReader* reader = NULL; FileDescriptor* file_descriptor; MD5_CTX md5; MD5Init(&md5); if (!unshield) goto exit; if (!(file_descriptor = unshield_get_file_descriptor(unshield, index))) { unshield_error("Failed to get file descriptor for file %i", index); goto exit; } if ((file_descriptor->flags & FILE_INVALID) || 0 == file_descriptor->data_offset) { /* invalid file */ goto exit; } if (file_descriptor->link_flags & LINK_PREV) { success = unshield_file_save(unshield, file_descriptor->link_previous, filename); goto exit; } reader = unshield_reader_create(unshield, index, file_descriptor); if (!reader) { unshield_error("Failed to create data reader for file %i", index); goto exit; } if (unshield_fsize(reader->volume_file) == (long)file_descriptor->data_offset) { unshield_error("File %i is not inside the cabinet.", index); goto exit; } if (filename) { output = fopen(filename, "w"); if (!output) { unshield_error("Failed to open output file '%s'", filename); goto exit; } } if (file_descriptor->flags & FILE_COMPRESSED) bytes_left = file_descriptor->compressed_size; else bytes_left = file_descriptor->expanded_size; /*unshield_trace("Bytes to read: %i", bytes_left);*/ while (bytes_left > 0) { uLong bytes_to_write = BUFFER_SIZE; int result; if (file_descriptor->flags & FILE_COMPRESSED) { uLong read_bytes; uint16_t bytes_to_read = 0; if (!unshield_reader_read(reader, &bytes_to_read, sizeof(bytes_to_read))) { #if VERBOSE unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", sizeof(bytes_to_read), index, unshield_file_name(unshield, index), file_descriptor->volume); #endif goto exit; } bytes_to_read = letoh16(bytes_to_read); if (!unshield_reader_read(reader, input_buffer, bytes_to_read)) { #if VERBOSE unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", bytes_to_read, index, unshield_file_name(unshield, index), file_descriptor->volume); #endif goto exit; } /* add a null byte to make inflate happy */ input_buffer[bytes_to_read] = 0; read_bytes = bytes_to_read+1; result = unshield_uncompress(output_buffer, &bytes_to_write, input_buffer, &read_bytes); if (Z_OK != result) { unshield_error("Decompression failed with code %i. bytes_to_read=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", result, bytes_to_read, reader->volume_bytes_left, file_descriptor->volume, read_bytes); goto exit; } #if VERBOSE >= 3 unshield_trace("read_bytes = %i", read_bytes); #endif bytes_left -= 2; bytes_left -= bytes_to_read; } else { bytes_to_write = MIN(bytes_left, BUFFER_SIZE); if (!unshield_reader_read(reader, output_buffer, bytes_to_write)) { #if VERBOSE unshield_error("Failed to read %i bytes from input cabinet file %i", bytes_to_write, file_descriptor->volume); #endif goto exit; } bytes_left -= bytes_to_write; } MD5Update(&md5, output_buffer, bytes_to_write); if (output) { if (bytes_to_write != fwrite(output_buffer, 1, bytes_to_write, output)) { unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); goto exit; } } total_written += bytes_to_write; } if (file_descriptor->expanded_size != total_written) { unshield_error("Expanded size expected to be %i, but was %i", file_descriptor->expanded_size, total_written); goto exit; } if (unshield->header_list->major_version >= 6) { unsigned char md5result[16]; MD5Final(md5result, &md5); if (0 != memcmp(md5result, file_descriptor->md5, 16)) { unshield_error("MD5 checksum failure for file %i (%s)", index, unshield_file_name(unshield, index)); goto exit; } } success = true; exit: unshield_reader_destroy(reader); FCLOSE(output); FREE(input_buffer); FREE(output_buffer); return success; }/*}}}*/
static bool unshield_reader_read(UnshieldReader* reader, void* buffer, size_t size)/*{{{*/ { bool success = false; uint8_t* p = buffer; size_t bytes_left = size; #if VERBOSE >= 3 unshield_trace("unshield_reader_read start: bytes_left = 0x%x, volume_bytes_left = 0x%x", bytes_left, reader->volume_bytes_left); #endif for (;;) { /* Read as much as possible from this volume */ size_t bytes_to_read = MIN(bytes_left, reader->volume_bytes_left); #if VERBOSE >= 3 unshield_trace("Trying to read 0x%x bytes from offset %08x in volume %i", bytes_to_read, ftell(reader->volume_file), reader->volume); #endif if (bytes_to_read != fread(p, 1, bytes_to_read, reader->volume_file)) { unshield_error("Failed to read 0x%08x bytes of file %i (%s) from volume %i. Current offset = 0x%08x", bytes_to_read, reader->index, unshield_file_name(reader->unshield, reader->index), reader->volume, ftell(reader->volume_file)); goto exit; } bytes_left -= bytes_to_read; reader->volume_bytes_left -= bytes_to_read; #if VERBOSE >= 3 unshield_trace("bytes_left = %i, volume_bytes_left = %i", bytes_left, reader->volume_bytes_left); #endif if (!bytes_left) break; p += bytes_to_read; /* Open next volume */ if (!unshield_reader_open_volume(reader, reader->volume + 1)) { unshield_error("Failed to open volume %i to read %i more bytes", reader->volume + 1, bytes_to_read); goto exit; } } if (reader->file_descriptor->flags & FILE_OBFUSCATED) unshield_reader_deobfuscate(reader, buffer, size); success = true; exit: return success; }/*}}}*/
static bool unshield_reader_open_volume(UnshieldReader* reader, int volume)/*{{{*/ { bool success = false; unsigned data_offset = 0; unsigned volume_bytes_left_compressed; unsigned volume_bytes_left_expanded; CommonHeader common_header; #if VERBOSE >= 2 unshield_trace("Open volume %i", volume); #endif FCLOSE(reader->volume_file); reader->volume_file = unshield_fopen_for_reading(reader->unshield, volume, CABINET_SUFFIX); if (!reader->volume_file) { unshield_error("Failed to open input cabinet file %i", volume); goto exit; } { uint8_t tmp[COMMON_HEADER_SIZE]; uint8_t* p = tmp; if (COMMON_HEADER_SIZE != fread(&tmp, 1, COMMON_HEADER_SIZE, reader->volume_file)) goto exit; if (!unshield_read_common_header(&p, &common_header)) goto exit; } memset(&reader->volume_header, 0, sizeof(VolumeHeader)); switch (reader->unshield->header_list->major_version) { case 0: case 5: { uint8_t five_header[VOLUME_HEADER_SIZE_V5]; uint8_t* p = five_header; if (VOLUME_HEADER_SIZE_V5 != fread(&five_header, 1, VOLUME_HEADER_SIZE_V5, reader->volume_file)) goto exit; reader->volume_header.data_offset = READ_UINT32(p); p += 4; #if VERBOSE if (READ_UINT32(p)) unshield_trace("Unknown = %08x", READ_UINT32(p)); #endif /* unknown */ p += 4; reader->volume_header.first_file_index = READ_UINT32(p); p += 4; reader->volume_header.last_file_index = READ_UINT32(p); p += 4; reader->volume_header.first_file_offset = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_expanded = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_compressed = READ_UINT32(p); p += 4; reader->volume_header.last_file_offset = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_expanded = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_compressed = READ_UINT32(p); p += 4; if (reader->volume_header.last_file_offset == 0) reader->volume_header.last_file_offset = INT32_MAX; } break; case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: default: { uint8_t six_header[VOLUME_HEADER_SIZE_V6]; uint8_t* p = six_header; if (VOLUME_HEADER_SIZE_V6 != fread(&six_header, 1, VOLUME_HEADER_SIZE_V6, reader->volume_file)) goto exit; reader->volume_header.data_offset = READ_UINT32(p); p += 4; reader->volume_header.data_offset_high = READ_UINT32(p); p += 4; reader->volume_header.first_file_index = READ_UINT32(p); p += 4; reader->volume_header.last_file_index = READ_UINT32(p); p += 4; reader->volume_header.first_file_offset = READ_UINT32(p); p += 4; reader->volume_header.first_file_offset_high = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_expanded = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_expanded_high = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_compressed = READ_UINT32(p); p += 4; reader->volume_header.first_file_size_compressed_high = READ_UINT32(p); p += 4; reader->volume_header.last_file_offset = READ_UINT32(p); p += 4; reader->volume_header.last_file_offset_high = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_expanded = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_expanded_high = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_compressed = READ_UINT32(p); p += 4; reader->volume_header.last_file_size_compressed_high = READ_UINT32(p); p += 4; } break; } #if VERBOSE >= 2 unshield_trace("First file index = %i, last file index = %i", reader->volume_header.first_file_index, reader->volume_header.last_file_index); unshield_trace("First file offset = %08x, last file offset = %08x", reader->volume_header.first_file_offset, reader->volume_header.last_file_offset); #endif /* enable support for split archives for IS5 */ if (reader->unshield->header_list->major_version == 5) { if (reader->index < (reader->unshield->header_list->cab.file_count - 1) && reader->index == reader->volume_header.last_file_index && reader->volume_header.last_file_size_compressed != reader->file_descriptor->compressed_size) { unshield_trace("IS5 split file last in volume"); reader->file_descriptor->flags |= FILE_SPLIT; } else if (reader->index > 0 && reader->index == reader->volume_header.first_file_index && reader->volume_header.first_file_size_compressed != reader->file_descriptor->compressed_size) { unshield_trace("IS5 split file first in volume"); reader->file_descriptor->flags |= FILE_SPLIT; } } if (reader->file_descriptor->flags & FILE_SPLIT) { #if VERBOSE unshield_trace(/*"Total bytes left = 0x08%x, "*/"previous data offset = 0x08%x", /*total_bytes_left, */data_offset); #endif if (reader->index == reader->volume_header.last_file_index && reader->volume_header.last_file_offset != 0x7FFFFFFF) { /* can be first file too... */ #if VERBOSE unshield_trace("Index %i is last file in cabinet file %i", reader->index, volume); #endif data_offset = reader->volume_header.last_file_offset; volume_bytes_left_expanded = reader->volume_header.last_file_size_expanded; volume_bytes_left_compressed = reader->volume_header.last_file_size_compressed; } else if (reader->index == reader->volume_header.first_file_index) { #if VERBOSE unshield_trace("Index %i is first file in cabinet file %i", reader->index, volume); #endif data_offset = reader->volume_header.first_file_offset; volume_bytes_left_expanded = reader->volume_header.first_file_size_expanded; volume_bytes_left_compressed = reader->volume_header.first_file_size_compressed; } else { success = true; goto exit; } #if VERBOSE unshield_trace("Will read 0x%08x bytes from offset 0x%08x", volume_bytes_left_compressed, data_offset); #endif } else { data_offset = reader->file_descriptor->data_offset; volume_bytes_left_expanded = reader->file_descriptor->expanded_size; volume_bytes_left_compressed = reader->file_descriptor->compressed_size; } if (reader->file_descriptor->flags & FILE_COMPRESSED) reader->volume_bytes_left = volume_bytes_left_compressed; else reader->volume_bytes_left = volume_bytes_left_expanded; fseek(reader->volume_file, data_offset, SEEK_SET); reader->volume = volume; success = true; exit: return success; }/*}}}*/
static FileDescriptor* unshield_read_file_descriptor(Unshield* unshield, int index) { /* XXX: multi-volume support... */ Header* header = unshield->header_list; uint8_t* p = NULL; uint8_t* saved_p = NULL; FileDescriptor* fd = NEW1(FileDescriptor); switch (header->major_version) { case 0: case 5: saved_p = p = header->data + header->common.cab_descriptor_offset + header->cab.file_table_offset + header->file_table[header->cab.directory_count + index]; #if VERBOSE unshield_trace("File descriptor offset %i: %08x", index, p - header->data); #endif fd->volume = header->index; fd->name_offset = READ_UINT32(p); p += 4; fd->directory_index = READ_UINT32(p); p += 4; fd->flags = READ_UINT16(p); p += 2; fd->expanded_size = READ_UINT32(p); p += 4; fd->compressed_size = READ_UINT32(p); p += 4; p += 0x14; fd->data_offset = READ_UINT32(p); p += 4; #if VERBOSE >= 2 unshield_trace("Name offset: %08x", fd->name_offset); unshield_trace("Directory index: %08x", fd->directory_index); unshield_trace("Flags: %04x", fd->flags); unshield_trace("Expanded size: %08x", fd->expanded_size); unshield_trace("Compressed size: %08x", fd->compressed_size); unshield_trace("Data offset: %08x", fd->data_offset); #endif if (header->major_version == 5) { memcpy(fd->md5, p, 0x10); p += 0x10; assert((p - saved_p) == 0x3a); } break; case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: default: saved_p = p = header->data + header->common.cab_descriptor_offset + header->cab.file_table_offset + header->cab.file_table_offset2 + index * 0x57; #if VERBOSE unshield_trace("File descriptor offset: %08x", p - header->data); #endif fd->flags = READ_UINT16(p); p += 2; fd->expanded_size = READ_UINT32(p); p += 4; p += 4; fd->compressed_size = READ_UINT32(p); p += 4; p += 4; fd->data_offset = READ_UINT32(p); p += 4; p += 4; memcpy(fd->md5, p, 0x10); p += 0x10; p += 0x10; fd->name_offset = READ_UINT32(p); p += 4; fd->directory_index = READ_UINT16(p); p += 2; assert((p - saved_p) == 0x40); p += 0xc; fd->link_previous = READ_UINT32(p); p += 4; fd->link_next = READ_UINT32(p); p += 4; fd->link_flags = *p; p ++; #if VERBOSE if (fd->link_flags != LINK_NONE) { unshield_trace("Link: previous=%i, next=%i, flags=%i", fd->link_previous, fd->link_next, fd->link_flags); } #endif fd->volume = READ_UINT16(p); p += 2; assert((p - saved_p) == 0x57); break; } if (!(fd->flags & FILE_COMPRESSED) && fd->compressed_size != fd->expanded_size) { unshield_warning("File is not compressed but compressed size is %08x and expanded size is %08x", fd->compressed_size, fd->expanded_size); } return fd; }
FILE* unshield_fopen_for_reading(Unshield* unshield, int index, const char* suffix) { if (unshield && unshield->filename_pattern) { FILE* result = NULL; char filename[256]; char dirname[256]; char * p = strrchr(unshield->filename_pattern, '/'); const char *q; struct dirent *dent = NULL; DIR *sourcedir; snprintf(filename, sizeof(filename), unshield->filename_pattern, index, suffix); q=strrchr(filename,'/'); if (q) q++; else q=filename; if (p) { strncpy( dirname, unshield->filename_pattern,sizeof(dirname)); if ((unsigned int)(p-unshield->filename_pattern) > sizeof(dirname)) { unshield_trace("WARN: size\n"); dirname[sizeof(dirname)-1]=0; } else dirname[(p-unshield->filename_pattern)] = 0; } else strcpy(dirname,"."); sourcedir = opendir(dirname); /* Search for the File case independent */ if (sourcedir) { for (dent=readdir(sourcedir);dent;dent=readdir(sourcedir)) { if (!(strcasecmp(q, dent->d_name))) { /*unshield_trace("Found match %s\n",dent->d_name);*/ break; } } if (dent == NULL) { unshield_trace("File %s not found even case insensitive\n",filename); goto exit; } else snprintf(filename, sizeof(filename), "%s/%s", dirname, dent->d_name); } else unshield_trace("Could not open directory %s error %s\n", dirname, strerror(errno)); #if VERBOSE unshield_trace("Opening file '%s'", filename); #endif result = fopen(filename, "r"); exit: if (sourcedir) closedir(sourcedir); return result; } return NULL; }