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