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