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