/*--[litsections.c]------------------------------------------------------------ | Copyright (C) 2002 Dan A. Jackson | | This file is part of the "openclit" library for processing .LIT files. | | "Openclit" 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. | | The GNU General Public License may also be available at the following | URL: http://www.gnu.org/licenses/gpl.html */ /* This file contains the code to read and manage "sections" | | Sections are second-level files-within-a-file-within-a-file structures, | so that multiple internal files can be packed together to acheive better | compression. */ #include #include #include #include "litlib.h" #include "litinternal.h" #include "lzx.h" static int decompress_section(lit_file * litfile, char * section_name, U8 * pControl, int sizeControl, U8 * pContent, int sizeContent, U8 ** ppUncompressed, int * psizeUncompressed); static const char * guid2string(U8 * guid); /* Constant names */ const char * content_tail = "/Content"; const char * control_tail = "/ControlData"; const char * namelist_string = "::DataSpace/NameList"; const char * storage_string = "::DataSpace/Storage/"; const char * transform_string = "/Transform/List"; const char * rt_tail = "/InstanceData/ResetTable"; const char * desencrypt_guid = "{67F6E4A2-60BF-11D3-8540-00C04F58C3CF}"; const char * lzxcompress_guid= "{0A9007C6-4076-11D3-8789-0000F8105754}"; const U32 LZXC_TAG = 0x43585a4c; /*--[guid2string]-------------------------------------------------------------- | | Careful - uses a static string. Will be awkward if called the same | time in a statement. | Assumes GUID is in Little Endian Order | */ const char * guid2string(U8 * guid) { static char guid_buffer[7+8+4+4+4+12+1]; sprintf(guid_buffer, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", (U32)READ_U32(guid), (U16)READ_U16((guid+4)), (U16)READ_U16((guid+6)), (U8)*(guid+8), (U8)*(guid+9), /*-*/ (U8)*(guid+10), (U8)*(guid+11), (U8)*(guid+12), (U8)*(guid+13), (U8)*(guid+14), (U8)*(guid+15)); return guid_buffer; } /***************************************************************************** Encryption ControlData +-------------------------------+--------------------------------+ | Number of dwords following (3)| 0x1729 (Unknown, Tag?) | +-------------------------------+--------------------------------+ | 1 (Unknown, Constant) | 0xA5A5 (Unknown, Constant) | +-------------------------------+--------------------------------+ Compression ControlData +-------------------------------+--------------------------------+ | Number of dwords (always 7) | 'LZXC' Tag | +-------------------------------+--------------------------------+ | 3 (Unknown, Constant) | Encoded Window size (15 - 21) | +-------------------------------+--------------------------------+ | Same as encoded window size | 0x02 (Unknown - Always constant| | See CHMFORMAT for speculation.| | +-------------------------------+--------------------------------+ | 0 (Unknown) | +-------------------------------+--------------------------------+ Reset Table +-------------------------------+--------------------------------+ | Version (3) | Number of reset table entries | +-------------------------------+--------------------------------+ | Unknown (always 8) | Header length (0x28) | +-------------------------------+--------------------------------+ | Uncompressed length | +-------------------------------+--------------------------------+ | Compressed length (or close) | +-------------------------------+--------------------------------+ | Reset Interval | Padding | +-------------------------------+--------------------------------+ | Reset location (0) | +----------------------------------------------------------------+ ... Successive reset locations follow .... ****************************************************************************/ #define LZXC_TAG 0x43585a4c #define CONTROL_TAG 4 #define CONTROL_WINDOW_SIZE 12 #define RESET_NENTRIES 4 #define RESET_HDRLEN 12 #define RESET_UCLENGTH 16 #define RESET_INTERVAL 32 /*--[lit_i_cache_section]------------------------------------------------------ | | Here is the code to actually read a section from a .LIT file -- this is | fairly ugly, because the section is usually transformed (read encrypted | or compressed). | */ int lit_i_cache_section(lit_file * litfile, section_type * pSection ) { char * path = NULL; int status; const char *guid; int idxTransform, idxControl, nbytes; U8 * pList = NULL, * pContent = NULL, *ptr = NULL; U8 * pControl; int sizeTransform, sizeContent, sizeControl, nDwords; path = lit_i_strmerge(storage_string,pSection->name,transform_string,0); if (!path) { return E_LIT_OUT_OF_MEMORY; } status = lit_get_file(litfile, path, &pList,&sizeTransform); if (status) goto bad; free(path); path = NULL; path = lit_i_strmerge(storage_string,pSection->name, content_tail,0); if (!path) { return E_LIT_OUT_OF_MEMORY; } status = lit_get_file(litfile, path, &pContent,&sizeContent); if (status) goto bad; free(path); path = NULL; path = lit_i_strmerge(storage_string,pSection->name, control_tail,0); if (!path) { return E_LIT_OUT_OF_MEMORY; } status = lit_get_file(litfile, path, &pControl,&sizeControl); if (status) goto bad; free(path); path = NULL; idxTransform = 0; idxControl = 0; while ((sizeTransform - idxTransform) >= 16) { nDwords = READ_INT32(pControl + idxControl) + 1; if (((idxControl + nDwords*4) > sizeControl) || (nDwords <= 0)) { lit_error(ERR_R,"ControlData is too short! (%d > %d), %d.\n", idxControl + nDwords*4, sizeControl, nDwords); status = E_LIT_FORMAT_ERROR; goto bad; } guid = guid2string(pList + idxTransform); if (strcmp(guid, desencrypt_guid) == 0) { status = lit_i_decrypt(litfile,pContent, sizeContent); if (status) goto bad; idxControl += (nDwords * 4); } else if (strcmp(guid, lzxcompress_guid) == 0) { status = decompress_section(litfile, pSection->name, pControl+idxControl, sizeControl-idxControl, pContent, sizeContent, &ptr, &nbytes); if (status) goto bad; free(pContent); pContent = ptr; ptr = NULL; sizeContent = nbytes; idxControl += (nDwords * 4); } else { lit_error(ERR_R,"Unrecognized transform: \"%s\".", guid); status = E_LIT_UNSUPPORTED; goto bad; } idxTransform += 16; } pSection->data_pointer = pContent; pSection->size = sizeContent; if (pControl) free(pControl); if (path) free(path); if (pList) free(pList); return 0; bad: if (ptr) free(ptr); if (path) free(path); if (pList) free(pList); if (pContent) free(pContent); if (pControl) free(pControl); return status; } /*--[lit_i_encrypt_section]---------------------------------------------------- | | Encrypts (or re-encrypts) a particular section. | This only works if the section already has an encryption transform in place. | */ int lit_i_encrypt_section(lit_file * litfile, char * name, U8 * new_key) { char * path = NULL; int status; const char *guid; U8 * pList = NULL, * pContent = NULL, *ptr = NULL; int sizeTransform, sizeContent, idxTransform; path = lit_i_strmerge(storage_string, name, transform_string, 0); if (!path) { return E_LIT_OUT_OF_MEMORY; } status = lit_get_file(litfile, path, &pList,&sizeTransform); if (status) goto bad; free(path); path = NULL; path = lit_i_strmerge(storage_string, name, content_tail, 0); status = lit_get_file(litfile, path, &pContent,&sizeContent); if (status) goto bad; idxTransform = 0; while ((sizeTransform - idxTransform) >= 16) { guid = guid2string(pList + idxTransform); if (strcmp(guid, desencrypt_guid) == 0) { ptr = malloc(sizeContent); if (!ptr) { lit_error(ERR_W,"Unable to make copy of section data " " for \"%s\"!", name); return E_LIT_OUT_OF_MEMORY; } memcpy(ptr, pContent, sizeContent); status = lit_i_decrypt(litfile,ptr, sizeContent); if (status) goto bad; status = lit_put_file(litfile,path,ptr,sizeContent,0); if (status) goto bad; lit_i_encrypt(ptr,sizeContent,new_key); free(pContent); pContent = NULL; } idxTransform += 16; } if (path) free(path); if (pList) free(pList); return 0; bad: if (ptr) free(ptr); if (path) free(path); if (pList) free(pList); if (pContent) free(pContent); return status; } /*--[decompress_section]------------------------------------------------------- | | A somewhat tricky routine as it handles interfacing with the external | LZX library. There is a reason for using the ResetTable here, as | otherwise certain files will not be correctly handled! | | Generally -- | I need to get the LZXC control information to get the window size. | This lets me initialize the LZX library. | | From there, I read the ResetTable to know the uncompressed length and | each "LZXReset()" point. | | Note that the LZXReset() that this routine requires is not part of the | standard lzx distribution. The version that works with these files is from | the chmlib implementation and already has been modified. */ int decompress_section(lit_file * litfile, char * section_name, U8 * pControl, int sizeControl, U8 * pContent, int sizeContent, U8 ** ppUncompressed, int * psizeUncompressed) { char *path; int sizeRT, ofsEntry, base, dst, u; int bytesRemaining, uclength, window_bytes, accum, size; U8 * ptr, * pRT; int window_size, status; if ((sizeControl < 32) || (READ_U32(pControl+CONTROL_TAG) != LZXC_TAG)) { lit_error(ERR_R, "Invalid ControlData tag value %08lx should be %08lx!", (sizeControl > 8)?READ_U32(pControl+CONTROL_TAG):0, LZXC_TAG); return E_LIT_FORMAT_ERROR; } window_size = 14; u = READ_U32(pControl + CONTROL_WINDOW_SIZE); while (u) { u >>= 1; window_size++; } if ((window_size < 15) || (window_size > 21)) { lit_error(ERR_R, "Invalid window in ControlData - %d from %lx.", window_size, READ_U32(pControl+CONTROL_WINDOW_SIZE)); return -1; } status = LZXinit(window_size); if (status) { lit_error(ERR_R, "LZXinit(%d) failed, status = %d.", window_size, status); return E_LIT_LZX_ERROR; } path = lit_i_strmerge(storage_string,section_name,"/Transform/", lzxcompress_guid, rt_tail, NULL); if (!path) { return E_LIT_OUT_OF_MEMORY; } status = lit_get_file(litfile, path, &pRT, &sizeRT); if (status) { free(path); return status;} free(path); path = NULL; if (sizeRT < (RESET_INTERVAL+8)) { lit_error(ERR_R, "Reset table is too short (%d bytes).", sizeRT); free(pRT); return E_LIT_FORMAT_ERROR; } if (READ_U32(pRT + RESET_UCLENGTH + 4)) { lit_error(ERR_R,"Reset table has 64bit value for UCLENGTH!"); free(pRT); return E_LIT_64BIT_VALUE; } /* Skip first entry -- always 0! */ ofsEntry = READ_INT32(pRT + RESET_HDRLEN) + 8; uclength = READ_INT32(pRT + RESET_UCLENGTH); accum = READ_INT32(pRT + RESET_INTERVAL); ptr = malloc(uclength+1); /* Check for corruption */ ptr[uclength] = 0xCC; if (!ptr) { lit_error(ERR_R, "Unable to malloc uc length (%d bytes)!", uclength); free(pRT); return E_LIT_OUT_OF_MEMORY; } bytesRemaining = uclength; window_bytes = (1 << window_size); base = 0; dst = 0; while (ofsEntry < sizeRT) { if (accum == window_bytes) { accum = 0; size = READ_INT32(pRT + ofsEntry); u = READ_INT32(pRT + ofsEntry + 4); if (u) { lit_error(ERR_R, "Reset table entry greater than 32 bits!"); free(pRT); free(ptr); return E_LIT_64BIT_VALUE; } if (size >= sizeContent) { lit_error(ERR_R, "ResetTable entry out of bounds, %lx. (%d)", size, ofsEntry); free(ptr); free(pRT); return E_LIT_FORMAT_ERROR; } status = 0; if (bytesRemaining >= window_bytes) { LZXreset(); status = LZXdecompress(pContent + base, ptr+dst, size - base, window_bytes); bytesRemaining -= window_bytes; dst += window_bytes; base = size; } if (status) { lit_error(ERR_R, "LZXdecompress failed, status = %d.", status); free(ptr); free(pRT); return -1; } } accum += READ_INT32(pRT + RESET_INTERVAL); ofsEntry += 8; } free(pRT); if (bytesRemaining < window_bytes) { LZXreset(); status = LZXdecompress(pContent + base, ptr + dst, sizeContent - base, bytesRemaining); bytesRemaining = 0; } if (ptr[uclength] != 0xCC) { lit_error(ERR_R, "LZXdecompress overflowed memory. (%02x). \n"\ "This is a serious bug, please report this.\n", ptr[uclength]); /* Can't really free, anything may be corrupted at this point */ return -1; } if (status) { lit_error(ERR_R, "LZXdecompress failed, status = %d.", status); free(ptr); return E_LIT_LZX_ERROR; } if (bytesRemaining) { lit_error(ERR_R, "Failed to completely decompress section! (%d left).", bytesRemaining); free(ptr); return E_LIT_LZX_ERROR; } if (ppUncompressed) *ppUncompressed = ptr; else free(ptr); if (psizeUncompressed) *psizeUncompressed = uclength; return 0; } /*--[lit_i_read_sections]------------------------------------------------------ | | This routine reads the directory of "sections" from the | "::DataSpace/NameList" file, and stores them for later. The real work | of reading a section occurs in the "cache_section" routine. | | NameList format: | U16 NUMBER OF SECTIONS | U16 Length of section name 0 | U16s (UTF16) section name | U16 Length of section name 1, and so forth */ #define MAX_SECTION_NAME 128 int lit_i_read_sections(lit_file * litfile ) { U8 *pNamelist; int status; int nbytes, num_sections, size, section, idx; status = lit_get_file(litfile, namelist_string, &pNamelist,&nbytes); if (status) return status; idx = 2; size = 2; if ((idx + size) > nbytes) goto bad; num_sections = READ_U16(pNamelist+idx); idx += 2; litfile->sections = malloc(num_sections * sizeof(section_type)); if (!litfile->sections) { lit_error(ERR_R|ERR_LIBC,"malloc(%d) failed!\n", num_sections*sizeof(section_type)); free(pNamelist); return E_LIT_OUT_OF_MEMORY; } memset(litfile->sections, 0, num_sections * sizeof(section_type)); litfile->num_sections = num_sections; for (section = 0; section < num_sections; section++) { int j; size = 2; if ((idx + size) > nbytes) goto bad; size = READ_U16(pNamelist + idx); idx += 2; if (size > MAX_SECTION_NAME) { lit_error(ERR_R, "litlib.c cannot handle a %d byte section name, fix MAX_SECTION_NAME (%d)\n", size, MAX_SECTION_NAME); return -1; } size = (size * 2) + 2; if ((idx + size) > nbytes) goto bad; for (j = 0; j < size; j+=2) { litfile->sections[section].name[j/2] = pNamelist[idx + j]; } litfile->sections[section].name[j/2] = '\0'; idx += size; } free(pNamelist); return 0; bad: lit_error(ERR_R, "Invalid or corrupt ::DataSpace/Namelist!\n" "\nTried to read %d bytes past length of (%ld)\n", size,nbytes); if (pNamelist) free(pNamelist); return -1; }