Silence a noisy FIXME.
[wine/multimedia.git] / dlls / itss / chm_lib.c
blob206924ab1c798e77233658a831ff300aa99e48af
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 ***************************************************************************/
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 ***************************************************************************/
42 /***************************************************************************
43 * *
44 * Adapted for Wine by Mike McCormack *
45 * *
46 ***************************************************************************/
48 #include "config.h"
49 #include "wine/port.h"
51 #include <stdarg.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
56 #include "windef.h"
57 #include "winbase.h"
58 #include "wine/unicode.h"
60 #include "chm_lib.h"
61 #include "lzx.h"
63 #define CHM_ACQUIRE_LOCK(a) do { \
64 EnterCriticalSection(&(a)); \
65 } while(0)
66 #define CHM_RELEASE_LOCK(a) do { \
67 LeaveCriticalSection(&(a)); \
68 } while(0)
70 #define CHM_NULL_FD (INVALID_HANDLE_VALUE)
71 #define CHM_CLOSE_FILE(fd) CloseHandle((fd))
74 * defines related to tuning
76 #ifndef CHM_MAX_BLOCKS_CACHED
77 #define CHM_MAX_BLOCKS_CACHED 5
78 #endif
81 * architecture specific defines
83 * Note: as soon as C99 is more widespread, the below defines should
84 * probably just use the C99 sized-int types.
86 * The following settings will probably work for many platforms. The sizes
87 * don't have to be exactly correct, but the types must accommodate at least as
88 * many bits as they specify.
91 /* i386, 32-bit, Windows */
92 typedef BYTE UChar;
93 typedef SHORT Int16;
94 typedef USHORT UInt16;
95 typedef LONG Int32;
96 typedef DWORD UInt32;
97 typedef LONGLONG Int64;
98 typedef ULONGLONG UInt64;
100 /* utilities for unmarshalling data */
101 static int _unmarshal_char_array(unsigned char **pData,
102 unsigned long *pLenRemain,
103 char *dest,
104 int count)
106 if (count <= 0 || (unsigned int)count > *pLenRemain)
107 return 0;
108 memcpy(dest, (*pData), count);
109 *pData += count;
110 *pLenRemain -= count;
111 return 1;
114 static int _unmarshal_uchar_array(unsigned char **pData,
115 unsigned long *pLenRemain,
116 unsigned char *dest,
117 int count)
119 if (count <= 0 || (unsigned int)count > *pLenRemain)
120 return 0;
121 memcpy(dest, (*pData), count);
122 *pData += count;
123 *pLenRemain -= count;
124 return 1;
127 static int _unmarshal_int32(unsigned char **pData,
128 unsigned long *pLenRemain,
129 Int32 *dest)
131 if (4 > *pLenRemain)
132 return 0;
133 *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
134 *pData += 4;
135 *pLenRemain -= 4;
136 return 1;
139 static int _unmarshal_uint32(unsigned char **pData,
140 unsigned long *pLenRemain,
141 UInt32 *dest)
143 if (4 > *pLenRemain)
144 return 0;
145 *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
146 *pData += 4;
147 *pLenRemain -= 4;
148 return 1;
151 static int _unmarshal_int64(unsigned char **pData,
152 unsigned long *pLenRemain,
153 Int64 *dest)
155 Int64 temp;
156 int i;
157 if (8 > *pLenRemain)
158 return 0;
159 temp=0;
160 for(i=8; i>0; i--)
162 temp <<= 8;
163 temp |= (*pData)[i-1];
165 *dest = temp;
166 *pData += 8;
167 *pLenRemain -= 8;
168 return 1;
171 static int _unmarshal_uint64(unsigned char **pData,
172 unsigned long *pLenRemain,
173 UInt64 *dest)
175 UInt64 temp;
176 int i;
177 if (8 > *pLenRemain)
178 return 0;
179 temp=0;
180 for(i=8; i>0; i--)
182 temp <<= 8;
183 temp |= (*pData)[i-1];
185 *dest = temp;
186 *pData += 8;
187 *pLenRemain -= 8;
188 return 1;
191 static int _unmarshal_uuid(unsigned char **pData,
192 unsigned long *pDataLen,
193 unsigned char *dest)
195 return _unmarshal_uchar_array(pData, pDataLen, dest, 16);
198 /* names of sections essential to decompression */
199 static const WCHAR _CHMU_RESET_TABLE[] = {
200 ':',':','D','a','t','a','S','p','a','c','e','/',
201 'S','t','o','r','a','g','e','/',
202 'M','S','C','o','m','p','r','e','s','s','e','d','/',
203 'T','r','a','n','s','f','o','r','m','/',
204 '{','7','F','C','2','8','9','4','0','-','9','D','3','1',
205 '-','1','1','D','0','-','9','B','2','7','-',
206 '0','0','A','0','C','9','1','E','9','C','7','C','}','/',
207 'I','n','s','t','a','n','c','e','D','a','t','a','/',
208 'R','e','s','e','t','T','a','b','l','e',0
210 static const WCHAR _CHMU_LZXC_CONTROLDATA[] = {
211 ':',':','D','a','t','a','S','p','a','c','e','/',
212 'S','t','o','r','a','g','e','/',
213 'M','S','C','o','m','p','r','e','s','s','e','d','/',
214 'C','o','n','t','r','o','l','D','a','t','a',0
216 static const WCHAR _CHMU_CONTENT[] = {
217 ':',':','D','a','t','a','S','p','a','c','e','/',
218 'S','t','o','r','a','g','e','/',
219 'M','S','C','o','m','p','r','e','s','s','e','d','/',
220 'C','o','n','t','e','n','t',0
222 static const WCHAR _CHMU_SPANINFO[] = {
223 ':',':','D','a','t','a','S','p','a','c','e','/',
224 'S','t','o','r','a','g','e','/',
225 'M','S','C','o','m','p','r','e','s','s','e','d','/',
226 'S','p','a','n','I','n','f','o',
230 * structures local to this module
233 /* structure of ITSF headers */
234 #define _CHM_ITSF_V2_LEN (0x58)
235 #define _CHM_ITSF_V3_LEN (0x60)
236 struct chmItsfHeader
238 char signature[4]; /* 0 (ITSF) */
239 Int32 version; /* 4 */
240 Int32 header_len; /* 8 */
241 Int32 unknown_000c; /* c */
242 UInt32 last_modified; /* 10 */
243 UInt32 lang_id; /* 14 */
244 UChar dir_uuid[16]; /* 18 */
245 UChar stream_uuid[16]; /* 28 */
246 UInt64 unknown_offset; /* 38 */
247 UInt64 unknown_len; /* 40 */
248 UInt64 dir_offset; /* 48 */
249 UInt64 dir_len; /* 50 */
250 UInt64 data_offset; /* 58 (Not present before V3) */
251 }; /* __attribute__ ((aligned (1))); */
253 static int _unmarshal_itsf_header(unsigned char **pData,
254 unsigned long *pDataLen,
255 struct chmItsfHeader *dest)
257 /* we only know how to deal with the 0x58 and 0x60 byte structures */
258 if (*pDataLen != _CHM_ITSF_V2_LEN && *pDataLen != _CHM_ITSF_V3_LEN)
259 return 0;
261 /* unmarshal common fields */
262 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
263 _unmarshal_int32 (pData, pDataLen, &dest->version);
264 _unmarshal_int32 (pData, pDataLen, &dest->header_len);
265 _unmarshal_int32 (pData, pDataLen, &dest->unknown_000c);
266 _unmarshal_uint32 (pData, pDataLen, &dest->last_modified);
267 _unmarshal_uint32 (pData, pDataLen, &dest->lang_id);
268 _unmarshal_uuid (pData, pDataLen, dest->dir_uuid);
269 _unmarshal_uuid (pData, pDataLen, dest->stream_uuid);
270 _unmarshal_uint64 (pData, pDataLen, &dest->unknown_offset);
271 _unmarshal_uint64 (pData, pDataLen, &dest->unknown_len);
272 _unmarshal_uint64 (pData, pDataLen, &dest->dir_offset);
273 _unmarshal_uint64 (pData, pDataLen, &dest->dir_len);
275 /* error check the data */
276 /* XXX: should also check UUIDs, probably, though with a version 3 file,
277 * current MS tools do not seem to use them.
279 if (memcmp(dest->signature, "ITSF", 4) != 0)
280 return 0;
281 if (dest->version == 2)
283 if (dest->header_len < _CHM_ITSF_V2_LEN)
284 return 0;
286 else if (dest->version == 3)
288 if (dest->header_len < _CHM_ITSF_V3_LEN)
289 return 0;
291 else
292 return 0;
294 /* now, if we have a V3 structure, unmarshal the rest.
295 * otherwise, compute it
297 if (dest->version == 3)
299 if (*pDataLen != 0)
300 _unmarshal_uint64(pData, pDataLen, &dest->data_offset);
301 else
302 return 0;
304 else
305 dest->data_offset = dest->dir_offset + dest->dir_len;
307 return 1;
310 /* structure of ITSP headers */
311 #define _CHM_ITSP_V1_LEN (0x54)
312 struct chmItspHeader
314 char signature[4]; /* 0 (ITSP) */
315 Int32 version; /* 4 */
316 Int32 header_len; /* 8 */
317 Int32 unknown_000c; /* c */
318 UInt32 block_len; /* 10 */
319 Int32 blockidx_intvl; /* 14 */
320 Int32 index_depth; /* 18 */
321 Int32 index_root; /* 1c */
322 Int32 index_head; /* 20 */
323 Int32 unknown_0024; /* 24 */
324 UInt32 num_blocks; /* 28 */
325 Int32 unknown_002c; /* 2c */
326 UInt32 lang_id; /* 30 */
327 UChar system_uuid[16]; /* 34 */
328 UChar unknown_0044[16]; /* 44 */
329 }; /* __attribute__ ((aligned (1))); */
331 static int _unmarshal_itsp_header(unsigned char **pData,
332 unsigned long *pDataLen,
333 struct chmItspHeader *dest)
335 /* we only know how to deal with a 0x54 byte structures */
336 if (*pDataLen != _CHM_ITSP_V1_LEN)
337 return 0;
339 /* unmarshal fields */
340 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
341 _unmarshal_int32 (pData, pDataLen, &dest->version);
342 _unmarshal_int32 (pData, pDataLen, &dest->header_len);
343 _unmarshal_int32 (pData, pDataLen, &dest->unknown_000c);
344 _unmarshal_uint32 (pData, pDataLen, &dest->block_len);
345 _unmarshal_int32 (pData, pDataLen, &dest->blockidx_intvl);
346 _unmarshal_int32 (pData, pDataLen, &dest->index_depth);
347 _unmarshal_int32 (pData, pDataLen, &dest->index_root);
348 _unmarshal_int32 (pData, pDataLen, &dest->index_head);
349 _unmarshal_int32 (pData, pDataLen, &dest->unknown_0024);
350 _unmarshal_uint32 (pData, pDataLen, &dest->num_blocks);
351 _unmarshal_int32 (pData, pDataLen, &dest->unknown_002c);
352 _unmarshal_uint32 (pData, pDataLen, &dest->lang_id);
353 _unmarshal_uuid (pData, pDataLen, dest->system_uuid);
354 _unmarshal_uchar_array(pData, pDataLen, dest->unknown_0044, 16);
356 /* error check the data */
357 if (memcmp(dest->signature, "ITSP", 4) != 0)
358 return 0;
359 if (dest->version != 1)
360 return 0;
361 if (dest->header_len != _CHM_ITSP_V1_LEN)
362 return 0;
364 return 1;
367 /* structure of PMGL headers */
368 static const char _chm_pmgl_marker[4] = "PMGL";
369 #define _CHM_PMGL_LEN (0x14)
370 struct chmPmglHeader
372 char signature[4]; /* 0 (PMGL) */
373 UInt32 free_space; /* 4 */
374 UInt32 unknown_0008; /* 8 */
375 Int32 block_prev; /* c */
376 Int32 block_next; /* 10 */
377 }; /* __attribute__ ((aligned (1))); */
379 static int _unmarshal_pmgl_header(unsigned char **pData,
380 unsigned long *pDataLen,
381 struct chmPmglHeader *dest)
383 /* we only know how to deal with a 0x14 byte structures */
384 if (*pDataLen != _CHM_PMGL_LEN)
385 return 0;
387 /* unmarshal fields */
388 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
389 _unmarshal_uint32 (pData, pDataLen, &dest->free_space);
390 _unmarshal_uint32 (pData, pDataLen, &dest->unknown_0008);
391 _unmarshal_int32 (pData, pDataLen, &dest->block_prev);
392 _unmarshal_int32 (pData, pDataLen, &dest->block_next);
394 /* check structure */
395 if (memcmp(dest->signature, _chm_pmgl_marker, 4) != 0)
396 return 0;
398 return 1;
401 /* structure of PMGI headers */
402 static const char _chm_pmgi_marker[4] = "PMGI";
403 #define _CHM_PMGI_LEN (0x08)
404 struct chmPmgiHeader
406 char signature[4]; /* 0 (PMGI) */
407 UInt32 free_space; /* 4 */
408 }; /* __attribute__ ((aligned (1))); */
410 static int _unmarshal_pmgi_header(unsigned char **pData,
411 unsigned long *pDataLen,
412 struct chmPmgiHeader *dest)
414 /* we only know how to deal with a 0x8 byte structures */
415 if (*pDataLen != _CHM_PMGI_LEN)
416 return 0;
418 /* unmarshal fields */
419 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
420 _unmarshal_uint32 (pData, pDataLen, &dest->free_space);
422 /* check structure */
423 if (memcmp(dest->signature, _chm_pmgi_marker, 4) != 0)
424 return 0;
426 return 1;
429 /* structure of LZXC reset table */
430 #define _CHM_LZXC_RESETTABLE_V1_LEN (0x28)
431 struct chmLzxcResetTable
433 UInt32 version;
434 UInt32 block_count;
435 UInt32 unknown;
436 UInt32 table_offset;
437 UInt64 uncompressed_len;
438 UInt64 compressed_len;
439 UInt64 block_len;
440 }; /* __attribute__ ((aligned (1))); */
442 static int _unmarshal_lzxc_reset_table(unsigned char **pData,
443 unsigned long *pDataLen,
444 struct chmLzxcResetTable *dest)
446 /* we only know how to deal with a 0x28 byte structures */
447 if (*pDataLen != _CHM_LZXC_RESETTABLE_V1_LEN)
448 return 0;
450 /* unmarshal fields */
451 _unmarshal_uint32 (pData, pDataLen, &dest->version);
452 _unmarshal_uint32 (pData, pDataLen, &dest->block_count);
453 _unmarshal_uint32 (pData, pDataLen, &dest->unknown);
454 _unmarshal_uint32 (pData, pDataLen, &dest->table_offset);
455 _unmarshal_uint64 (pData, pDataLen, &dest->uncompressed_len);
456 _unmarshal_uint64 (pData, pDataLen, &dest->compressed_len);
457 _unmarshal_uint64 (pData, pDataLen, &dest->block_len);
459 /* check structure */
460 if (dest->version != 2)
461 return 0;
463 return 1;
466 /* structure of LZXC control data block */
467 #define _CHM_LZXC_MIN_LEN (0x18)
468 #define _CHM_LZXC_V2_LEN (0x1c)
469 struct chmLzxcControlData
471 UInt32 size; /* 0 */
472 char signature[4]; /* 4 (LZXC) */
473 UInt32 version; /* 8 */
474 UInt32 resetInterval; /* c */
475 UInt32 windowSize; /* 10 */
476 UInt32 windowsPerReset; /* 14 */
477 UInt32 unknown_18; /* 18 */
480 static int _unmarshal_lzxc_control_data(unsigned char **pData,
481 unsigned long *pDataLen,
482 struct chmLzxcControlData *dest)
484 /* we want at least 0x18 bytes */
485 if (*pDataLen < _CHM_LZXC_MIN_LEN)
486 return 0;
488 /* unmarshal fields */
489 _unmarshal_uint32 (pData, pDataLen, &dest->size);
490 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
491 _unmarshal_uint32 (pData, pDataLen, &dest->version);
492 _unmarshal_uint32 (pData, pDataLen, &dest->resetInterval);
493 _unmarshal_uint32 (pData, pDataLen, &dest->windowSize);
494 _unmarshal_uint32 (pData, pDataLen, &dest->windowsPerReset);
496 if (*pDataLen >= _CHM_LZXC_V2_LEN)
497 _unmarshal_uint32 (pData, pDataLen, &dest->unknown_18);
498 else
499 dest->unknown_18 = 0;
501 if (dest->version == 2)
503 dest->resetInterval *= 0x8000;
504 dest->windowSize *= 0x8000;
506 if (dest->windowSize == 0 || dest->resetInterval == 0)
507 return 0;
509 /* for now, only support resetInterval a multiple of windowSize/2 */
510 if (dest->windowSize == 1)
511 return 0;
512 if ((dest->resetInterval % (dest->windowSize/2)) != 0)
513 return 0;
515 /* check structure */
516 if (memcmp(dest->signature, "LZXC", 4) != 0)
517 return 0;
519 return 1;
522 /* the structure used for chm file handles */
523 struct chmFile
525 HANDLE fd;
527 CRITICAL_SECTION mutex;
528 CRITICAL_SECTION lzx_mutex;
529 CRITICAL_SECTION cache_mutex;
531 UInt64 dir_offset;
532 UInt64 dir_len;
533 UInt64 data_offset;
534 Int32 index_root;
535 Int32 index_head;
536 UInt32 block_len;
538 UInt64 span;
539 struct chmUnitInfo rt_unit;
540 struct chmUnitInfo cn_unit;
541 struct chmLzxcResetTable reset_table;
543 /* LZX control data */
544 int compression_enabled;
545 UInt32 window_size;
546 UInt32 reset_interval;
547 UInt32 reset_blkcount;
549 /* decompressor state */
550 struct LZXstate *lzx_state;
551 int lzx_last_block;
553 /* cache for decompressed blocks */
554 UChar **cache_blocks;
555 Int64 *cache_block_indices;
556 Int32 cache_num_blocks;
560 * utility functions local to this module
563 /* utility function to handle differences between {pread,read}(64)? */
564 static Int64 _chm_fetch_bytes(struct chmFile *h,
565 UChar *buf,
566 UInt64 os,
567 Int64 len)
569 Int64 readLen=0;
570 if (h->fd == CHM_NULL_FD)
571 return readLen;
573 CHM_ACQUIRE_LOCK(h->mutex);
574 /* NOTE: this might be better done with CreateFileMapping, et cetera... */
576 DWORD origOffsetLo=0, origOffsetHi=0;
577 DWORD offsetLo, offsetHi;
578 DWORD actualLen=0;
580 /* awkward Win32 Seek/Tell */
581 offsetLo = (unsigned long)(os & 0xffffffffL);
582 offsetHi = (unsigned long)((os >> 32) & 0xffffffffL);
583 origOffsetLo = SetFilePointer(h->fd, 0, &origOffsetHi, FILE_CURRENT);
584 offsetLo = SetFilePointer(h->fd, offsetLo, &offsetHi, FILE_BEGIN);
586 /* read the data */
587 if (ReadFile(h->fd,
588 buf,
589 (DWORD)len,
590 &actualLen,
591 NULL))
592 readLen = actualLen;
593 else
594 readLen = 0;
596 /* restore original position */
597 SetFilePointer(h->fd, origOffsetLo, &origOffsetHi, FILE_BEGIN);
599 CHM_RELEASE_LOCK(h->mutex);
600 return readLen;
603 /* open an ITS archive */
604 struct chmFile *chm_openW(const WCHAR *filename)
606 unsigned char sbuffer[256];
607 unsigned long sremain;
608 unsigned char *sbufpos;
609 struct chmFile *newHandle=NULL;
610 struct chmItsfHeader itsfHeader;
611 struct chmItspHeader itspHeader;
612 #if 0
613 struct chmUnitInfo uiSpan;
614 #endif
615 struct chmUnitInfo uiLzxc;
616 struct chmLzxcControlData ctlData;
618 /* allocate handle */
619 newHandle = (struct chmFile *)malloc(sizeof(struct chmFile));
620 newHandle->fd = CHM_NULL_FD;
621 newHandle->lzx_state = NULL;
622 newHandle->cache_blocks = NULL;
623 newHandle->cache_block_indices = NULL;
624 newHandle->cache_num_blocks = 0;
626 /* open file */
627 if ((newHandle->fd=CreateFileW(filename,
628 GENERIC_READ,
629 FILE_SHARE_READ,
630 NULL,
631 OPEN_EXISTING,
632 FILE_ATTRIBUTE_NORMAL,
633 NULL)) == CHM_NULL_FD)
635 free(newHandle);
636 return NULL;
639 /* initialize mutexes, if needed */
640 InitializeCriticalSection(&newHandle->mutex);
641 InitializeCriticalSection(&newHandle->lzx_mutex);
642 InitializeCriticalSection(&newHandle->cache_mutex);
644 /* read and verify header */
645 sremain = _CHM_ITSF_V3_LEN;
646 sbufpos = sbuffer;
647 if (_chm_fetch_bytes(newHandle, sbuffer, (UInt64)0, sremain) != sremain ||
648 !_unmarshal_itsf_header(&sbufpos, &sremain, &itsfHeader))
650 chm_close(newHandle);
651 return NULL;
654 /* stash important values from header */
655 newHandle->dir_offset = itsfHeader.dir_offset;
656 newHandle->dir_len = itsfHeader.dir_len;
657 newHandle->data_offset = itsfHeader.data_offset;
659 /* now, read and verify the directory header chunk */
660 sremain = _CHM_ITSP_V1_LEN;
661 sbufpos = sbuffer;
662 if (_chm_fetch_bytes(newHandle, sbuffer,
663 (UInt64)itsfHeader.dir_offset, sremain) != sremain ||
664 !_unmarshal_itsp_header(&sbufpos, &sremain, &itspHeader))
666 chm_close(newHandle);
667 return NULL;
670 /* grab essential information from ITSP header */
671 newHandle->dir_offset += itspHeader.header_len;
672 newHandle->dir_len -= itspHeader.header_len;
673 newHandle->index_root = itspHeader.index_root;
674 newHandle->index_head = itspHeader.index_head;
675 newHandle->block_len = itspHeader.block_len;
677 /* if the index root is -1, this means we don't have any PMGI blocks.
678 * as a result, we must use the sole PMGL block as the index root
680 if (newHandle->index_root == -1)
681 newHandle->index_root = newHandle->index_head;
683 /* By default, compression is enabled. */
684 newHandle->compression_enabled = 1;
686 /* Jed, Sun Jun 27: 'span' doesn't seem to be used anywhere?! */
687 #if 0
688 /* fetch span */
689 if (CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
690 _CHMU_SPANINFO,
691 &uiSpan) ||
692 uiSpan.space == CHM_COMPRESSED)
694 chm_close(newHandle);
695 return NULL;
698 /* N.B.: we've already checked that uiSpan is in the uncompressed section,
699 * so this should not require attempting to decompress, which may
700 * rely on having a valid "span"
702 sremain = 8;
703 sbufpos = sbuffer;
704 if (chm_retrieve_object(newHandle, &uiSpan, sbuffer,
705 0, sremain) != sremain ||
706 !_unmarshal_uint64(&sbufpos, &sremain, &newHandle->span))
708 chm_close(newHandle);
709 return NULL;
711 #endif
713 /* prefetch most commonly needed unit infos */
714 if (CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
715 _CHMU_RESET_TABLE,
716 &newHandle->rt_unit) ||
717 newHandle->rt_unit.space == CHM_COMPRESSED ||
718 CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
719 _CHMU_CONTENT,
720 &newHandle->cn_unit) ||
721 newHandle->cn_unit.space == CHM_COMPRESSED ||
722 CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
723 _CHMU_LZXC_CONTROLDATA,
724 &uiLzxc) ||
725 uiLzxc.space == CHM_COMPRESSED)
727 newHandle->compression_enabled = 0;
730 /* read reset table info */
731 if (newHandle->compression_enabled)
733 sremain = _CHM_LZXC_RESETTABLE_V1_LEN;
734 sbufpos = sbuffer;
735 if (chm_retrieve_object(newHandle, &newHandle->rt_unit, sbuffer,
736 0, sremain) != sremain ||
737 !_unmarshal_lzxc_reset_table(&sbufpos, &sremain,
738 &newHandle->reset_table))
740 newHandle->compression_enabled = 0;
744 /* read control data */
745 if (newHandle->compression_enabled)
747 sremain = (unsigned long)uiLzxc.length;
748 sbufpos = sbuffer;
749 if (chm_retrieve_object(newHandle, &uiLzxc, sbuffer,
750 0, sremain) != sremain ||
751 !_unmarshal_lzxc_control_data(&sbufpos, &sremain,
752 &ctlData))
754 newHandle->compression_enabled = 0;
757 newHandle->window_size = ctlData.windowSize;
758 newHandle->reset_interval = ctlData.resetInterval;
760 /* Jed, Mon Jun 28: Experimentally, it appears that the reset block count */
761 /* must be multiplied by this formerly unknown ctrl data field in */
762 /* order to decompress some files. */
763 #if 0
764 newHandle->reset_blkcount = newHandle->reset_interval /
765 (newHandle->window_size / 2);
766 #else
767 newHandle->reset_blkcount = newHandle->reset_interval /
768 (newHandle->window_size / 2) *
769 ctlData.windowsPerReset;
770 #endif
773 /* initialize cache */
774 chm_set_param(newHandle, CHM_PARAM_MAX_BLOCKS_CACHED,
775 CHM_MAX_BLOCKS_CACHED);
777 return newHandle;
780 /* close an ITS archive */
781 void chm_close(struct chmFile *h)
783 if (h != NULL)
785 if (h->fd != CHM_NULL_FD)
786 CHM_CLOSE_FILE(h->fd);
787 h->fd = CHM_NULL_FD;
789 DeleteCriticalSection(&h->mutex);
790 DeleteCriticalSection(&h->lzx_mutex);
791 DeleteCriticalSection(&h->cache_mutex);
793 if (h->lzx_state)
794 LZXteardown(h->lzx_state);
795 h->lzx_state = NULL;
797 if (h->cache_blocks)
799 int i;
800 for (i=0; i<h->cache_num_blocks; i++)
802 if (h->cache_blocks[i])
803 free(h->cache_blocks[i]);
805 free(h->cache_blocks);
806 h->cache_blocks = NULL;
809 if (h->cache_block_indices)
810 free(h->cache_block_indices);
811 h->cache_block_indices = NULL;
813 free(h);
818 * set a parameter on the file handle.
819 * valid parameter types:
820 * CHM_PARAM_MAX_BLOCKS_CACHED:
821 * how many decompressed blocks should be cached? A simple
822 * caching scheme is used, wherein the index of the block is
823 * used as a hash value, and hash collision results in the
824 * invalidation of the previously cached block.
826 void chm_set_param(struct chmFile *h,
827 int paramType,
828 int paramVal)
830 switch (paramType)
832 case CHM_PARAM_MAX_BLOCKS_CACHED:
833 CHM_ACQUIRE_LOCK(h->cache_mutex);
834 if (paramVal != h->cache_num_blocks)
836 UChar **newBlocks;
837 UInt64 *newIndices;
838 int i;
840 /* allocate new cached blocks */
841 newBlocks = (UChar **)malloc(paramVal * sizeof (UChar *));
842 newIndices = (UInt64 *)malloc(paramVal * sizeof (UInt64));
843 for (i=0; i<paramVal; i++)
845 newBlocks[i] = NULL;
846 newIndices[i] = 0;
849 /* re-distribute old cached blocks */
850 if (h->cache_blocks)
852 for (i=0; i<h->cache_num_blocks; i++)
854 int newSlot = (int)(h->cache_block_indices[i] % paramVal);
856 if (h->cache_blocks[i])
858 /* in case of collision, destroy newcomer */
859 if (newBlocks[newSlot])
861 free(h->cache_blocks[i]);
862 h->cache_blocks[i] = NULL;
864 else
866 newBlocks[newSlot] = h->cache_blocks[i];
867 newIndices[newSlot] =
868 h->cache_block_indices[i];
873 free(h->cache_blocks);
874 free(h->cache_block_indices);
877 /* now, set new values */
878 h->cache_blocks = newBlocks;
879 h->cache_block_indices = newIndices;
880 h->cache_num_blocks = paramVal;
882 CHM_RELEASE_LOCK(h->cache_mutex);
883 break;
885 default:
886 break;
891 * helper methods for chm_resolve_object
894 /* skip a compressed dword */
895 static void _chm_skip_cword(UChar **pEntry)
897 while (*(*pEntry)++ >= 0x80)
901 /* skip the data from a PMGL entry */
902 static void _chm_skip_PMGL_entry_data(UChar **pEntry)
904 _chm_skip_cword(pEntry);
905 _chm_skip_cword(pEntry);
906 _chm_skip_cword(pEntry);
909 /* parse a compressed dword */
910 static UInt64 _chm_parse_cword(UChar **pEntry)
912 UInt64 accum = 0;
913 UChar temp;
914 while ((temp=*(*pEntry)++) >= 0x80)
916 accum <<= 7;
917 accum += temp & 0x7f;
920 return (accum << 7) + temp;
923 /* parse a utf-8 string into an ASCII char buffer */
924 static int _chm_parse_UTF8(UChar **pEntry, UInt64 count, WCHAR *path)
926 /* MJM - Modified to return real Unicode strings */
927 while (count != 0)
929 *path++ = (*(*pEntry)++);
930 --count;
933 *path = '\0';
934 return 1;
937 /* parse a PMGL entry into a chmUnitInfo struct; return 1 on success. */
938 static int _chm_parse_PMGL_entry(UChar **pEntry, struct chmUnitInfo *ui)
940 UInt64 strLen;
942 /* parse str len */
943 strLen = _chm_parse_cword(pEntry);
944 if (strLen > CHM_MAX_PATHLEN)
945 return 0;
947 /* parse path */
948 if (! _chm_parse_UTF8(pEntry, strLen, ui->path))
949 return 0;
951 /* parse info */
952 ui->space = (int)_chm_parse_cword(pEntry);
953 ui->start = _chm_parse_cword(pEntry);
954 ui->length = _chm_parse_cword(pEntry);
955 return 1;
958 /* find an exact entry in PMGL; return NULL if we fail */
959 static UChar *_chm_find_in_PMGL(UChar *page_buf,
960 UInt32 block_len,
961 const WCHAR *objPath)
963 /* XXX: modify this to do a binary search using the nice index structure
964 * that is provided for us.
966 struct chmPmglHeader header;
967 UInt32 hremain;
968 UChar *end;
969 UChar *cur;
970 UChar *temp;
971 UInt64 strLen;
972 WCHAR buffer[CHM_MAX_PATHLEN+1];
974 /* figure out where to start and end */
975 cur = page_buf;
976 hremain = _CHM_PMGL_LEN;
977 if (! _unmarshal_pmgl_header(&cur, &hremain, &header))
978 return NULL;
979 end = page_buf + block_len - (header.free_space);
981 /* now, scan progressively */
982 while (cur < end)
984 /* grab the name */
985 temp = cur;
986 strLen = _chm_parse_cword(&cur);
987 if (! _chm_parse_UTF8(&cur, strLen, buffer))
988 return NULL;
990 /* check if it is the right name */
991 if (! strcmpiW(buffer, objPath))
992 return temp;
994 _chm_skip_PMGL_entry_data(&cur);
997 return NULL;
1000 /* find which block should be searched next for the entry; -1 if no block */
1001 static Int32 _chm_find_in_PMGI(UChar *page_buf,
1002 UInt32 block_len,
1003 const WCHAR *objPath)
1005 /* XXX: modify this to do a binary search using the nice index structure
1006 * that is provided for us
1008 struct chmPmgiHeader header;
1009 UInt32 hremain;
1010 int page=-1;
1011 UChar *end;
1012 UChar *cur;
1013 UInt64 strLen;
1014 WCHAR buffer[CHM_MAX_PATHLEN+1];
1016 /* figure out where to start and end */
1017 cur = page_buf;
1018 hremain = _CHM_PMGI_LEN;
1019 if (! _unmarshal_pmgi_header(&cur, &hremain, &header))
1020 return -1;
1021 end = page_buf + block_len - (header.free_space);
1023 /* now, scan progressively */
1024 while (cur < end)
1026 /* grab the name */
1027 strLen = _chm_parse_cword(&cur);
1028 if (! _chm_parse_UTF8(&cur, strLen, buffer))
1029 return -1;
1031 /* check if it is the right name */
1032 if (strcmpiW(buffer, objPath) > 0)
1033 return page;
1035 /* load next value for path */
1036 page = (int)_chm_parse_cword(&cur);
1039 return page;
1042 /* resolve a particular object from the archive */
1043 int chm_resolve_object(struct chmFile *h,
1044 const WCHAR *objPath,
1045 struct chmUnitInfo *ui)
1048 * XXX: implement caching scheme for dir pages
1051 Int32 curPage;
1053 /* buffer to hold whatever page we're looking at */
1054 UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, h->block_len);
1056 /* starting page */
1057 curPage = h->index_root;
1059 /* until we have either returned or given up */
1060 while (curPage != -1)
1063 /* try to fetch the index page */
1064 if (_chm_fetch_bytes(h, page_buf,
1065 (UInt64)h->dir_offset + (UInt64)curPage*h->block_len,
1066 h->block_len) != h->block_len)
1068 HeapFree(GetProcessHeap(), 0, page_buf);
1069 return CHM_RESOLVE_FAILURE;
1072 /* now, if it is a leaf node: */
1073 if (memcmp(page_buf, _chm_pmgl_marker, 4) == 0)
1075 /* scan block */
1076 UChar *pEntry = _chm_find_in_PMGL(page_buf,
1077 h->block_len,
1078 objPath);
1079 if (pEntry == NULL)
1081 HeapFree(GetProcessHeap(), 0, page_buf);
1082 return CHM_RESOLVE_FAILURE;
1085 /* parse entry and return */
1086 _chm_parse_PMGL_entry(&pEntry, ui);
1087 HeapFree(GetProcessHeap(), 0, page_buf);
1088 return CHM_RESOLVE_SUCCESS;
1091 /* else, if it is a branch node: */
1092 else if (memcmp(page_buf, _chm_pmgi_marker, 4) == 0)
1093 curPage = _chm_find_in_PMGI(page_buf, h->block_len, objPath);
1095 /* else, we are confused. give up. */
1096 else
1098 HeapFree(GetProcessHeap(), 0, page_buf);
1099 return CHM_RESOLVE_FAILURE;
1103 /* didn't find anything. fail. */
1104 HeapFree(GetProcessHeap(), 0, page_buf);
1105 return CHM_RESOLVE_FAILURE;
1109 * utility methods for dealing with compressed data
1112 /* get the bounds of a compressed block. return 0 on failure */
1113 static int _chm_get_cmpblock_bounds(struct chmFile *h,
1114 UInt64 block,
1115 UInt64 *start,
1116 Int64 *len)
1118 UChar buffer[8], *dummy;
1119 UInt32 remain;
1121 /* for all but the last block, use the reset table */
1122 if (block < h->reset_table.block_count-1)
1124 /* unpack the start address */
1125 dummy = buffer;
1126 remain = 8;
1127 if (_chm_fetch_bytes(h, buffer,
1128 (UInt64)h->data_offset
1129 + (UInt64)h->rt_unit.start
1130 + (UInt64)h->reset_table.table_offset
1131 + (UInt64)block*8,
1132 remain) != remain ||
1133 !_unmarshal_uint64(&dummy, &remain, start))
1134 return 0;
1136 /* unpack the end address */
1137 dummy = buffer;
1138 remain = 8;
1139 if (_chm_fetch_bytes(h, buffer,
1140 (UInt64)h->data_offset
1141 + (UInt64)h->rt_unit.start
1142 + (UInt64)h->reset_table.table_offset
1143 + (UInt64)block*8 + 8,
1144 remain) != remain ||
1145 !_unmarshal_int64(&dummy, &remain, len))
1146 return 0;
1149 /* for the last block, use the span in addition to the reset table */
1150 else
1152 /* unpack the start address */
1153 dummy = buffer;
1154 remain = 8;
1155 if (_chm_fetch_bytes(h, buffer,
1156 (UInt64)h->data_offset
1157 + (UInt64)h->rt_unit.start
1158 + (UInt64)h->reset_table.table_offset
1159 + (UInt64)block*8,
1160 remain) != remain ||
1161 !_unmarshal_uint64(&dummy, &remain, start))
1162 return 0;
1164 *len = h->reset_table.compressed_len;
1167 /* compute the length and absolute start address */
1168 *len -= *start;
1169 *start += h->data_offset + h->cn_unit.start;
1171 return 1;
1174 /* decompress the block. must have lzx_mutex. */
1175 static Int64 _chm_decompress_block(struct chmFile *h,
1176 UInt64 block,
1177 UChar **ubuffer)
1179 UChar *cbuffer = HeapAlloc( GetProcessHeap(), 0,
1180 ((unsigned int)h->reset_table.block_len + 6144));
1181 UInt64 cmpStart; /* compressed start */
1182 Int64 cmpLen; /* compressed len */
1183 int indexSlot; /* cache index slot */
1184 UChar *lbuffer; /* local buffer ptr */
1185 UInt32 blockAlign = (UInt32)(block % h->reset_blkcount); /* reset intvl. aln. */
1186 UInt32 i; /* local loop index */
1188 /* let the caching system pull its weight! */
1189 if (block - blockAlign <= h->lzx_last_block &&
1190 block >= h->lzx_last_block)
1191 blockAlign = (block - h->lzx_last_block);
1193 /* check if we need previous blocks */
1194 if (blockAlign != 0)
1196 /* fetch all required previous blocks since last reset */
1197 for (i = blockAlign; i > 0; i--)
1199 UInt32 curBlockIdx = block - i;
1201 /* check if we most recently decompressed the previous block */
1202 if (h->lzx_last_block != curBlockIdx)
1204 if ((curBlockIdx % h->reset_blkcount) == 0)
1206 #ifdef CHM_DEBUG
1207 fprintf(stderr, "***RESET (1)***\n");
1208 #endif
1209 LZXreset(h->lzx_state);
1212 indexSlot = (int)((curBlockIdx) % h->cache_num_blocks);
1213 h->cache_block_indices[indexSlot] = curBlockIdx;
1214 if (! h->cache_blocks[indexSlot])
1215 h->cache_blocks[indexSlot] = (UChar *)malloc(
1216 (unsigned int)(h->reset_table.block_len));
1217 lbuffer = h->cache_blocks[indexSlot];
1219 /* decompress the previous block */
1220 #ifdef CHM_DEBUG
1221 fprintf(stderr, "Decompressing block #%4d (EXTRA)\n", curBlockIdx);
1222 #endif
1223 if (!_chm_get_cmpblock_bounds(h, curBlockIdx, &cmpStart, &cmpLen) ||
1224 _chm_fetch_bytes(h, cbuffer, cmpStart, cmpLen) != cmpLen ||
1225 LZXdecompress(h->lzx_state, cbuffer, lbuffer, (int)cmpLen,
1226 (int)h->reset_table.block_len) != DECR_OK)
1228 #ifdef CHM_DEBUG
1229 fprintf(stderr, " (DECOMPRESS FAILED!)\n");
1230 #endif
1231 HeapFree(GetProcessHeap(), 0, cbuffer);
1232 return (Int64)0;
1235 h->lzx_last_block = (int)curBlockIdx;
1239 else
1241 if ((block % h->reset_blkcount) == 0)
1243 #ifdef CHM_DEBUG
1244 fprintf(stderr, "***RESET (2)***\n");
1245 #endif
1246 LZXreset(h->lzx_state);
1250 /* allocate slot in cache */
1251 indexSlot = (int)(block % h->cache_num_blocks);
1252 h->cache_block_indices[indexSlot] = block;
1253 if (! h->cache_blocks[indexSlot])
1254 h->cache_blocks[indexSlot] = (UChar *)malloc(
1255 ((unsigned int)h->reset_table.block_len));
1256 lbuffer = h->cache_blocks[indexSlot];
1257 *ubuffer = lbuffer;
1259 /* decompress the block we actually want */
1260 #ifdef CHM_DEBUG
1261 fprintf(stderr, "Decompressing block #%4d (REAL )\n", block);
1262 #endif
1263 if (! _chm_get_cmpblock_bounds(h, block, &cmpStart, &cmpLen) ||
1264 _chm_fetch_bytes(h, cbuffer, cmpStart, cmpLen) != cmpLen ||
1265 LZXdecompress(h->lzx_state, cbuffer, lbuffer, (int)cmpLen,
1266 (int)h->reset_table.block_len) != DECR_OK)
1268 #ifdef CHM_DEBUG
1269 fprintf(stderr, " (DECOMPRESS FAILED!)\n");
1270 #endif
1271 HeapFree(GetProcessHeap(), 0, cbuffer);
1272 return (Int64)0;
1274 h->lzx_last_block = (int)block;
1276 /* XXX: modify LZX routines to return the length of the data they
1277 * decompressed and return that instead, for an extra sanity check.
1279 HeapFree(GetProcessHeap(), 0, cbuffer);
1280 return h->reset_table.block_len;
1283 /* grab a region from a compressed block */
1284 static Int64 _chm_decompress_region(struct chmFile *h,
1285 UChar *buf,
1286 UInt64 start,
1287 Int64 len)
1289 UInt64 nBlock, nOffset;
1290 UInt64 nLen;
1291 UInt64 gotLen;
1292 UChar *ubuffer;
1294 if (len <= 0)
1295 return (Int64)0;
1297 /* figure out what we need to read */
1298 nBlock = start / h->reset_table.block_len;
1299 nOffset = start % h->reset_table.block_len;
1300 nLen = len;
1301 if (nLen > (h->reset_table.block_len - nOffset))
1302 nLen = h->reset_table.block_len - nOffset;
1304 /* if block is cached, return data from it. */
1305 CHM_ACQUIRE_LOCK(h->lzx_mutex);
1306 CHM_ACQUIRE_LOCK(h->cache_mutex);
1307 if (h->cache_block_indices[nBlock % h->cache_num_blocks] == nBlock &&
1308 h->cache_blocks[nBlock % h->cache_num_blocks] != NULL)
1310 memcpy(buf,
1311 h->cache_blocks[nBlock % h->cache_num_blocks] + nOffset,
1312 (unsigned int)nLen);
1313 CHM_RELEASE_LOCK(h->cache_mutex);
1314 CHM_RELEASE_LOCK(h->lzx_mutex);
1315 return nLen;
1317 CHM_RELEASE_LOCK(h->cache_mutex);
1319 /* data request not satisfied, so... start up the decompressor machine */
1320 if (! h->lzx_state)
1322 int window_size = ffs(h->window_size) - 1;
1323 h->lzx_last_block = -1;
1324 h->lzx_state = LZXinit(window_size);
1327 /* decompress some data */
1328 gotLen = _chm_decompress_block(h, nBlock, &ubuffer);
1329 if (gotLen < nLen)
1330 nLen = gotLen;
1331 memcpy(buf, ubuffer+nOffset, (unsigned int)nLen);
1332 CHM_RELEASE_LOCK(h->lzx_mutex);
1333 return nLen;
1336 /* retrieve (part of) an object */
1337 LONGINT64 chm_retrieve_object(struct chmFile *h,
1338 struct chmUnitInfo *ui,
1339 unsigned char *buf,
1340 LONGUINT64 addr,
1341 LONGINT64 len)
1343 /* must be valid file handle */
1344 if (h == NULL)
1345 return (Int64)0;
1347 /* starting address must be in correct range */
1348 if (addr < 0 || addr >= ui->length)
1349 return (Int64)0;
1351 /* clip length */
1352 if (addr + len > ui->length)
1353 len = ui->length - addr;
1355 /* if the file is uncompressed, it's simple */
1356 if (ui->space == CHM_UNCOMPRESSED)
1358 /* read data */
1359 return _chm_fetch_bytes(h,
1360 buf,
1361 (UInt64)h->data_offset + (UInt64)ui->start + (UInt64)addr,
1362 len);
1365 /* else if the file is compressed, it's a little trickier */
1366 else /* ui->space == CHM_COMPRESSED */
1368 Int64 swath=0, total=0;
1370 /* if compression is not enabled for this file... */
1371 if (! h->compression_enabled)
1372 return total;
1374 do {
1376 /* swill another mouthful */
1377 swath = _chm_decompress_region(h, buf, ui->start + addr, len);
1379 /* if we didn't get any... */
1380 if (swath == 0)
1381 return total;
1383 /* update stats */
1384 total += swath;
1385 len -= swath;
1386 addr += swath;
1387 buf += swath;
1389 } while (len != 0);
1391 return total;
1395 /* enumerate the objects in the .chm archive */
1396 int chm_enumerate(struct chmFile *h,
1397 int what,
1398 CHM_ENUMERATOR e,
1399 void *context)
1401 Int32 curPage;
1403 /* buffer to hold whatever page we're looking at */
1404 UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, (unsigned int)h->block_len);
1405 struct chmPmglHeader header;
1406 UChar *end;
1407 UChar *cur;
1408 unsigned long lenRemain;
1409 UInt64 ui_path_len;
1411 /* the current ui */
1412 struct chmUnitInfo ui;
1413 int flag;
1415 /* starting page */
1416 curPage = h->index_head;
1418 /* until we have either returned or given up */
1419 while (curPage != -1)
1422 /* try to fetch the index page */
1423 if (_chm_fetch_bytes(h,
1424 page_buf,
1425 (UInt64)h->dir_offset + (UInt64)curPage*h->block_len,
1426 h->block_len) != h->block_len)
1428 HeapFree(GetProcessHeap(), 0, page_buf);
1429 return 0;
1432 /* figure out start and end for this page */
1433 cur = page_buf;
1434 lenRemain = _CHM_PMGL_LEN;
1435 if (! _unmarshal_pmgl_header(&cur, &lenRemain, &header))
1437 HeapFree(GetProcessHeap(), 0, page_buf);
1438 return 0;
1440 end = page_buf + h->block_len - (header.free_space);
1442 /* loop over this page */
1443 while (cur < end)
1445 if (! _chm_parse_PMGL_entry(&cur, &ui))
1447 HeapFree(GetProcessHeap(), 0, page_buf);
1448 return 0;
1451 /* get the length of the path */
1452 ui_path_len = strlenW(ui.path)-1;
1454 /* check for DIRS */
1455 if (ui.path[ui_path_len] == '/' && !(what & CHM_ENUMERATE_DIRS))
1456 continue;
1458 /* check for FILES */
1459 if (ui.path[ui_path_len] != '/' && !(what & CHM_ENUMERATE_FILES))
1460 continue;
1462 /* check for NORMAL vs. META */
1463 if (ui.path[0] == '/')
1466 /* check for NORMAL vs. SPECIAL */
1467 if (ui.path[1] == '#' || ui.path[1] == '$')
1468 flag = CHM_ENUMERATE_SPECIAL;
1469 else
1470 flag = CHM_ENUMERATE_NORMAL;
1472 else
1473 flag = CHM_ENUMERATE_META;
1474 if (! (what & flag))
1475 continue;
1477 /* call the enumerator */
1479 int status = (*e)(h, &ui, context);
1480 switch (status)
1482 case CHM_ENUMERATOR_FAILURE:
1483 HeapFree(GetProcessHeap(), 0, page_buf);
1484 return 0;
1485 case CHM_ENUMERATOR_CONTINUE:
1486 break;
1487 case CHM_ENUMERATOR_SUCCESS:
1488 HeapFree(GetProcessHeap(), 0, page_buf);
1489 return 1;
1490 default:
1491 break;
1496 /* advance to next page */
1497 curPage = header.block_next;
1500 HeapFree(GetProcessHeap(), 0, page_buf);
1501 return 1;
1504 int chm_enumerate_dir(struct chmFile *h,
1505 const WCHAR *prefix,
1506 int what,
1507 CHM_ENUMERATOR e,
1508 void *context)
1511 * XXX: do this efficiently (i.e. using the tree index)
1514 Int32 curPage;
1516 /* buffer to hold whatever page we're looking at */
1517 UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, (unsigned int)h->block_len);
1518 struct chmPmglHeader header;
1519 UChar *end;
1520 UChar *cur;
1521 unsigned long lenRemain;
1523 /* set to 1 once we've started */
1524 int it_has_begun=0;
1526 /* the current ui */
1527 struct chmUnitInfo ui;
1528 int flag;
1529 UInt64 ui_path_len;
1531 /* the length of the prefix */
1532 WCHAR prefixRectified[CHM_MAX_PATHLEN+1];
1533 int prefixLen;
1534 WCHAR lastPath[CHM_MAX_PATHLEN];
1535 int lastPathLen;
1537 /* starting page */
1538 curPage = h->index_head;
1540 /* initialize pathname state */
1541 strncpyW(prefixRectified, prefix, CHM_MAX_PATHLEN);
1542 prefixLen = strlenW(prefixRectified);
1543 if (prefixLen != 0)
1545 if (prefixRectified[prefixLen-1] != '/')
1547 prefixRectified[prefixLen] = '/';
1548 prefixRectified[prefixLen+1] = '\0';
1549 ++prefixLen;
1552 lastPath[0] = '\0';
1553 lastPathLen = -1;
1555 /* until we have either returned or given up */
1556 while (curPage != -1)
1559 /* try to fetch the index page */
1560 if (_chm_fetch_bytes(h,
1561 page_buf,
1562 (UInt64)h->dir_offset + (UInt64)curPage*h->block_len,
1563 h->block_len) != h->block_len)
1565 HeapFree(GetProcessHeap(), 0, page_buf);
1566 return 0;
1569 /* figure out start and end for this page */
1570 cur = page_buf;
1571 lenRemain = _CHM_PMGL_LEN;
1572 if (! _unmarshal_pmgl_header(&cur, &lenRemain, &header))
1574 HeapFree(GetProcessHeap(), 0, page_buf);
1575 return 0;
1577 end = page_buf + h->block_len - (header.free_space);
1579 /* loop over this page */
1580 while (cur < end)
1582 if (! _chm_parse_PMGL_entry(&cur, &ui))
1584 HeapFree(GetProcessHeap(), 0, page_buf);
1585 return 0;
1588 /* check if we should start */
1589 if (! it_has_begun)
1591 if (ui.length == 0 && strncmpiW(ui.path, prefixRectified, prefixLen) == 0)
1592 it_has_begun = 1;
1593 else
1594 continue;
1596 if (ui.path[prefixLen] == '\0')
1597 continue;
1600 /* check if we should stop */
1601 else
1603 if (strncmpiW(ui.path, prefixRectified, prefixLen) != 0)
1605 HeapFree(GetProcessHeap(), 0, page_buf);
1606 return 1;
1610 /* check if we should include this path */
1611 if (lastPathLen != -1)
1613 if (strncmpiW(ui.path, lastPath, lastPathLen) == 0)
1614 continue;
1616 strcpyW(lastPath, ui.path);
1617 lastPathLen = strlenW(lastPath);
1619 /* get the length of the path */
1620 ui_path_len = strlenW(ui.path)-1;
1622 /* check for DIRS */
1623 if (ui.path[ui_path_len] == '/' && !(what & CHM_ENUMERATE_DIRS))
1624 continue;
1626 /* check for FILES */
1627 if (ui.path[ui_path_len] != '/' && !(what & CHM_ENUMERATE_FILES))
1628 continue;
1630 /* check for NORMAL vs. META */
1631 if (ui.path[0] == '/')
1634 /* check for NORMAL vs. SPECIAL */
1635 if (ui.path[1] == '#' || ui.path[1] == '$')
1636 flag = CHM_ENUMERATE_SPECIAL;
1637 else
1638 flag = CHM_ENUMERATE_NORMAL;
1640 else
1641 flag = CHM_ENUMERATE_META;
1642 if (! (what & flag))
1643 continue;
1645 /* call the enumerator */
1647 int status = (*e)(h, &ui, context);
1648 switch (status)
1650 case CHM_ENUMERATOR_FAILURE:
1651 HeapFree(GetProcessHeap(), 0, page_buf);
1652 return 0;
1653 case CHM_ENUMERATOR_CONTINUE:
1654 break;
1655 case CHM_ENUMERATOR_SUCCESS:
1656 HeapFree(GetProcessHeap(), 0, page_buf);
1657 return 1;
1658 default:
1659 break;
1664 /* advance to next page */
1665 curPage = header.block_next;
1668 HeapFree(GetProcessHeap(), 0, page_buf);
1669 return 1;