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