xref: /reactos/dll/win32/itss/chm_lib.c (revision ac43fd2b)
1 /***************************************************************************
2  *             chm_lib.c - CHM archive manipulation routines               *
3  *                           -------------------                           *
4  *                                                                         *
5  *  author:     Jed Wing <jedwin@ugcs.caltech.edu>                         *
6  *  version:    0.3                                                        *
7  *  notes:      These routines are meant for the manipulation of microsoft *
8  *              .chm (compiled html help) files, but may likely be used    *
9  *              for the manipulation of any ITSS archive, if ever ITSS     *
10  *              archives are used for any other purpose.                   *
11  *                                                                         *
12  *              Note also that the section names are statically handled.   *
13  *              To be entirely correct, the section names should be read   *
14  *              from the section names meta-file, and then the various     *
15  *              content sections and the "transforms" to apply to the data *
16  *              they contain should be inferred from the section name and  *
17  *              the meta-files referenced using that name; however, all of *
18  *              the files I've been able to get my hands on appear to have *
19  *              only two sections: Uncompressed and MSCompressed.          *
20  *              Additionally, the ITSS.DLL file included with Windows does *
21  *              not appear to handle any different transforms than the     *
22  *              simple LZX-transform.  Furthermore, the list of transforms *
23  *              to apply is broken, in that only half the required space   *
24  *              is allocated for the list.  (It appears as though the      *
25  *              space is allocated for ASCII strings, but the strings are  *
26  *              written as unicode.  As a result, only the first half of   *
27  *              the string appears.)  So this is probably not too big of   *
28  *              a deal, at least until CHM v4 (MS .lit files), which also  *
29  *              incorporate encryption, of some description.               *
30  *                                                                         *
31  ***************************************************************************/
32 
33 /***************************************************************************
34  *
35  * This library is free software; you can redistribute it and/or
36  * modify it under the terms of the GNU Lesser General Public
37  * License as published by the Free Software Foundation; either
38  * version 2.1 of the License, or (at your option) any later version.
39  *
40  * This library is distributed in the hope that it will be useful,
41  * but WITHOUT ANY WARRANTY; without even the implied warranty of
42  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43  * Lesser General Public License for more details.
44  *
45  * You should have received a copy of the GNU Lesser General Public
46  * License along with this library; if not, write to the Free Software
47  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
48  *
49  ***************************************************************************/
50 
51 /***************************************************************************
52  *                                                                         *
53  * Adapted for Wine by Mike McCormack                                      *
54  *                                                                         *
55  ***************************************************************************/
56 
57 
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 
63 #include "windef.h"
64 #include "winbase.h"
65 #include "winnls.h"
66 
67 #include "chm_lib.h"
68 #include "lzx.h"
69 
70 #define CHM_ACQUIRE_LOCK(a) do {                        \
71         EnterCriticalSection(&(a));                     \
72     } while(0)
73 #define CHM_RELEASE_LOCK(a) do {                        \
74         LeaveCriticalSection(&(a));                     \
75     } while(0)
76 
77 #define CHM_NULL_FD (INVALID_HANDLE_VALUE)
78 #define CHM_CLOSE_FILE(fd) CloseHandle((fd))
79 
80 /*
81  * defines related to tuning
82  */
83 #ifndef CHM_MAX_BLOCKS_CACHED
84 #define CHM_MAX_BLOCKS_CACHED 5
85 #endif
86 #define CHM_PARAM_MAX_BLOCKS_CACHED 0
87 
88 /*
89  * architecture specific defines
90  *
91  * Note: as soon as C99 is more widespread, the below defines should
92  * probably just use the C99 sized-int types.
93  *
94  * The following settings will probably work for many platforms.  The sizes
95  * don't have to be exactly correct, but the types must accommodate at least as
96  * many bits as they specify.
97  */
98 
99 /* i386, 32-bit, Windows */
100 typedef BYTE   UChar;
101 typedef SHORT  Int16;
102 typedef USHORT UInt16;
103 typedef LONG   Int32;
104 typedef DWORD      UInt32;
105 typedef LONGLONG   Int64;
106 typedef ULONGLONG  UInt64;
107 
108 /* utilities for unmarshalling data */
109 static BOOL _unmarshal_char_array(unsigned char **pData,
110                                   unsigned int *pLenRemain,
111                                   char *dest,
112                                   int count)
113 {
114     if (count <= 0  ||  (unsigned int)count > *pLenRemain)
115         return FALSE;
116     memcpy(dest, (*pData), count);
117     *pData += count;
118     *pLenRemain -= count;
119     return TRUE;
120 }
121 
122 static BOOL _unmarshal_uchar_array(unsigned char **pData,
123                                    unsigned int *pLenRemain,
124                                    unsigned char *dest,
125                                    int count)
126 {
127     if (count <= 0  || (unsigned int)count > *pLenRemain)
128         return FALSE;
129     memcpy(dest, (*pData), count);
130     *pData += count;
131     *pLenRemain -= count;
132     return TRUE;
133 }
134 
135 static BOOL _unmarshal_int32(unsigned char **pData,
136                              unsigned int *pLenRemain,
137                              Int32 *dest)
138 {
139     if (4 > *pLenRemain)
140         return FALSE;
141     *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
142     *pData += 4;
143     *pLenRemain -= 4;
144     return TRUE;
145 }
146 
147 static BOOL _unmarshal_uint32(unsigned char **pData,
148                               unsigned int *pLenRemain,
149                               UInt32 *dest)
150 {
151     if (4 > *pLenRemain)
152         return FALSE;
153     *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
154     *pData += 4;
155     *pLenRemain -= 4;
156     return TRUE;
157 }
158 
159 static BOOL _unmarshal_int64(unsigned char **pData,
160                              unsigned int *pLenRemain,
161                              Int64 *dest)
162 {
163     Int64 temp;
164     int i;
165     if (8 > *pLenRemain)
166         return FALSE;
167     temp=0;
168     for(i=8; i>0; i--)
169     {
170         temp <<= 8;
171         temp |= (*pData)[i-1];
172     }
173     *dest = temp;
174     *pData += 8;
175     *pLenRemain -= 8;
176     return TRUE;
177 }
178 
179 static BOOL _unmarshal_uint64(unsigned char **pData,
180                               unsigned int *pLenRemain,
181                               UInt64 *dest)
182 {
183     UInt64 temp;
184     int i;
185     if (8 > *pLenRemain)
186         return FALSE;
187     temp=0;
188     for(i=8; i>0; i--)
189     {
190         temp <<= 8;
191         temp |= (*pData)[i-1];
192     }
193     *dest = temp;
194     *pData += 8;
195     *pLenRemain -= 8;
196     return TRUE;
197 }
198 
199 static BOOL _unmarshal_uuid(unsigned char **pData,
200                             unsigned int *pDataLen,
201                             unsigned char *dest)
202 {
203     return _unmarshal_uchar_array(pData, pDataLen, dest, 16);
204 }
205 
206 /* names of sections essential to decompression */
207 static const WCHAR _CHMU_RESET_TABLE[] = {
208 ':',':','D','a','t','a','S','p','a','c','e','/',
209         'S','t','o','r','a','g','e','/',
210         'M','S','C','o','m','p','r','e','s','s','e','d','/',
211         'T','r','a','n','s','f','o','r','m','/',
212         '{','7','F','C','2','8','9','4','0','-','9','D','3','1',
213           '-','1','1','D','0','-','9','B','2','7','-',
214           '0','0','A','0','C','9','1','E','9','C','7','C','}','/',
215         'I','n','s','t','a','n','c','e','D','a','t','a','/',
216         'R','e','s','e','t','T','a','b','l','e',0
217 };
218 static const WCHAR _CHMU_LZXC_CONTROLDATA[] = {
219 ':',':','D','a','t','a','S','p','a','c','e','/',
220         'S','t','o','r','a','g','e','/',
221         'M','S','C','o','m','p','r','e','s','s','e','d','/',
222         'C','o','n','t','r','o','l','D','a','t','a',0
223 };
224 static const WCHAR _CHMU_CONTENT[] = {
225 ':',':','D','a','t','a','S','p','a','c','e','/',
226         'S','t','o','r','a','g','e','/',
227         'M','S','C','o','m','p','r','e','s','s','e','d','/',
228         'C','o','n','t','e','n','t',0
229 };
230 
231 /*
232  * structures local to this module
233  */
234 
235 /* structure of ITSF headers */
236 #define _CHM_ITSF_V2_LEN (0x58)
237 #define _CHM_ITSF_V3_LEN (0x60)
238 struct chmItsfHeader
239 {
240     char        signature[4];           /*  0 (ITSF) */
241     Int32       version;                /*  4 */
242     Int32       header_len;             /*  8 */
243     Int32       unknown_000c;           /*  c */
244     UInt32      last_modified;          /* 10 */
245     UInt32      lang_id;                /* 14 */
246     UChar       dir_uuid[16];           /* 18 */
247     UChar       stream_uuid[16];        /* 28 */
248     UInt64      unknown_offset;         /* 38 */
249     UInt64      unknown_len;            /* 40 */
250     UInt64      dir_offset;             /* 48 */
251     UInt64      dir_len;                /* 50 */
252     UInt64      data_offset;            /* 58 (Not present before V3) */
253 }; /* __attribute__ ((aligned (1))); */
254 
255 static BOOL _unmarshal_itsf_header(unsigned char **pData,
256                                    unsigned int *pDataLen,
257                                    struct chmItsfHeader *dest)
258 {
259     /* we only know how to deal with the 0x58 and 0x60 byte structures */
260     if (*pDataLen != _CHM_ITSF_V2_LEN  &&  *pDataLen != _CHM_ITSF_V3_LEN)
261         return FALSE;
262 
263     /* unmarshal common fields */
264     _unmarshal_char_array(pData, pDataLen,  dest->signature, 4);
265     _unmarshal_int32     (pData, pDataLen, &dest->version);
266     _unmarshal_int32     (pData, pDataLen, &dest->header_len);
267     _unmarshal_int32     (pData, pDataLen, &dest->unknown_000c);
268     _unmarshal_uint32    (pData, pDataLen, &dest->last_modified);
269     _unmarshal_uint32    (pData, pDataLen, &dest->lang_id);
270     _unmarshal_uuid      (pData, pDataLen,  dest->dir_uuid);
271     _unmarshal_uuid      (pData, pDataLen,  dest->stream_uuid);
272     _unmarshal_uint64    (pData, pDataLen, &dest->unknown_offset);
273     _unmarshal_uint64    (pData, pDataLen, &dest->unknown_len);
274     _unmarshal_uint64    (pData, pDataLen, &dest->dir_offset);
275     _unmarshal_uint64    (pData, pDataLen, &dest->dir_len);
276 
277     /* error check the data */
278     /* XXX: should also check UUIDs, probably, though with a version 3 file,
279      * current MS tools do not seem to use them.
280      */
281     if (memcmp(dest->signature, "ITSF", 4) != 0)
282         return FALSE;
283     if (dest->version == 2)
284     {
285         if (dest->header_len < _CHM_ITSF_V2_LEN)
286             return FALSE;
287     }
288     else if (dest->version == 3)
289     {
290         if (dest->header_len < _CHM_ITSF_V3_LEN)
291             return FALSE;
292     }
293     else
294         return FALSE;
295 
296     /* now, if we have a V3 structure, unmarshal the rest.
297      * otherwise, compute it
298      */
299     if (dest->version == 3)
300     {
301         if (*pDataLen != 0)
302             _unmarshal_uint64(pData, pDataLen, &dest->data_offset);
303         else
304             return FALSE;
305     }
306     else
307         dest->data_offset = dest->dir_offset + dest->dir_len;
308 
309     return TRUE;
310 }
311 
312 /* structure of ITSP headers */
313 #define _CHM_ITSP_V1_LEN (0x54)
314 struct chmItspHeader
315 {
316     char        signature[4];           /*  0 (ITSP) */
317     Int32       version;                /*  4 */
318     Int32       header_len;             /*  8 */
319     Int32       unknown_000c;           /*  c */
320     UInt32      block_len;              /* 10 */
321     Int32       blockidx_intvl;         /* 14 */
322     Int32       index_depth;            /* 18 */
323     Int32       index_root;             /* 1c */
324     Int32       index_head;             /* 20 */
325     Int32       unknown_0024;           /* 24 */
326     UInt32      num_blocks;             /* 28 */
327     Int32       unknown_002c;           /* 2c */
328     UInt32      lang_id;                /* 30 */
329     UChar       system_uuid[16];        /* 34 */
330     UChar       unknown_0044[16];       /* 44 */
331 }; /* __attribute__ ((aligned (1))); */
332 
333 static BOOL _unmarshal_itsp_header(unsigned char **pData,
334                                    unsigned int *pDataLen,
335                                    struct chmItspHeader *dest)
336 {
337     /* we only know how to deal with a 0x54 byte structures */
338     if (*pDataLen != _CHM_ITSP_V1_LEN)
339         return FALSE;
340 
341     /* unmarshal fields */
342     _unmarshal_char_array(pData, pDataLen,  dest->signature, 4);
343     _unmarshal_int32     (pData, pDataLen, &dest->version);
344     _unmarshal_int32     (pData, pDataLen, &dest->header_len);
345     _unmarshal_int32     (pData, pDataLen, &dest->unknown_000c);
346     _unmarshal_uint32    (pData, pDataLen, &dest->block_len);
347     _unmarshal_int32     (pData, pDataLen, &dest->blockidx_intvl);
348     _unmarshal_int32     (pData, pDataLen, &dest->index_depth);
349     _unmarshal_int32     (pData, pDataLen, &dest->index_root);
350     _unmarshal_int32     (pData, pDataLen, &dest->index_head);
351     _unmarshal_int32     (pData, pDataLen, &dest->unknown_0024);
352     _unmarshal_uint32    (pData, pDataLen, &dest->num_blocks);
353     _unmarshal_int32     (pData, pDataLen, &dest->unknown_002c);
354     _unmarshal_uint32    (pData, pDataLen, &dest->lang_id);
355     _unmarshal_uuid      (pData, pDataLen,  dest->system_uuid);
356     _unmarshal_uchar_array(pData, pDataLen, dest->unknown_0044, 16);
357 
358     /* error check the data */
359     if (memcmp(dest->signature, "ITSP", 4) != 0)
360         return FALSE;
361     if (dest->version != 1)
362         return FALSE;
363     if (dest->header_len != _CHM_ITSP_V1_LEN)
364         return FALSE;
365 
366     return TRUE;
367 }
368 
369 /* structure of PMGL headers */
370 static const char _chm_pmgl_marker[4] = "PMGL";
371 #define _CHM_PMGL_LEN (0x14)
372 struct chmPmglHeader
373 {
374     char        signature[4];           /*  0 (PMGL) */
375     UInt32      free_space;             /*  4 */
376     UInt32      unknown_0008;           /*  8 */
377     Int32       block_prev;             /*  c */
378     Int32       block_next;             /* 10 */
379 }; /* __attribute__ ((aligned (1))); */
380 
381 static BOOL _unmarshal_pmgl_header(unsigned char **pData,
382                                    unsigned int *pDataLen,
383                                    struct chmPmglHeader *dest)
384 {
385     /* we only know how to deal with a 0x14 byte structures */
386     if (*pDataLen != _CHM_PMGL_LEN)
387         return FALSE;
388 
389     /* unmarshal fields */
390     _unmarshal_char_array(pData, pDataLen,  dest->signature, 4);
391     _unmarshal_uint32    (pData, pDataLen, &dest->free_space);
392     _unmarshal_uint32    (pData, pDataLen, &dest->unknown_0008);
393     _unmarshal_int32     (pData, pDataLen, &dest->block_prev);
394     _unmarshal_int32     (pData, pDataLen, &dest->block_next);
395 
396     /* check structure */
397     if (memcmp(dest->signature, _chm_pmgl_marker, 4) != 0)
398         return FALSE;
399 
400     return TRUE;
401 }
402 
403 /* structure of PMGI headers */
404 static const char _chm_pmgi_marker[4] = "PMGI";
405 #define _CHM_PMGI_LEN (0x08)
406 struct chmPmgiHeader
407 {
408     char        signature[4];           /*  0 (PMGI) */
409     UInt32      free_space;             /*  4 */
410 }; /* __attribute__ ((aligned (1))); */
411 
412 static BOOL _unmarshal_pmgi_header(unsigned char **pData,
413                                    unsigned int *pDataLen,
414                                    struct chmPmgiHeader *dest)
415 {
416     /* we only know how to deal with a 0x8 byte structures */
417     if (*pDataLen != _CHM_PMGI_LEN)
418         return FALSE;
419 
420     /* unmarshal fields */
421     _unmarshal_char_array(pData, pDataLen,  dest->signature, 4);
422     _unmarshal_uint32    (pData, pDataLen, &dest->free_space);
423 
424     /* check structure */
425     if (memcmp(dest->signature, _chm_pmgi_marker, 4) != 0)
426         return FALSE;
427 
428     return TRUE;
429 }
430 
431 /* structure of LZXC reset table */
432 #define _CHM_LZXC_RESETTABLE_V1_LEN (0x28)
433 struct chmLzxcResetTable
434 {
435     UInt32      version;
436     UInt32      block_count;
437     UInt32      unknown;
438     UInt32      table_offset;
439     UInt64      uncompressed_len;
440     UInt64      compressed_len;
441     UInt64      block_len;
442 }; /* __attribute__ ((aligned (1))); */
443 
444 static BOOL _unmarshal_lzxc_reset_table(unsigned char **pData,
445                                         unsigned int *pDataLen,
446                                         struct chmLzxcResetTable *dest)
447 {
448     /* we only know how to deal with a 0x28 byte structures */
449     if (*pDataLen != _CHM_LZXC_RESETTABLE_V1_LEN)
450         return FALSE;
451 
452     /* unmarshal fields */
453     _unmarshal_uint32    (pData, pDataLen, &dest->version);
454     _unmarshal_uint32    (pData, pDataLen, &dest->block_count);
455     _unmarshal_uint32    (pData, pDataLen, &dest->unknown);
456     _unmarshal_uint32    (pData, pDataLen, &dest->table_offset);
457     _unmarshal_uint64    (pData, pDataLen, &dest->uncompressed_len);
458     _unmarshal_uint64    (pData, pDataLen, &dest->compressed_len);
459     _unmarshal_uint64    (pData, pDataLen, &dest->block_len);
460 
461     /* check structure */
462     if (dest->version != 2)
463         return FALSE;
464 
465     return TRUE;
466 }
467 
468 /* structure of LZXC control data block */
469 #define _CHM_LZXC_MIN_LEN (0x18)
470 #define _CHM_LZXC_V2_LEN (0x1c)
471 struct chmLzxcControlData
472 {
473     UInt32      size;                   /*  0        */
474     char        signature[4];           /*  4 (LZXC) */
475     UInt32      version;                /*  8        */
476     UInt32      resetInterval;          /*  c        */
477     UInt32      windowSize;             /* 10        */
478     UInt32      windowsPerReset;        /* 14        */
479     UInt32      unknown_18;             /* 18        */
480 };
481 
482 static BOOL _unmarshal_lzxc_control_data(unsigned char **pData,
483                                          unsigned int *pDataLen,
484                                          struct chmLzxcControlData *dest)
485 {
486     /* we want at least 0x18 bytes */
487     if (*pDataLen < _CHM_LZXC_MIN_LEN)
488         return FALSE;
489 
490     /* unmarshal fields */
491     _unmarshal_uint32    (pData, pDataLen, &dest->size);
492     _unmarshal_char_array(pData, pDataLen,  dest->signature, 4);
493     _unmarshal_uint32    (pData, pDataLen, &dest->version);
494     _unmarshal_uint32    (pData, pDataLen, &dest->resetInterval);
495     _unmarshal_uint32    (pData, pDataLen, &dest->windowSize);
496     _unmarshal_uint32    (pData, pDataLen, &dest->windowsPerReset);
497 
498     if (*pDataLen >= _CHM_LZXC_V2_LEN)
499         _unmarshal_uint32    (pData, pDataLen, &dest->unknown_18);
500     else
501         dest->unknown_18 = 0;
502 
503     if (dest->version == 2)
504     {
505         dest->resetInterval *= 0x8000;
506         dest->windowSize *= 0x8000;
507     }
508     if (dest->windowSize == 0  ||  dest->resetInterval == 0)
509         return FALSE;
510 
511     /* for now, only support resetInterval a multiple of windowSize/2 */
512     if (dest->windowSize == 1)
513         return FALSE;
514     if ((dest->resetInterval % (dest->windowSize/2)) != 0)
515         return FALSE;
516 
517     /* check structure */
518     if (memcmp(dest->signature, "LZXC", 4) != 0)
519         return FALSE;
520 
521     return TRUE;
522 }
523 
524 /* the structure used for chm file handles */
525 struct chmFile
526 {
527     HANDLE              fd;
528 
529     CRITICAL_SECTION    mutex;
530     CRITICAL_SECTION    lzx_mutex;
531     CRITICAL_SECTION    cache_mutex;
532 
533     UInt64              dir_offset;
534     UInt64              dir_len;
535     UInt64              data_offset;
536     Int32               index_root;
537     Int32               index_head;
538     UInt32              block_len;
539 
540     UInt64              span;
541     struct chmUnitInfo  rt_unit;
542     struct chmUnitInfo  cn_unit;
543     struct chmLzxcResetTable reset_table;
544 
545     /* LZX control data */
546     int                 compression_enabled;
547     UInt32              window_size;
548     UInt32              reset_interval;
549     UInt32              reset_blkcount;
550 
551     /* decompressor state */
552     struct LZXstate    *lzx_state;
553     int                 lzx_last_block;
554 
555     /* cache for decompressed blocks */
556     UChar             **cache_blocks;
557     Int64              *cache_block_indices;
558     Int32               cache_num_blocks;
559 };
560 
561 /*
562  * utility functions local to this module
563  */
564 
565 /* utility function to handle differences between {pread,read}(64)? */
566 static Int64 _chm_fetch_bytes(struct chmFile *h,
567                               UChar *buf,
568                               UInt64 os,
569                               Int64 len)
570 {
571     Int64 readLen=0;
572     if (h->fd  ==  CHM_NULL_FD)
573         return readLen;
574 
575     CHM_ACQUIRE_LOCK(h->mutex);
576     /* NOTE: this might be better done with CreateFileMapping, et cetera... */
577     {
578         LARGE_INTEGER old_pos, new_pos;
579         DWORD actualLen=0;
580 
581         /* awkward Win32 Seek/Tell */
582         new_pos.QuadPart = 0;
583         SetFilePointerEx( h->fd, new_pos, &old_pos, FILE_CURRENT );
584         new_pos.QuadPart = os;
585         SetFilePointerEx( h->fd, new_pos, NULL, FILE_BEGIN );
586 
587         /* read the data */
588         if (ReadFile(h->fd,
589                      buf,
590                      (DWORD)len,
591                      &actualLen,
592                      NULL))
593             readLen = actualLen;
594         else
595             readLen = 0;
596 
597         /* restore original position */
598         SetFilePointerEx( h->fd, old_pos, NULL, FILE_BEGIN );
599     }
600     CHM_RELEASE_LOCK(h->mutex);
601     return readLen;
602 }
603 
604 /*
605  * set a parameter on the file handle.
606  * valid parameter types:
607  *          CHM_PARAM_MAX_BLOCKS_CACHED:
608  *                 how many decompressed blocks should be cached?  A simple
609  *                 caching scheme is used, wherein the index of the block is
610  *                 used as a hash value, and hash collision results in the
611  *                 invalidation of the previously cached block.
612  */
613 static void chm_set_param(struct chmFile *h,
614                           int paramType,
615                           int paramVal)
616 {
617     switch (paramType)
618     {
619         case CHM_PARAM_MAX_BLOCKS_CACHED:
620             CHM_ACQUIRE_LOCK(h->cache_mutex);
621             if (paramVal != h->cache_num_blocks)
622             {
623                 UChar **newBlocks;
624                 Int64 *newIndices;
625                 int     i;
626 
627                 /* allocate new cached blocks */
628                 newBlocks = HeapAlloc(GetProcessHeap(), 0, paramVal * sizeof (UChar *));
629                 newIndices = HeapAlloc(GetProcessHeap(), 0, paramVal * sizeof (UInt64));
630                 for (i=0; i<paramVal; i++)
631                 {
632                     newBlocks[i] = NULL;
633                     newIndices[i] = 0;
634                 }
635 
636                 /* re-distribute old cached blocks */
637                 if (h->cache_blocks)
638                 {
639                     for (i=0; i<h->cache_num_blocks; i++)
640                     {
641                         int newSlot = (int)(h->cache_block_indices[i] % paramVal);
642 
643                         if (h->cache_blocks[i])
644                         {
645                             /* in case of collision, destroy newcomer */
646                             if (newBlocks[newSlot])
647                             {
648                                 HeapFree(GetProcessHeap(), 0, h->cache_blocks[i]);
649                                 h->cache_blocks[i] = NULL;
650                             }
651                             else
652                             {
653                                 newBlocks[newSlot] = h->cache_blocks[i];
654                                 newIndices[newSlot] =
655                                             h->cache_block_indices[i];
656                             }
657                         }
658                     }
659 
660                     HeapFree(GetProcessHeap(), 0, h->cache_blocks);
661                     HeapFree(GetProcessHeap(), 0, h->cache_block_indices);
662                 }
663 
664                 /* now, set new values */
665                 h->cache_blocks = newBlocks;
666                 h->cache_block_indices = newIndices;
667                 h->cache_num_blocks = paramVal;
668             }
669             CHM_RELEASE_LOCK(h->cache_mutex);
670             break;
671 
672         default:
673             break;
674     }
675 }
676 
677 /* open an ITS archive */
678 struct chmFile *chm_openW(const WCHAR *filename)
679 {
680     unsigned char               sbuffer[256];
681     unsigned int                sremain;
682     unsigned char              *sbufpos;
683     struct chmFile             *newHandle=NULL;
684     struct chmItsfHeader        itsfHeader;
685     struct chmItspHeader        itspHeader;
686 #if 0
687     struct chmUnitInfo          uiSpan;
688 #endif
689     struct chmUnitInfo          uiLzxc;
690     struct chmLzxcControlData   ctlData;
691 
692     /* allocate handle */
693     newHandle = HeapAlloc(GetProcessHeap(), 0, sizeof(struct chmFile));
694     newHandle->fd = CHM_NULL_FD;
695     newHandle->lzx_state = NULL;
696     newHandle->cache_blocks = NULL;
697     newHandle->cache_block_indices = NULL;
698     newHandle->cache_num_blocks = 0;
699 
700     /* open file */
701     if ((newHandle->fd=CreateFileW(filename,
702                                    GENERIC_READ,
703                                    FILE_SHARE_READ,
704                                    NULL,
705                                    OPEN_EXISTING,
706                                    FILE_ATTRIBUTE_NORMAL,
707                                    NULL)) == CHM_NULL_FD)
708     {
709         HeapFree(GetProcessHeap(), 0, newHandle);
710         return NULL;
711     }
712 
713     /* initialize mutexes, if needed */
714     InitializeCriticalSection(&newHandle->mutex);
715     newHandle->mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.mutex");
716     InitializeCriticalSection(&newHandle->lzx_mutex);
717     newHandle->lzx_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.lzx_mutex");
718     InitializeCriticalSection(&newHandle->cache_mutex);
719     newHandle->cache_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.cache_mutex");
720 
721     /* read and verify header */
722     sremain = _CHM_ITSF_V3_LEN;
723     sbufpos = sbuffer;
724     if (_chm_fetch_bytes(newHandle, sbuffer, 0, sremain) != sremain    ||
725         !_unmarshal_itsf_header(&sbufpos, &sremain, &itsfHeader))
726     {
727         chm_close(newHandle);
728         return NULL;
729     }
730 
731     /* stash important values from header */
732     newHandle->dir_offset  = itsfHeader.dir_offset;
733     newHandle->dir_len     = itsfHeader.dir_len;
734     newHandle->data_offset = itsfHeader.data_offset;
735 
736     /* now, read and verify the directory header chunk */
737     sremain = _CHM_ITSP_V1_LEN;
738     sbufpos = sbuffer;
739     if (_chm_fetch_bytes(newHandle, sbuffer,
740                          itsfHeader.dir_offset, sremain) != sremain    ||
741         !_unmarshal_itsp_header(&sbufpos, &sremain, &itspHeader))
742     {
743         chm_close(newHandle);
744         return NULL;
745     }
746 
747     /* grab essential information from ITSP header */
748     newHandle->dir_offset += itspHeader.header_len;
749     newHandle->dir_len    -= itspHeader.header_len;
750     newHandle->index_root  = itspHeader.index_root;
751     newHandle->index_head  = itspHeader.index_head;
752     newHandle->block_len   = itspHeader.block_len;
753 
754     /* if the index root is -1, this means we don't have any PMGI blocks.
755      * as a result, we must use the sole PMGL block as the index root
756      */
757     if (newHandle->index_root == -1)
758         newHandle->index_root = newHandle->index_head;
759 
760     /* initialize cache */
761     chm_set_param(newHandle, CHM_PARAM_MAX_BLOCKS_CACHED,
762                   CHM_MAX_BLOCKS_CACHED);
763 
764     /* By default, compression is enabled. */
765     newHandle->compression_enabled = 1;
766 
767     /* prefetch most commonly needed unit infos */
768     if (CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
769                                                   _CHMU_RESET_TABLE,
770                                                   &newHandle->rt_unit)    ||
771         newHandle->rt_unit.space == CHM_COMPRESSED                        ||
772         CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
773                                                   _CHMU_CONTENT,
774                                                   &newHandle->cn_unit)    ||
775         newHandle->cn_unit.space == CHM_COMPRESSED                        ||
776         CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
777                                                   _CHMU_LZXC_CONTROLDATA,
778                                                   &uiLzxc)                ||
779         uiLzxc.space == CHM_COMPRESSED)
780     {
781         newHandle->compression_enabled = 0;
782     }
783 
784     /* read reset table info */
785     if (newHandle->compression_enabled)
786     {
787         sremain = _CHM_LZXC_RESETTABLE_V1_LEN;
788         sbufpos = sbuffer;
789         if (chm_retrieve_object(newHandle, &newHandle->rt_unit, sbuffer,
790                                 0, sremain) != sremain                        ||
791             !_unmarshal_lzxc_reset_table(&sbufpos, &sremain,
792                                          &newHandle->reset_table))
793         {
794             newHandle->compression_enabled = 0;
795         }
796     }
797 
798     /* read control data */
799     if (newHandle->compression_enabled)
800     {
801         sremain = (unsigned long)uiLzxc.length;
802         sbufpos = sbuffer;
803         if (chm_retrieve_object(newHandle, &uiLzxc, sbuffer,
804                                 0, sremain) != sremain                       ||
805             !_unmarshal_lzxc_control_data(&sbufpos, &sremain,
806                                           &ctlData))
807         {
808             newHandle->compression_enabled = 0;
809         }
810 
811         newHandle->window_size = ctlData.windowSize;
812         newHandle->reset_interval = ctlData.resetInterval;
813 
814 /* Jed, Mon Jun 28: Experimentally, it appears that the reset block count */
815 /*       must be multiplied by this formerly unknown ctrl data field in   */
816 /*       order to decompress some files.                                  */
817 #if 0
818         newHandle->reset_blkcount = newHandle->reset_interval /
819                     (newHandle->window_size / 2);
820 #else
821         newHandle->reset_blkcount = newHandle->reset_interval    /
822                                     (newHandle->window_size / 2) *
823                                     ctlData.windowsPerReset;
824 #endif
825     }
826 
827     return newHandle;
828 }
829 
830 /* Duplicate an ITS archive handle */
831 struct chmFile *chm_dup(struct chmFile *oldHandle)
832 {
833     struct chmFile *newHandle=NULL;
834 
835     newHandle = HeapAlloc(GetProcessHeap(), 0, sizeof(struct chmFile));
836     *newHandle = *oldHandle;
837 
838     /* duplicate fd handle */
839     DuplicateHandle(GetCurrentProcess(), oldHandle->fd,
840                     GetCurrentProcess(), &(newHandle->fd),
841                     0, FALSE, DUPLICATE_SAME_ACCESS);
842     newHandle->lzx_state = NULL;
843     newHandle->cache_blocks = NULL;
844     newHandle->cache_block_indices = NULL;
845     newHandle->cache_num_blocks = 0;
846 
847     /* initialize mutexes, if needed */
848     InitializeCriticalSection(&newHandle->mutex);
849     newHandle->mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.mutex");
850     InitializeCriticalSection(&newHandle->lzx_mutex);
851     newHandle->lzx_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.lzx_mutex");
852     InitializeCriticalSection(&newHandle->cache_mutex);
853     newHandle->cache_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.cache_mutex");
854 
855     /* initialize cache */
856     chm_set_param(newHandle, CHM_PARAM_MAX_BLOCKS_CACHED,
857                   CHM_MAX_BLOCKS_CACHED);
858 
859     return newHandle;
860 }
861 
862 /* close an ITS archive */
863 void chm_close(struct chmFile *h)
864 {
865     if (h != NULL)
866     {
867         if (h->fd != CHM_NULL_FD)
868             CHM_CLOSE_FILE(h->fd);
869         h->fd = CHM_NULL_FD;
870 
871         h->mutex.DebugInfo->Spare[0] = 0;
872         DeleteCriticalSection(&h->mutex);
873         h->lzx_mutex.DebugInfo->Spare[0] = 0;
874         DeleteCriticalSection(&h->lzx_mutex);
875         h->cache_mutex.DebugInfo->Spare[0] = 0;
876         DeleteCriticalSection(&h->cache_mutex);
877 
878         if (h->lzx_state)
879             LZXteardown(h->lzx_state);
880         h->lzx_state = NULL;
881 
882         if (h->cache_blocks)
883         {
884             int i;
885             for (i=0; i<h->cache_num_blocks; i++)
886             {
887                 HeapFree(GetProcessHeap(), 0, h->cache_blocks[i]);
888             }
889             HeapFree(GetProcessHeap(), 0, h->cache_blocks);
890             h->cache_blocks = NULL;
891         }
892 
893         HeapFree(GetProcessHeap(), 0, h->cache_block_indices);
894         h->cache_block_indices = NULL;
895 
896         HeapFree(GetProcessHeap(), 0, h);
897     }
898 }
899 
900 /*
901  * helper methods for chm_resolve_object
902  */
903 
904 /* skip a compressed dword */
905 static void _chm_skip_cword(UChar **pEntry)
906 {
907     while (*(*pEntry)++ >= 0x80)
908         ;
909 }
910 
911 /* skip the data from a PMGL entry */
912 static void _chm_skip_PMGL_entry_data(UChar **pEntry)
913 {
914     _chm_skip_cword(pEntry);
915     _chm_skip_cword(pEntry);
916     _chm_skip_cword(pEntry);
917 }
918 
919 /* parse a compressed dword */
920 static UInt64 _chm_parse_cword(UChar **pEntry)
921 {
922     UInt64 accum = 0;
923     UChar temp;
924     while ((temp=*(*pEntry)++) >= 0x80)
925     {
926         accum <<= 7;
927         accum += temp & 0x7f;
928     }
929 
930     return (accum << 7) + temp;
931 }
932 
933 /* parse a utf-8 string into an ASCII char buffer */
934 static BOOL _chm_parse_UTF8(UChar **pEntry, UInt64 count, WCHAR *path)
935 {
936     DWORD length = MultiByteToWideChar(CP_UTF8, 0, (char *)*pEntry, count, path, CHM_MAX_PATHLEN);
937     path[length] = '\0';
938     *pEntry += count;
939     return !!length;
940 }
941 
942 /* parse a PMGL entry into a chmUnitInfo struct; return 1 on success. */
943 static BOOL _chm_parse_PMGL_entry(UChar **pEntry, struct chmUnitInfo *ui)
944 {
945     UInt64 strLen;
946 
947     /* parse str len */
948     strLen = _chm_parse_cword(pEntry);
949     if (strLen > CHM_MAX_PATHLEN)
950         return FALSE;
951 
952     /* parse path */
953     if (! _chm_parse_UTF8(pEntry, strLen, ui->path))
954         return FALSE;
955 
956     /* parse info */
957     ui->space  = (int)_chm_parse_cword(pEntry);
958     ui->start  = _chm_parse_cword(pEntry);
959     ui->length = _chm_parse_cword(pEntry);
960     return TRUE;
961 }
962 
963 /* find an exact entry in PMGL; return NULL if we fail */
964 static UChar *_chm_find_in_PMGL(UChar *page_buf,
965                          UInt32 block_len,
966                          const WCHAR *objPath)
967 {
968     /* XXX: modify this to do a binary search using the nice index structure
969      *      that is provided for us.
970      */
971     struct chmPmglHeader header;
972     UInt32 hremain;
973     UChar *end;
974     UChar *cur;
975     UChar *temp;
976     UInt64 strLen;
977     WCHAR buffer[CHM_MAX_PATHLEN+1];
978 
979     /* figure out where to start and end */
980     cur = page_buf;
981     hremain = _CHM_PMGL_LEN;
982     if (! _unmarshal_pmgl_header(&cur, &hremain, &header))
983         return NULL;
984     end = page_buf + block_len - (header.free_space);
985 
986     /* now, scan progressively */
987     while (cur < end)
988     {
989         /* grab the name */
990         temp = cur;
991         strLen = _chm_parse_cword(&cur);
992         if (! _chm_parse_UTF8(&cur, strLen, buffer))
993             return NULL;
994 
995         /* check if it is the right name */
996         if (! wcsicmp(buffer, objPath))
997             return temp;
998 
999         _chm_skip_PMGL_entry_data(&cur);
1000     }
1001 
1002     return NULL;
1003 }
1004 
1005 /* find which block should be searched next for the entry; -1 if no block */
1006 static Int32 _chm_find_in_PMGI(UChar *page_buf,
1007                         UInt32 block_len,
1008                         const WCHAR *objPath)
1009 {
1010     /* XXX: modify this to do a binary search using the nice index structure
1011      *      that is provided for us
1012      */
1013     struct chmPmgiHeader header;
1014     UInt32 hremain;
1015     int page=-1;
1016     UChar *end;
1017     UChar *cur;
1018     UInt64 strLen;
1019     WCHAR buffer[CHM_MAX_PATHLEN+1];
1020 
1021     /* figure out where to start and end */
1022     cur = page_buf;
1023     hremain = _CHM_PMGI_LEN;
1024     if (! _unmarshal_pmgi_header(&cur, &hremain, &header))
1025         return -1;
1026     end = page_buf + block_len - (header.free_space);
1027 
1028     /* now, scan progressively */
1029     while (cur < end)
1030     {
1031         /* grab the name */
1032         strLen = _chm_parse_cword(&cur);
1033         if (! _chm_parse_UTF8(&cur, strLen, buffer))
1034             return -1;
1035 
1036         /* check if it is the right name */
1037         if (wcsicmp(buffer, objPath) > 0)
1038             return page;
1039 
1040         /* load next value for path */
1041         page = (int)_chm_parse_cword(&cur);
1042     }
1043 
1044     return page;
1045 }
1046 
1047 /* resolve a particular object from the archive */
1048 int chm_resolve_object(struct chmFile *h,
1049                        const WCHAR *objPath,
1050                        struct chmUnitInfo *ui)
1051 {
1052     /*
1053      * XXX: implement caching scheme for dir pages
1054      */
1055 
1056     Int32 curPage;
1057 
1058     /* buffer to hold whatever page we're looking at */
1059     UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, h->block_len);
1060 
1061     /* starting page */
1062     curPage = h->index_root;
1063 
1064     /* until we have either returned or given up */
1065     while (curPage != -1)
1066     {
1067 
1068         /* try to fetch the index page */
1069         if (_chm_fetch_bytes(h, page_buf,
1070                              h->dir_offset + (UInt64)curPage*h->block_len,
1071                              h->block_len) != h->block_len)
1072 	{
1073 	    HeapFree(GetProcessHeap(), 0, page_buf);
1074             return CHM_RESOLVE_FAILURE;
1075 	}
1076 
1077         /* now, if it is a leaf node: */
1078         if (memcmp(page_buf, _chm_pmgl_marker, 4) == 0)
1079         {
1080             /* scan block */
1081             UChar *pEntry = _chm_find_in_PMGL(page_buf,
1082                                               h->block_len,
1083                                               objPath);
1084             if (pEntry == NULL)
1085             {
1086 	        HeapFree(GetProcessHeap(), 0, page_buf);
1087                 return CHM_RESOLVE_FAILURE;
1088             }
1089 
1090             /* parse entry and return */
1091             _chm_parse_PMGL_entry(&pEntry, ui);
1092 	    HeapFree(GetProcessHeap(), 0, page_buf);
1093             return CHM_RESOLVE_SUCCESS;
1094         }
1095 
1096         /* else, if it is a branch node: */
1097         else if (memcmp(page_buf, _chm_pmgi_marker, 4) == 0)
1098             curPage = _chm_find_in_PMGI(page_buf, h->block_len, objPath);
1099 
1100         /* else, we are confused.  give up. */
1101         else
1102         {
1103 	    HeapFree(GetProcessHeap(), 0, page_buf);
1104             return CHM_RESOLVE_FAILURE;
1105         }
1106     }
1107 
1108     /* didn't find anything.  fail. */
1109     HeapFree(GetProcessHeap(), 0, page_buf);
1110     return CHM_RESOLVE_FAILURE;
1111 }
1112 
1113 /*
1114  * utility methods for dealing with compressed data
1115  */
1116 
1117 /* get the bounds of a compressed block. Returns FALSE on failure */
1118 static BOOL _chm_get_cmpblock_bounds(struct chmFile *h,
1119                                      UInt64 block,
1120                                      UInt64 *start,
1121                                      Int64 *len)
1122 {
1123     UChar buffer[8], *dummy;
1124     UInt32 remain;
1125 
1126     /* for all but the last block, use the reset table */
1127     if (block < h->reset_table.block_count-1)
1128     {
1129         /* unpack the start address */
1130         dummy = buffer;
1131         remain = 8;
1132         if (_chm_fetch_bytes(h, buffer,
1133                              h->data_offset
1134                                 + h->rt_unit.start
1135                                 + h->reset_table.table_offset
1136                                 + block*8,
1137                              remain) != remain                            ||
1138             !_unmarshal_uint64(&dummy, &remain, start))
1139             return FALSE;
1140 
1141         /* unpack the end address */
1142         dummy = buffer;
1143         remain = 8;
1144         if (_chm_fetch_bytes(h, buffer,
1145                              h->data_offset
1146                                 + h->rt_unit.start
1147                                 + h->reset_table.table_offset
1148                                 + block*8 + 8,
1149                          remain) != remain                                ||
1150             !_unmarshal_int64(&dummy, &remain, len))
1151             return FALSE;
1152     }
1153 
1154     /* for the last block, use the span in addition to the reset table */
1155     else
1156     {
1157         /* unpack the start address */
1158         dummy = buffer;
1159         remain = 8;
1160         if (_chm_fetch_bytes(h, buffer,
1161                              h->data_offset
1162                                 + h->rt_unit.start
1163                                 + h->reset_table.table_offset
1164                                 + block*8,
1165                              remain) != remain                            ||
1166             !_unmarshal_uint64(&dummy, &remain, start))
1167             return FALSE;
1168 
1169         *len = h->reset_table.compressed_len;
1170     }
1171 
1172     /* compute the length and absolute start address */
1173     *len -= *start;
1174     *start += h->data_offset + h->cn_unit.start;
1175 
1176     return TRUE;
1177 }
1178 
1179 /* decompress the block.  must have lzx_mutex. */
1180 static Int64 _chm_decompress_block(struct chmFile *h,
1181                                    UInt64 block,
1182                                    UChar **ubuffer)
1183 {
1184     UChar *cbuffer = HeapAlloc( GetProcessHeap(), 0,
1185                               ((unsigned int)h->reset_table.block_len + 6144));
1186     UInt64 cmpStart;                                    /* compressed start  */
1187     Int64 cmpLen;                                       /* compressed len    */
1188     int indexSlot;                                      /* cache index slot  */
1189     UChar *lbuffer;                                     /* local buffer ptr  */
1190     UInt32 blockAlign = (UInt32)(block % h->reset_blkcount); /* reset interval align */
1191     UInt32 i;                                           /* local loop index  */
1192 
1193     /* let the caching system pull its weight! */
1194     if (block - blockAlign <= h->lzx_last_block  &&
1195         block              >= h->lzx_last_block)
1196         blockAlign = (block - h->lzx_last_block);
1197 
1198     /* check if we need previous blocks */
1199     if (blockAlign != 0)
1200     {
1201         /* fetch all required previous blocks since last reset */
1202         for (i = blockAlign; i > 0; i--)
1203         {
1204             UInt32 curBlockIdx = block - i;
1205 
1206             /* check if we most recently decompressed the previous block */
1207             if (h->lzx_last_block != curBlockIdx)
1208             {
1209                 if ((curBlockIdx % h->reset_blkcount) == 0)
1210                 {
1211 #ifdef CHM_DEBUG
1212                     fprintf(stderr, "***RESET (1)***\n");
1213 #endif
1214                     LZXreset(h->lzx_state);
1215                 }
1216 
1217                 indexSlot = (int)((curBlockIdx) % h->cache_num_blocks);
1218                 h->cache_block_indices[indexSlot] = curBlockIdx;
1219                 if (! h->cache_blocks[indexSlot])
1220                     h->cache_blocks[indexSlot] =
1221                       HeapAlloc(GetProcessHeap(), 0,
1222                                 (unsigned int)(h->reset_table.block_len));
1223                 lbuffer = h->cache_blocks[indexSlot];
1224 
1225                 /* decompress the previous block */
1226 #ifdef CHM_DEBUG
1227                 fprintf(stderr, "Decompressing block #%4d (EXTRA)\n", curBlockIdx);
1228 #endif
1229                 if (!_chm_get_cmpblock_bounds(h, curBlockIdx, &cmpStart, &cmpLen) ||
1230                     _chm_fetch_bytes(h, cbuffer, cmpStart, cmpLen) != cmpLen      ||
1231                     LZXdecompress(h->lzx_state, cbuffer, lbuffer, (int)cmpLen,
1232                                   (int)h->reset_table.block_len) != DECR_OK)
1233                 {
1234 #ifdef CHM_DEBUG
1235                     fprintf(stderr, "   (DECOMPRESS FAILED!)\n");
1236 #endif
1237                     HeapFree(GetProcessHeap(), 0, cbuffer);
1238                     return 0;
1239                 }
1240 
1241                 h->lzx_last_block = (int)curBlockIdx;
1242             }
1243         }
1244     }
1245     else
1246     {
1247         if ((block % h->reset_blkcount) == 0)
1248         {
1249 #ifdef CHM_DEBUG
1250             fprintf(stderr, "***RESET (2)***\n");
1251 #endif
1252             LZXreset(h->lzx_state);
1253         }
1254     }
1255 
1256     /* allocate slot in cache */
1257     indexSlot = (int)(block % h->cache_num_blocks);
1258     h->cache_block_indices[indexSlot] = block;
1259     if (! h->cache_blocks[indexSlot])
1260         h->cache_blocks[indexSlot] =
1261           HeapAlloc(GetProcessHeap(), 0, ((unsigned int)h->reset_table.block_len));
1262     lbuffer = h->cache_blocks[indexSlot];
1263     *ubuffer = lbuffer;
1264 
1265     /* decompress the block we actually want */
1266 #ifdef CHM_DEBUG
1267     fprintf(stderr, "Decompressing block #%4d (REAL )\n", block);
1268 #endif
1269     if (! _chm_get_cmpblock_bounds(h, block, &cmpStart, &cmpLen)          ||
1270         _chm_fetch_bytes(h, cbuffer, cmpStart, cmpLen) != cmpLen          ||
1271         LZXdecompress(h->lzx_state, cbuffer, lbuffer, (int)cmpLen,
1272                       (int)h->reset_table.block_len) != DECR_OK)
1273     {
1274 #ifdef CHM_DEBUG
1275         fprintf(stderr, "   (DECOMPRESS FAILED!)\n");
1276 #endif
1277         HeapFree(GetProcessHeap(), 0, cbuffer);
1278         return 0;
1279     }
1280     h->lzx_last_block = (int)block;
1281 
1282     /* XXX: modify LZX routines to return the length of the data they
1283      * decompressed and return that instead, for an extra sanity check.
1284      */
1285     HeapFree(GetProcessHeap(), 0, cbuffer);
1286     return h->reset_table.block_len;
1287 }
1288 
1289 /* grab a region from a compressed block */
1290 static Int64 _chm_decompress_region(struct chmFile *h,
1291                                     UChar *buf,
1292                                     UInt64 start,
1293                                     Int64 len)
1294 {
1295     UInt64 nBlock, nOffset;
1296     UInt64 nLen;
1297     UInt64 gotLen;
1298     UChar *ubuffer = NULL;
1299 
1300         if (len <= 0)
1301                 return 0;
1302 
1303     /* figure out what we need to read */
1304     nBlock = start / h->reset_table.block_len;
1305     nOffset = start % h->reset_table.block_len;
1306     nLen = len;
1307     if (nLen > (h->reset_table.block_len - nOffset))
1308         nLen = h->reset_table.block_len - nOffset;
1309 
1310     /* if block is cached, return data from it. */
1311     CHM_ACQUIRE_LOCK(h->lzx_mutex);
1312     CHM_ACQUIRE_LOCK(h->cache_mutex);
1313     if (h->cache_block_indices[nBlock % h->cache_num_blocks] == nBlock    &&
1314         h->cache_blocks[nBlock % h->cache_num_blocks] != NULL)
1315     {
1316         memcpy(buf,
1317                h->cache_blocks[nBlock % h->cache_num_blocks] + nOffset,
1318                (unsigned int)nLen);
1319         CHM_RELEASE_LOCK(h->cache_mutex);
1320         CHM_RELEASE_LOCK(h->lzx_mutex);
1321         return nLen;
1322     }
1323     CHM_RELEASE_LOCK(h->cache_mutex);
1324 
1325     /* data request not satisfied, so... start up the decompressor machine */
1326     if (! h->lzx_state)
1327     {
1328         h->lzx_last_block = -1;
1329         h->lzx_state = LZXinit(h->window_size);
1330     }
1331 
1332     /* decompress some data */
1333     gotLen = _chm_decompress_block(h, nBlock, &ubuffer);
1334     if (gotLen < nLen)
1335         nLen = gotLen;
1336     memcpy(buf, ubuffer+nOffset, (unsigned int)nLen);
1337     CHM_RELEASE_LOCK(h->lzx_mutex);
1338     return nLen;
1339 }
1340 
1341 /* retrieve (part of) an object */
1342 LONGINT64 chm_retrieve_object(struct chmFile *h,
1343                                struct chmUnitInfo *ui,
1344                                unsigned char *buf,
1345                                LONGUINT64 addr,
1346                                LONGINT64 len)
1347 {
1348     /* must be valid file handle */
1349     if (h == NULL)
1350         return 0;
1351 
1352     /* starting address must be in correct range */
1353     if (addr >= ui->length)
1354         return 0;
1355 
1356     /* clip length */
1357     if (addr + len > ui->length)
1358         len = ui->length - addr;
1359 
1360     /* if the file is uncompressed, it's simple */
1361     if (ui->space == CHM_UNCOMPRESSED)
1362     {
1363         /* read data */
1364         return _chm_fetch_bytes(h,
1365                                 buf,
1366                                 h->data_offset + ui->start + addr,
1367                                 len);
1368     }
1369 
1370     /* else if the file is compressed, it's a little trickier */
1371     else /* ui->space == CHM_COMPRESSED */
1372     {
1373         Int64 swath=0, total=0;
1374 
1375         /* if compression is not enabled for this file... */
1376         if (! h->compression_enabled)
1377             return total;
1378 
1379         do {
1380 
1381             /* swill another mouthful */
1382             swath = _chm_decompress_region(h, buf, ui->start + addr, len);
1383 
1384             /* if we didn't get any... */
1385             if (swath == 0)
1386                 return total;
1387 
1388             /* update stats */
1389             total += swath;
1390             len -= swath;
1391             addr += swath;
1392             buf += swath;
1393 
1394         } while (len != 0);
1395 
1396         return total;
1397     }
1398 }
1399 
1400 BOOL chm_enumerate_dir(struct chmFile *h,
1401                        const WCHAR *prefix,
1402                        int what,
1403                        CHM_ENUMERATOR e,
1404                        void *context)
1405 {
1406     /*
1407      * XXX: do this efficiently (i.e. using the tree index)
1408      */
1409 
1410     Int32 curPage;
1411 
1412     /* buffer to hold whatever page we're looking at */
1413     UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, h->block_len);
1414     struct chmPmglHeader header;
1415     UChar *end;
1416     UChar *cur;
1417     unsigned int lenRemain;
1418 
1419     /* set to TRUE once we've started */
1420     BOOL it_has_begun = FALSE;
1421 
1422     /* the current ui */
1423     struct chmUnitInfo ui;
1424     int flag;
1425     UInt64 ui_path_len;
1426 
1427     /* the length of the prefix */
1428     WCHAR prefixRectified[CHM_MAX_PATHLEN+1];
1429     int prefixLen;
1430     WCHAR lastPath[CHM_MAX_PATHLEN];
1431     int lastPathLen;
1432 
1433     /* starting page */
1434     curPage = h->index_head;
1435 
1436     /* initialize pathname state */
1437     lstrcpynW(prefixRectified, prefix, CHM_MAX_PATHLEN);
1438     prefixLen = lstrlenW(prefixRectified);
1439     if (prefixLen != 0)
1440     {
1441         if (prefixRectified[prefixLen-1] != '/')
1442         {
1443             prefixRectified[prefixLen] = '/';
1444             prefixRectified[prefixLen+1] = '\0';
1445             ++prefixLen;
1446         }
1447     }
1448     lastPath[0] = '\0';
1449     lastPathLen = -1;
1450 
1451     /* until we have either returned or given up */
1452     while (curPage != -1)
1453     {
1454 
1455         /* try to fetch the index page */
1456         if (_chm_fetch_bytes(h,
1457                              page_buf,
1458                              h->dir_offset + (UInt64)curPage*h->block_len,
1459                              h->block_len) != h->block_len)
1460         {
1461             HeapFree(GetProcessHeap(), 0, page_buf);
1462             return FALSE;
1463         }
1464 
1465         /* figure out start and end for this page */
1466         cur = page_buf;
1467         lenRemain = _CHM_PMGL_LEN;
1468         if (! _unmarshal_pmgl_header(&cur, &lenRemain, &header))
1469         {
1470             HeapFree(GetProcessHeap(), 0, page_buf);
1471             return FALSE;
1472         }
1473         end = page_buf + h->block_len - (header.free_space);
1474 
1475         /* loop over this page */
1476         while (cur < end)
1477         {
1478             if (! _chm_parse_PMGL_entry(&cur, &ui))
1479             {
1480                 HeapFree(GetProcessHeap(), 0, page_buf);
1481                 return FALSE;
1482             }
1483 
1484             /* check if we should start */
1485             if (! it_has_begun)
1486             {
1487                 if (ui.length == 0  &&  _wcsnicmp(ui.path, prefixRectified, prefixLen) == 0)
1488                     it_has_begun = TRUE;
1489                 else
1490                     continue;
1491 
1492                 if (ui.path[prefixLen] == '\0')
1493                     continue;
1494             }
1495 
1496             /* check if we should stop */
1497             else
1498             {
1499                 if (_wcsnicmp(ui.path, prefixRectified, prefixLen) != 0)
1500                 {
1501                     HeapFree(GetProcessHeap(), 0, page_buf);
1502                     return TRUE;
1503                 }
1504             }
1505 
1506             /* check if we should include this path */
1507             if (lastPathLen != -1)
1508             {
1509                 if (_wcsnicmp(ui.path, lastPath, lastPathLen) == 0)
1510                     continue;
1511             }
1512             lstrcpyW(lastPath, ui.path);
1513             lastPathLen = lstrlenW(lastPath);
1514 
1515             /* get the length of the path */
1516             ui_path_len = lstrlenW(ui.path)-1;
1517 
1518             /* check for DIRS */
1519             if (ui.path[ui_path_len] == '/'  &&  !(what & CHM_ENUMERATE_DIRS))
1520                 continue;
1521 
1522             /* check for FILES */
1523             if (ui.path[ui_path_len] != '/'  &&  !(what & CHM_ENUMERATE_FILES))
1524                 continue;
1525 
1526             /* check for NORMAL vs. META */
1527             if (ui.path[0] == '/')
1528             {
1529 
1530                 /* check for NORMAL vs. SPECIAL */
1531                 if (ui.path[1] == '#'  ||  ui.path[1] == '$')
1532                     flag = CHM_ENUMERATE_SPECIAL;
1533                 else
1534                     flag = CHM_ENUMERATE_NORMAL;
1535             }
1536             else
1537                 flag = CHM_ENUMERATE_META;
1538             if (! (what & flag))
1539                 continue;
1540 
1541             /* call the enumerator */
1542             {
1543                 int status = (*e)(h, &ui, context);
1544                 switch (status)
1545                 {
1546                     case CHM_ENUMERATOR_FAILURE:
1547                         HeapFree(GetProcessHeap(), 0, page_buf);
1548                         return FALSE;
1549                     case CHM_ENUMERATOR_CONTINUE:
1550                         break;
1551                     case CHM_ENUMERATOR_SUCCESS:
1552                         HeapFree(GetProcessHeap(), 0, page_buf);
1553                         return TRUE;
1554                     default:
1555                         break;
1556                 }
1557             }
1558         }
1559 
1560         /* advance to next page */
1561         curPage = header.block_next;
1562     }
1563 
1564     HeapFree(GetProcessHeap(), 0, page_buf);
1565     return TRUE;
1566 }
1567