/* * Extract component parts of MS CHM files * * Copyright (C) 2007-2008 Sourcefire, Inc. * * Authors: Trog * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include "fmap.h" #include "others.h" #include "mspack.h" #include "cltypes.h" #include "chmunpack.h" #include "cab.h" #ifndef HAVE_ATTRIB_PACKED #define __attribute__(x) #endif #ifdef HAVE_PRAGMA_PACK #pragma pack(1) #endif #ifdef HAVE_PRAGMA_PACK_HPPA #pragma pack 1 #endif #define CHM_CHUNK_HDR_LEN (0x14) #define CHM_CONTROL_LEN (0x18) typedef struct lzx_control_tag { uint32_t length __attribute__ ((packed)); unsigned char signature[4]; uint32_t version __attribute__ ((packed)); uint32_t reset_interval __attribute__ ((packed)); uint32_t window_size __attribute__ ((packed)); uint32_t cache_size __attribute__ ((packed)); } lzx_control_t; /* Don't need to include rt_offset in the strucuture len*/ #define CHM_RESET_TABLE_LEN (0x24) typedef struct lzx_reset_table_tag { uint32_t num_entries __attribute__ ((packed)); uint32_t entry_size __attribute__ ((packed)); uint32_t table_offset __attribute__ ((packed)); uint64_t uncom_len __attribute__ ((packed)); uint64_t com_len __attribute__ ((packed)); uint64_t frame_len __attribute__ ((packed)); off_t rt_offset __attribute__ ((packed)); } lzx_reset_table_t; typedef struct lzx_content_tag { uint64_t offset; uint64_t length; } lzx_content_t; #ifdef HAVE_PRAGMA_PACK #pragma pack() #endif #ifdef HAVE_PRAGMA_PACK_HPPA #pragma pack #endif #define CHM_SYS_CONTROL_NAME "::DataSpace/Storage/MSCompressed/ControlData" #define CHM_SYS_CONTENT_NAME "::DataSpace/Storage/MSCompressed/Content" #define CHM_SYS_RESETTABLE_NAME "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable" #define CHM_SYS_CONTROL_LEN 44 #define CHM_SYS_CONTENT_LEN 40 #define CHM_SYS_RESETTABLE_LEN 105 #define chm_endian_convert_16(x) le16_to_host(x) #define chm_endian_convert_32(x) le32_to_host(x) #define chm_endian_convert_64(x) le64_to_host(x) /* Read in a block of data from either the mmap area or the given fd */ static int chm_read_data(fmap_t *map, char *dest, off_t offset, off_t len) { void *src = fmap_need_off_once(map, offset, len); if(!src) return FALSE; memcpy(dest, src, len); return TRUE; } static uint64_t chm_copy_file_data(int ifd, int ofd, uint64_t len) { unsigned char data[8192]; uint64_t count, rem; unsigned int todo; rem = len; while (rem > 0) { todo = MIN(8192, rem); count = cli_readn(ifd, data, todo); if (count != todo) { return len-rem; } if (cli_writen(ofd, data, count) != (int64_t)count) { return len-rem-count; } rem -= count; } return len; } static void itsf_print_header(chm_itsf_header_t *itsf_hdr) { if (!itsf_hdr) { return; } cli_dbgmsg("---- ITSF ----\n"); cli_dbgmsg("Signature:\t%c%c%c%c\n", itsf_hdr->signature[0], itsf_hdr->signature[1],itsf_hdr->signature[2],itsf_hdr->signature[3]); cli_dbgmsg("Version:\t%d\n", itsf_hdr->version); cli_dbgmsg("Header len:\t%d\n", itsf_hdr->header_len); cli_dbgmsg("Lang ID:\t%d\n", itsf_hdr->lang_id); cli_dbgmsg("Sec0 offset:\t%lu\n", (unsigned long int) itsf_hdr->sec0_offset); cli_dbgmsg("Sec0 len:\t%lu\n", (unsigned long int) itsf_hdr->sec0_len); cli_dbgmsg("Dir offset:\t%lu\n", (unsigned long int) itsf_hdr->dir_offset); cli_dbgmsg("Dir len:\t%lu\n", (unsigned long int) itsf_hdr->dir_len); if (itsf_hdr->version > 2) { cli_dbgmsg("Data offset:\t%lu\n\n", (unsigned long int) itsf_hdr->data_offset); } } static int itsf_read_header(chm_metadata_t *metadata) { chm_itsf_header_t *itsf_hdr = &metadata->itsf_hdr; if (!chm_read_data(metadata->map, (char *)itsf_hdr, 0, CHM_ITSF_MIN_LEN)) return FALSE; if (memcmp(itsf_hdr->signature, "ITSF", 4) != 0) { cli_dbgmsg("ITSF signature mismatch\n"); return FALSE; } itsf_hdr->version = chm_endian_convert_32(itsf_hdr->version); itsf_hdr->header_len = chm_endian_convert_32(itsf_hdr->header_len); itsf_hdr->last_modified = chm_endian_convert_32(itsf_hdr->last_modified); itsf_hdr->lang_id = chm_endian_convert_32(itsf_hdr->lang_id); itsf_hdr->sec0_offset = chm_endian_convert_64(itsf_hdr->sec0_offset); itsf_hdr->sec0_len = chm_endian_convert_64(itsf_hdr->sec0_len); itsf_hdr->dir_offset = chm_endian_convert_64(itsf_hdr->dir_offset); itsf_hdr->dir_len = chm_endian_convert_64(itsf_hdr->dir_len); if (itsf_hdr->version > 2) { itsf_hdr->data_offset = chm_endian_convert_64(itsf_hdr->data_offset); } return TRUE; } static void itsp_print_header(chm_itsp_header_t *itsp_hdr) { if (!itsp_hdr) { return; } cli_dbgmsg("---- ITSP ----\n"); cli_dbgmsg("Signature:\t%c%c%c%c\n", itsp_hdr->signature[0], itsp_hdr->signature[1],itsp_hdr->signature[2],itsp_hdr->signature[3]); cli_dbgmsg("Version:\t%d\n", itsp_hdr->version); cli_dbgmsg("Block len:\t%u\n", itsp_hdr->block_len); cli_dbgmsg("Block idx int:\t%d\n", itsp_hdr->blockidx_intvl); cli_dbgmsg("Index depth:\t%d\n", itsp_hdr->index_depth); cli_dbgmsg("Index root:\t%d\n", itsp_hdr->index_root); cli_dbgmsg("Index head:\t%u\n", itsp_hdr->index_head); cli_dbgmsg("Index tail:\t%u\n", itsp_hdr->index_tail); cli_dbgmsg("Num Blocks:\t%u\n", itsp_hdr->num_blocks); cli_dbgmsg("Lang ID:\t%u\n\n", itsp_hdr->lang_id); } static int itsp_read_header(chm_metadata_t *metadata, off_t offset) { chm_itsp_header_t *itsp_hdr = &metadata->itsp_hdr; if (!chm_read_data(metadata->map, (char *)itsp_hdr, offset, CHM_ITSP_LEN)) return FALSE; if (memcmp(itsp_hdr->signature, "ITSP", 4) != 0) { cli_dbgmsg("ITSP signature mismatch\n"); return FALSE; } itsp_hdr->version = chm_endian_convert_32(itsp_hdr->version); itsp_hdr->header_len = chm_endian_convert_32(itsp_hdr->header_len); itsp_hdr->block_len = chm_endian_convert_32(itsp_hdr->block_len); itsp_hdr->blockidx_intvl = chm_endian_convert_32(itsp_hdr->blockidx_intvl); itsp_hdr->index_depth = chm_endian_convert_32(itsp_hdr->index_depth); itsp_hdr->index_root = chm_endian_convert_32(itsp_hdr->index_root); itsp_hdr->index_head = chm_endian_convert_32(itsp_hdr->index_head); itsp_hdr->index_tail = chm_endian_convert_32(itsp_hdr->index_tail); itsp_hdr->num_blocks = chm_endian_convert_32(itsp_hdr->num_blocks); itsp_hdr->lang_id = chm_endian_convert_32(itsp_hdr->lang_id); if ((itsp_hdr->version != 1) || (itsp_hdr->header_len != CHM_ITSP_LEN)) { cli_dbgmsg("ITSP header mismatch\n"); return FALSE; } return TRUE; } static uint64_t read_enc_int(char **start, char *end) { uint64_t retval=0; char *current; current = *start; if (current > end) { return 0; } do { if (current > end) { return 0; } retval = (retval << 7) | (*current & 0x7f); } while (*current++ & 0x80); *start = current; return retval; } /* Read control entries */ static int read_control_entries(chm_metadata_t *metadata) { char *name; uint64_t name_len, section, offset, length; while (metadata->chunk_entries--) { if (metadata->chunk_current > metadata->chunk_end) { cli_dbgmsg("read chunk entries failed\n"); return FALSE; } name_len = read_enc_int(&metadata->chunk_current, metadata->chunk_end); if (((metadata->chunk_current + name_len) > metadata->chunk_end) || ((metadata->chunk_current + name_len) < metadata->chunk_data)) { cli_dbgmsg("Bad CHM name_len detected\n"); return FALSE; } name = metadata->chunk_current; metadata->chunk_current += name_len; section = read_enc_int(&metadata->chunk_current, metadata->chunk_end); offset = read_enc_int(&metadata->chunk_current, metadata->chunk_end); length = read_enc_int(&metadata->chunk_current, metadata->chunk_end); /* CHM_SYS_CONTENT_LEN is the shortest name we are searching for */ if ((name_len >= CHM_SYS_CONTENT_LEN) && (name[0] == ':') && (name[1] == ':')) { if ((name_len == CHM_SYS_CONTROL_LEN) && (strcmp(name, CHM_SYS_CONTROL_NAME) == 0)) { cli_dbgmsg("found CHM_SYS_CONTROL_NAME\n"); metadata->sys_control.offset = offset; metadata->sys_control.length = length; } else if ((name_len == CHM_SYS_CONTENT_LEN) && (strcmp(name, CHM_SYS_CONTENT_NAME) == 0)) { cli_dbgmsg("found CHM_SYS_CONTENT_NAME\n"); metadata->sys_content.offset = offset; metadata->sys_content.length = length; } else if ((name_len == CHM_SYS_RESETTABLE_LEN) && (strcmp(name, CHM_SYS_RESETTABLE_NAME) == 0)) { cli_dbgmsg("found CHM_SYS_RESETTABLE_NAME\n"); metadata->sys_reset.offset = offset; metadata->sys_reset.length = length; } } } return TRUE; } static int prepare_file(chm_metadata_t *metadata) { uint64_t name_len, section; while (metadata->chunk_entries != 0) { if (metadata->chunk_current >= metadata->chunk_end) { return CL_EFORMAT; } name_len = read_enc_int(&metadata->chunk_current, metadata->chunk_end); if (((metadata->chunk_current + name_len) >= metadata->chunk_end) || ((metadata->chunk_current + name_len) < metadata->chunk_data)) { cli_dbgmsg("Bad CHM name_len detected\n"); return CL_EFORMAT; } metadata->chunk_current += name_len; section = read_enc_int(&metadata->chunk_current, metadata->chunk_end); metadata->file_offset = read_enc_int(&metadata->chunk_current, metadata->chunk_end); metadata->file_length = read_enc_int(&metadata->chunk_current, metadata->chunk_end); metadata->chunk_entries--; if (section == 1) { return CL_SUCCESS; } } return CL_BREAK; } static int read_chunk(chm_metadata_t *metadata) { cli_dbgmsg("in read_chunk\n"); if (metadata->itsp_hdr.block_len < 8 || metadata->itsp_hdr.block_len > 33554432) { return CL_EFORMAT; } if (metadata->chunk_offset > metadata->m_length) { return CL_EFORMAT; } if ((metadata->chunk_offset + metadata->itsp_hdr.block_len) > metadata->m_length) { return CL_EFORMAT; } metadata->chunk_data = fmap_need_off_once(metadata->map, metadata->chunk_offset, metadata->itsp_hdr.block_len); if(!metadata->chunk_data) return CL_EFORMAT; metadata->chunk_current = metadata->chunk_data + CHM_CHUNK_HDR_LEN; metadata->chunk_end = metadata->chunk_data + metadata->itsp_hdr.block_len; if (memcmp(metadata->chunk_data, "PMGL", 4) == 0) { metadata->chunk_entries = (uint16_t)((((uint8_t const *)(metadata->chunk_data))[metadata->itsp_hdr.block_len-2] << 0) | (((uint8_t const *)(metadata->chunk_data))[metadata->itsp_hdr.block_len-1] << 8)); } else if (memcmp(metadata->chunk_data, "PMGI", 4) != 0) { return CL_BREAK; } return CL_SUCCESS; } static void print_sys_control(lzx_control_t *lzx_control) { if (!lzx_control) { return; } cli_dbgmsg("---- Control ----\n"); cli_dbgmsg("Length:\t\t%u\n", lzx_control->length); cli_dbgmsg("Signature:\t%c%c%c%c\n", lzx_control->signature[0], lzx_control->signature[1],lzx_control->signature[2],lzx_control->signature[3]); cli_dbgmsg("Version:\t%d\n", lzx_control->version); cli_dbgmsg("Reset Interval:\t%d\n", lzx_control->reset_interval); cli_dbgmsg("Window Size:\t%d\n", lzx_control->window_size); cli_dbgmsg("Cache Size:\t%d\n\n", lzx_control->cache_size); } static int read_sys_control(chm_metadata_t *metadata, lzx_control_t *lzx_control) { off_t offset; if (metadata->sys_control.length != 28) { return FALSE; } offset = metadata->itsf_hdr.data_offset + metadata->sys_control.offset; if (offset < 0) { return FALSE; } if (!chm_read_data(metadata->map, (char *) lzx_control, offset, CHM_CONTROL_LEN)) { return FALSE; } lzx_control->length = chm_endian_convert_32(lzx_control->length); lzx_control->version = chm_endian_convert_32(lzx_control->version); lzx_control->reset_interval = chm_endian_convert_32(lzx_control->reset_interval); lzx_control->window_size = chm_endian_convert_32(lzx_control->window_size); lzx_control->cache_size = chm_endian_convert_32(lzx_control->cache_size); if (strncmp((const char *) "LZXC", (const char *) lzx_control->signature, 4) != 0) { cli_dbgmsg("bad sys_control signature\n"); return FALSE; } switch(lzx_control->version) { case 1: break; case 2: lzx_control->reset_interval *= LZX_FRAME_SIZE; lzx_control->window_size *= LZX_FRAME_SIZE; break; default: cli_dbgmsg("Unknown sys_control version:%d\n", lzx_control->version); return FALSE; } print_sys_control(lzx_control); return TRUE; } static void print_sys_content(lzx_content_t *lzx_content) { if (!lzx_content) { return; } cli_dbgmsg("---- Content ----\n"); cli_dbgmsg("Offset:\t%lu\n", (unsigned long int) lzx_content->offset); cli_dbgmsg("Length:\t%lu\n\n", (unsigned long int) lzx_content->length); } static int read_sys_content(chm_metadata_t *metadata, lzx_content_t *lzx_content) { lzx_content->offset = metadata->itsf_hdr.data_offset + metadata->sys_content.offset; lzx_content->length = metadata->sys_content.length; print_sys_content(lzx_content); return TRUE; } static void print_sys_reset_table(lzx_reset_table_t *lzx_reset_table) { if (!lzx_reset_table) { return; } cli_dbgmsg("---- Reset Table ----\n"); cli_dbgmsg("Num Entries:\t%u\n", lzx_reset_table->num_entries); cli_dbgmsg("Entry Size:\t%u\n", lzx_reset_table->entry_size); cli_dbgmsg("Table Offset:\t%u\n", lzx_reset_table->table_offset); cli_dbgmsg("Uncom Len:\t%lu\n", (unsigned long int) lzx_reset_table->uncom_len); cli_dbgmsg("Com Len:\t%lu\n", (unsigned long int) lzx_reset_table->com_len); cli_dbgmsg("Frame Len:\t%lu\n\n", (unsigned long int) lzx_reset_table->frame_len); } static int read_sys_reset_table(chm_metadata_t *metadata, lzx_reset_table_t *lzx_reset_table) { off_t offset; if (metadata->sys_reset.length < 40) { return FALSE; } /* Skip past unknown entry in offset calc */ offset = metadata->itsf_hdr.data_offset + metadata->sys_reset.offset + 4; if (offset < 0) { return FALSE; } /* Save the entry offset for later use */ lzx_reset_table->rt_offset = offset-4; if (!chm_read_data(metadata->map, (char *) lzx_reset_table, offset, CHM_RESET_TABLE_LEN)) { return FALSE; } lzx_reset_table->num_entries = chm_endian_convert_32(lzx_reset_table->num_entries); lzx_reset_table->entry_size = chm_endian_convert_32(lzx_reset_table->entry_size); lzx_reset_table->table_offset = chm_endian_convert_32(lzx_reset_table->table_offset); lzx_reset_table->uncom_len = chm_endian_convert_64(lzx_reset_table->uncom_len); lzx_reset_table->com_len = chm_endian_convert_64(lzx_reset_table->com_len); lzx_reset_table->frame_len = chm_endian_convert_64(lzx_reset_table->frame_len); if (lzx_reset_table->frame_len != LZX_FRAME_SIZE) { cli_dbgmsg("bad sys_reset_table frame_len: 0x%lx\n", (long unsigned int) lzx_reset_table->frame_len); return FALSE; } if ((lzx_reset_table->entry_size != 4) && (lzx_reset_table->entry_size != 8)) { cli_dbgmsg("bad sys_reset_table entry_size: 0x%x\n",lzx_reset_table->entry_size); return FALSE; } print_sys_reset_table(lzx_reset_table); return TRUE; } /* *****************************************************************/ /* This section interfaces to the mspack files. As such, this is a */ /* little bit dirty compared to my usual code */ static int chm_decompress_stream(int fd, chm_metadata_t *metadata, const char *dirname, cli_ctx *ctx) { lzx_content_t lzx_content; lzx_reset_table_t lzx_reset_table; lzx_control_t lzx_control; int window_bits, length, tmpfd, retval=-1; struct lzx_stream * stream; char filename[1024]; struct cab_file file; snprintf(filename, 1024, "%s"PATHSEP"clamav-unchm.bin", dirname); tmpfd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU); if (tmpfd<0) { cli_dbgmsg("open failed for %s\n", filename); return -1; } if (!metadata->sys_control.length || !metadata->sys_content.length ||!metadata->sys_reset.length) { cli_dbgmsg("Control file missing\n"); goto abort; } if (!read_sys_control(metadata, &lzx_control)) { goto abort; } if (!read_sys_content(metadata, &lzx_content)) { goto abort; } if (!read_sys_reset_table(metadata, &lzx_reset_table)) { goto abort; } switch (lzx_control.window_size) { case 0x008000: window_bits = 15; break; case 0x010000: window_bits = 16; break; case 0x020000: window_bits = 17; break; case 0x040000: window_bits = 18; break; case 0x080000: window_bits = 19; break; case 0x100000: window_bits = 20; break; case 0x200000: window_bits = 21; break; default: cli_dbgmsg("bad control window size: 0x%x\n", lzx_control.window_size); goto abort; } if (lzx_control.reset_interval % LZX_FRAME_SIZE) { cli_dbgmsg("bad reset_interval: 0x%x\n", lzx_control.window_size); goto abort; } length = lzx_reset_table.uncom_len; length += lzx_control.reset_interval; length &= -lzx_control.reset_interval; cli_dbgmsg("Compressed offset: %lu\n", (unsigned long int) lzx_content.offset); if ((uint64_t) lseek(fd, lzx_content.offset, SEEK_SET) != lzx_content.offset) { goto abort; } memset(&file, 0, sizeof(struct cab_file)); file.max_size = ctx->engine->maxfilesize; stream = lzx_init(fd, tmpfd, window_bits, lzx_control.reset_interval / LZX_FRAME_SIZE, 4096, length, &file, NULL); if (!stream) { cli_dbgmsg("lzx_init failed\n"); goto abort; } lzx_decompress(stream, length); lzx_free(stream); #ifndef _WIN32 /* Delete the file */ if(cli_unlink(filename)) retval = -1; else #endif retval = tmpfd; abort: if ((retval == -1) && (tmpfd >= 0)) { close(tmpfd); } return retval; } /* ************ End dirty section ********************/ static int chm_init_metadata(chm_metadata_t *metadata) { if (!metadata) { return CL_ENULLARG; } metadata->sys_control.length = metadata->sys_content.length = metadata->sys_reset.length = 0; metadata->map = NULL; metadata->ufd = -1; metadata->num_chunks = metadata->chunk_entries = 0; metadata->chunk_data = NULL; return CL_SUCCESS; } void cli_chm_close(chm_metadata_t *metadata) { if (metadata->ufd >= 0) { close(metadata->ufd); } funmap(metadata->map); } int cli_chm_extract_file(char *dirname, chm_metadata_t *metadata, cli_ctx *ctx) { char filename[1024]; uint64_t len; cli_dbgmsg("in cli_chm_extract_file\n"); if (lseek(metadata->ufd, metadata->file_offset, SEEK_SET) != (off_t) metadata->file_offset) { cli_dbgmsg("seek in uncompressed stream failed\n"); return CL_EFORMAT; } snprintf(filename, 1024, "%s"PATHSEP"%lu.chm", dirname, (unsigned long int) metadata->file_offset); metadata->ofd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRWXU); if (metadata->ofd < 0) { return CL_ECREAT; } len = ctx->engine->maxfilesize ? (MIN(ctx->engine->maxfilesize, metadata->file_length)) : metadata->file_length; if (chm_copy_file_data(metadata->ufd, metadata->ofd, len) != len) { cli_dbgmsg("failed to copy %lu bytes\n", (unsigned long int) len); close(metadata->ofd); return CL_EFORMAT; /* most likely a corrupted file */ } return CL_SUCCESS; } int cli_chm_prepare_file(chm_metadata_t *metadata) { int retval; cli_dbgmsg("in cli_chm_prepare_file\n"); do { if (metadata->chunk_entries == 0) { if (metadata->num_chunks == 0) { return CL_BREAK; } if ((retval = read_chunk(metadata)) != CL_SUCCESS) { return retval; } metadata->num_chunks--; metadata->chunk_offset += metadata->itsp_hdr.block_len; } retval = prepare_file(metadata); } while (retval == CL_BREAK); /* Ran out of chunk entries before finding a file */ return retval; } int cli_chm_open(int fd, const char *dirname, chm_metadata_t *metadata, cli_ctx *ctx) { struct stat statbuf; int retval; cli_dbgmsg("in cli_chm_open\n"); if ((retval = chm_init_metadata(metadata)) != CL_SUCCESS) { return retval; } if (fstat(fd, &statbuf) == 0) { if (statbuf.st_size < CHM_ITSF_MIN_LEN) { return CL_ESTAT; } metadata->m_length = statbuf.st_size; metadata->map = fmap(fd, 0, metadata->m_length); if (!metadata->map) { return CL_EMAP; } } if (!itsf_read_header(metadata)) { goto abort; } itsf_print_header(&metadata->itsf_hdr); if (!itsp_read_header(metadata, metadata->itsf_hdr.dir_offset)) { goto abort; } itsp_print_header(&metadata->itsp_hdr); metadata->chunk_offset = metadata->itsf_hdr.dir_offset+CHM_ITSP_LEN; /* TODO: need to check this first calculation, currently have no files of this type */ if (metadata->itsp_hdr.index_head > 0) { metadata->chunk_offset += metadata->itsp_hdr.index_head * metadata->itsp_hdr.block_len; } metadata->num_chunks = metadata->itsp_hdr.index_tail - metadata->itsp_hdr.index_head + 1; /* Versions before 3 didn't have a data_offset */ /* TODO: need to check this calculation, currently have no files of this type */ if (metadata->itsf_hdr.version < 3) { metadata->itsf_hdr.data_offset = metadata->itsf_hdr.dir_offset + CHM_ITSP_LEN + (metadata->itsp_hdr.block_len*metadata->itsp_hdr.num_blocks); } while (metadata->num_chunks) { if (read_chunk(metadata) != CL_SUCCESS) { cli_dbgmsg("read_chunk failed\n"); goto abort; } if (read_control_entries(metadata) == FALSE) { goto abort; } metadata->num_chunks--; metadata->chunk_offset += metadata->itsp_hdr.block_len; } if (!metadata->sys_content.length || !metadata->sys_control.length || !metadata->sys_reset.length) { cli_dbgmsg("sys file missing\n"); goto abort; } metadata->ufd = chm_decompress_stream(fd, metadata, dirname, ctx); if (metadata->ufd == -1) { goto abort; } metadata->chunk_entries = 0; metadata->chunk_data = NULL; metadata->chunk_offset = metadata->itsf_hdr.dir_offset+CHM_ITSP_LEN; metadata->num_chunks = metadata->itsp_hdr.index_tail - metadata->itsp_hdr.index_head + 1; return CL_SUCCESS; abort: funmap(metadata->map); return CL_EFORMAT; }