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