Sync with trunk revision 64099.
[reactos.git] / dll / win32 / itss / chm_lib.c
1 /***************************************************************************
2 * chm_lib.c - CHM archive manipulation routines *
3 * ------------------- *
4 * *
5 * author: Jed Wing <jedwin@ugcs.caltech.edu> *
6 * version: 0.3 *
7 * notes: These routines are meant for the manipulation of microsoft *
8 * .chm (compiled html help) files, but may likely be used *
9 * for the manipulation of any ITSS archive, if ever ITSS *
10 * archives are used for any other purpose. *
11 * *
12 * Note also that the section names are statically handled. *
13 * To be entirely correct, the section names should be read *
14 * from the section names meta-file, and then the various *
15 * content sections and the "transforms" to apply to the data *
16 * they contain should be inferred from the section name and *
17 * the meta-files referenced using that name; however, all of *
18 * the files I've been able to get my hands on appear to have *
19 * only two sections: Uncompressed and MSCompressed. *
20 * Additionally, the ITSS.DLL file included with Windows does *
21 * not appear to handle any different transforms than the *
22 * simple LZX-transform. Furthermore, the list of transforms *
23 * to apply is broken, in that only half the required space *
24 * is allocated for the list. (It appears as though the *
25 * space is allocated for ASCII strings, but the strings are *
26 * written as unicode. As a result, only the first half of *
27 * the string appears.) So this is probably not too big of *
28 * a deal, at least until CHM v4 (MS .lit files), which also *
29 * incorporate encryption, of some description. *
30 * *
31 ***************************************************************************/
32
33 /***************************************************************************
34 *
35 * This library is free software; you can redistribute it and/or
36 * modify it under the terms of the GNU Lesser General Public
37 * License as published by the Free Software Foundation; either
38 * version 2.1 of the License, or (at your option) any later version.
39 *
40 * This library is distributed in the hope that it will be useful,
41 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43 * Lesser General Public License for more details.
44 *
45 * You should have received a copy of the GNU Lesser General Public
46 * License along with this library; if not, write to the Free Software
47 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
48 *
49 ***************************************************************************/
50
51 /***************************************************************************
52 * *
53 * Adapted for Wine by Mike McCormack *
54 * *
55 ***************************************************************************/
56
57 #include "precomp.h"
58
59 #define CHM_ACQUIRE_LOCK(a) do { \
60 EnterCriticalSection(&(a)); \
61 } while(0)
62 #define CHM_RELEASE_LOCK(a) do { \
63 LeaveCriticalSection(&(a)); \
64 } while(0)
65
66 #define CHM_NULL_FD (INVALID_HANDLE_VALUE)
67 #define CHM_CLOSE_FILE(fd) CloseHandle((fd))
68
69 /*
70 * defines related to tuning
71 */
72 #ifndef CHM_MAX_BLOCKS_CACHED
73 #define CHM_MAX_BLOCKS_CACHED 5
74 #endif
75 #define CHM_PARAM_MAX_BLOCKS_CACHED 0
76
77 /*
78 * architecture specific defines
79 *
80 * Note: as soon as C99 is more widespread, the below defines should
81 * probably just use the C99 sized-int types.
82 *
83 * The following settings will probably work for many platforms. The sizes
84 * don't have to be exactly correct, but the types must accommodate at least as
85 * many bits as they specify.
86 */
87
88 /* i386, 32-bit, Windows */
89 typedef BYTE UChar;
90 typedef SHORT Int16;
91 typedef USHORT UInt16;
92 typedef LONG Int32;
93 typedef DWORD UInt32;
94 typedef LONGLONG Int64;
95 typedef ULONGLONG UInt64;
96
97 /* utilities for unmarshalling data */
98 static BOOL _unmarshal_char_array(unsigned char **pData,
99 unsigned int *pLenRemain,
100 char *dest,
101 int count)
102 {
103 if (count <= 0 || (unsigned int)count > *pLenRemain)
104 return FALSE;
105 memcpy(dest, (*pData), count);
106 *pData += count;
107 *pLenRemain -= count;
108 return TRUE;
109 }
110
111 static BOOL _unmarshal_uchar_array(unsigned char **pData,
112 unsigned int *pLenRemain,
113 unsigned char *dest,
114 int count)
115 {
116 if (count <= 0 || (unsigned int)count > *pLenRemain)
117 return FALSE;
118 memcpy(dest, (*pData), count);
119 *pData += count;
120 *pLenRemain -= count;
121 return TRUE;
122 }
123
124 static BOOL _unmarshal_int32(unsigned char **pData,
125 unsigned int *pLenRemain,
126 Int32 *dest)
127 {
128 if (4 > *pLenRemain)
129 return FALSE;
130 *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
131 *pData += 4;
132 *pLenRemain -= 4;
133 return TRUE;
134 }
135
136 static BOOL _unmarshal_uint32(unsigned char **pData,
137 unsigned int *pLenRemain,
138 UInt32 *dest)
139 {
140 if (4 > *pLenRemain)
141 return FALSE;
142 *dest = (*pData)[0] | (*pData)[1]<<8 | (*pData)[2]<<16 | (*pData)[3]<<24;
143 *pData += 4;
144 *pLenRemain -= 4;
145 return TRUE;
146 }
147
148 static BOOL _unmarshal_int64(unsigned char **pData,
149 unsigned int *pLenRemain,
150 Int64 *dest)
151 {
152 Int64 temp;
153 int i;
154 if (8 > *pLenRemain)
155 return FALSE;
156 temp=0;
157 for(i=8; i>0; i--)
158 {
159 temp <<= 8;
160 temp |= (*pData)[i-1];
161 }
162 *dest = temp;
163 *pData += 8;
164 *pLenRemain -= 8;
165 return TRUE;
166 }
167
168 static BOOL _unmarshal_uint64(unsigned char **pData,
169 unsigned int *pLenRemain,
170 UInt64 *dest)
171 {
172 UInt64 temp;
173 int i;
174 if (8 > *pLenRemain)
175 return FALSE;
176 temp=0;
177 for(i=8; i>0; i--)
178 {
179 temp <<= 8;
180 temp |= (*pData)[i-1];
181 }
182 *dest = temp;
183 *pData += 8;
184 *pLenRemain -= 8;
185 return TRUE;
186 }
187
188 static BOOL _unmarshal_uuid(unsigned char **pData,
189 unsigned int *pDataLen,
190 unsigned char *dest)
191 {
192 return _unmarshal_uchar_array(pData, pDataLen, dest, 16);
193 }
194
195 /* names of sections essential to decompression */
196 static const WCHAR _CHMU_RESET_TABLE[] = {
197 ':',':','D','a','t','a','S','p','a','c','e','/',
198 'S','t','o','r','a','g','e','/',
199 'M','S','C','o','m','p','r','e','s','s','e','d','/',
200 'T','r','a','n','s','f','o','r','m','/',
201 '{','7','F','C','2','8','9','4','0','-','9','D','3','1',
202 '-','1','1','D','0','-','9','B','2','7','-',
203 '0','0','A','0','C','9','1','E','9','C','7','C','}','/',
204 'I','n','s','t','a','n','c','e','D','a','t','a','/',
205 'R','e','s','e','t','T','a','b','l','e',0
206 };
207 static const WCHAR _CHMU_LZXC_CONTROLDATA[] = {
208 ':',':','D','a','t','a','S','p','a','c','e','/',
209 'S','t','o','r','a','g','e','/',
210 'M','S','C','o','m','p','r','e','s','s','e','d','/',
211 'C','o','n','t','r','o','l','D','a','t','a',0
212 };
213 static const WCHAR _CHMU_CONTENT[] = {
214 ':',':','D','a','t','a','S','p','a','c','e','/',
215 'S','t','o','r','a','g','e','/',
216 'M','S','C','o','m','p','r','e','s','s','e','d','/',
217 'C','o','n','t','e','n','t',0
218 };
219
220 /*
221 * structures local to this module
222 */
223
224 /* structure of ITSF headers */
225 #define _CHM_ITSF_V2_LEN (0x58)
226 #define _CHM_ITSF_V3_LEN (0x60)
227 struct chmItsfHeader
228 {
229 char signature[4]; /* 0 (ITSF) */
230 Int32 version; /* 4 */
231 Int32 header_len; /* 8 */
232 Int32 unknown_000c; /* c */
233 UInt32 last_modified; /* 10 */
234 UInt32 lang_id; /* 14 */
235 UChar dir_uuid[16]; /* 18 */
236 UChar stream_uuid[16]; /* 28 */
237 UInt64 unknown_offset; /* 38 */
238 UInt64 unknown_len; /* 40 */
239 UInt64 dir_offset; /* 48 */
240 UInt64 dir_len; /* 50 */
241 UInt64 data_offset; /* 58 (Not present before V3) */
242 }; /* __attribute__ ((aligned (1))); */
243
244 static BOOL _unmarshal_itsf_header(unsigned char **pData,
245 unsigned int *pDataLen,
246 struct chmItsfHeader *dest)
247 {
248 /* we only know how to deal with the 0x58 and 0x60 byte structures */
249 if (*pDataLen != _CHM_ITSF_V2_LEN && *pDataLen != _CHM_ITSF_V3_LEN)
250 return FALSE;
251
252 /* unmarshal common fields */
253 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
254 _unmarshal_int32 (pData, pDataLen, &dest->version);
255 _unmarshal_int32 (pData, pDataLen, &dest->header_len);
256 _unmarshal_int32 (pData, pDataLen, &dest->unknown_000c);
257 _unmarshal_uint32 (pData, pDataLen, &dest->last_modified);
258 _unmarshal_uint32 (pData, pDataLen, &dest->lang_id);
259 _unmarshal_uuid (pData, pDataLen, dest->dir_uuid);
260 _unmarshal_uuid (pData, pDataLen, dest->stream_uuid);
261 _unmarshal_uint64 (pData, pDataLen, &dest->unknown_offset);
262 _unmarshal_uint64 (pData, pDataLen, &dest->unknown_len);
263 _unmarshal_uint64 (pData, pDataLen, &dest->dir_offset);
264 _unmarshal_uint64 (pData, pDataLen, &dest->dir_len);
265
266 /* error check the data */
267 /* XXX: should also check UUIDs, probably, though with a version 3 file,
268 * current MS tools do not seem to use them.
269 */
270 if (memcmp(dest->signature, "ITSF", 4) != 0)
271 return FALSE;
272 if (dest->version == 2)
273 {
274 if (dest->header_len < _CHM_ITSF_V2_LEN)
275 return FALSE;
276 }
277 else if (dest->version == 3)
278 {
279 if (dest->header_len < _CHM_ITSF_V3_LEN)
280 return FALSE;
281 }
282 else
283 return FALSE;
284
285 /* now, if we have a V3 structure, unmarshal the rest.
286 * otherwise, compute it
287 */
288 if (dest->version == 3)
289 {
290 if (*pDataLen != 0)
291 _unmarshal_uint64(pData, pDataLen, &dest->data_offset);
292 else
293 return FALSE;
294 }
295 else
296 dest->data_offset = dest->dir_offset + dest->dir_len;
297
298 return TRUE;
299 }
300
301 /* structure of ITSP headers */
302 #define _CHM_ITSP_V1_LEN (0x54)
303 struct chmItspHeader
304 {
305 char signature[4]; /* 0 (ITSP) */
306 Int32 version; /* 4 */
307 Int32 header_len; /* 8 */
308 Int32 unknown_000c; /* c */
309 UInt32 block_len; /* 10 */
310 Int32 blockidx_intvl; /* 14 */
311 Int32 index_depth; /* 18 */
312 Int32 index_root; /* 1c */
313 Int32 index_head; /* 20 */
314 Int32 unknown_0024; /* 24 */
315 UInt32 num_blocks; /* 28 */
316 Int32 unknown_002c; /* 2c */
317 UInt32 lang_id; /* 30 */
318 UChar system_uuid[16]; /* 34 */
319 UChar unknown_0044[16]; /* 44 */
320 }; /* __attribute__ ((aligned (1))); */
321
322 static BOOL _unmarshal_itsp_header(unsigned char **pData,
323 unsigned int *pDataLen,
324 struct chmItspHeader *dest)
325 {
326 /* we only know how to deal with a 0x54 byte structures */
327 if (*pDataLen != _CHM_ITSP_V1_LEN)
328 return FALSE;
329
330 /* unmarshal fields */
331 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
332 _unmarshal_int32 (pData, pDataLen, &dest->version);
333 _unmarshal_int32 (pData, pDataLen, &dest->header_len);
334 _unmarshal_int32 (pData, pDataLen, &dest->unknown_000c);
335 _unmarshal_uint32 (pData, pDataLen, &dest->block_len);
336 _unmarshal_int32 (pData, pDataLen, &dest->blockidx_intvl);
337 _unmarshal_int32 (pData, pDataLen, &dest->index_depth);
338 _unmarshal_int32 (pData, pDataLen, &dest->index_root);
339 _unmarshal_int32 (pData, pDataLen, &dest->index_head);
340 _unmarshal_int32 (pData, pDataLen, &dest->unknown_0024);
341 _unmarshal_uint32 (pData, pDataLen, &dest->num_blocks);
342 _unmarshal_int32 (pData, pDataLen, &dest->unknown_002c);
343 _unmarshal_uint32 (pData, pDataLen, &dest->lang_id);
344 _unmarshal_uuid (pData, pDataLen, dest->system_uuid);
345 _unmarshal_uchar_array(pData, pDataLen, dest->unknown_0044, 16);
346
347 /* error check the data */
348 if (memcmp(dest->signature, "ITSP", 4) != 0)
349 return FALSE;
350 if (dest->version != 1)
351 return FALSE;
352 if (dest->header_len != _CHM_ITSP_V1_LEN)
353 return FALSE;
354
355 return TRUE;
356 }
357
358 /* structure of PMGL headers */
359 static const char _chm_pmgl_marker[4] = "PMGL";
360 #define _CHM_PMGL_LEN (0x14)
361 struct chmPmglHeader
362 {
363 char signature[4]; /* 0 (PMGL) */
364 UInt32 free_space; /* 4 */
365 UInt32 unknown_0008; /* 8 */
366 Int32 block_prev; /* c */
367 Int32 block_next; /* 10 */
368 }; /* __attribute__ ((aligned (1))); */
369
370 static BOOL _unmarshal_pmgl_header(unsigned char **pData,
371 unsigned int *pDataLen,
372 struct chmPmglHeader *dest)
373 {
374 /* we only know how to deal with a 0x14 byte structures */
375 if (*pDataLen != _CHM_PMGL_LEN)
376 return FALSE;
377
378 /* unmarshal fields */
379 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
380 _unmarshal_uint32 (pData, pDataLen, &dest->free_space);
381 _unmarshal_uint32 (pData, pDataLen, &dest->unknown_0008);
382 _unmarshal_int32 (pData, pDataLen, &dest->block_prev);
383 _unmarshal_int32 (pData, pDataLen, &dest->block_next);
384
385 /* check structure */
386 if (memcmp(dest->signature, _chm_pmgl_marker, 4) != 0)
387 return FALSE;
388
389 return TRUE;
390 }
391
392 /* structure of PMGI headers */
393 static const char _chm_pmgi_marker[4] = "PMGI";
394 #define _CHM_PMGI_LEN (0x08)
395 struct chmPmgiHeader
396 {
397 char signature[4]; /* 0 (PMGI) */
398 UInt32 free_space; /* 4 */
399 }; /* __attribute__ ((aligned (1))); */
400
401 static BOOL _unmarshal_pmgi_header(unsigned char **pData,
402 unsigned int *pDataLen,
403 struct chmPmgiHeader *dest)
404 {
405 /* we only know how to deal with a 0x8 byte structures */
406 if (*pDataLen != _CHM_PMGI_LEN)
407 return FALSE;
408
409 /* unmarshal fields */
410 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
411 _unmarshal_uint32 (pData, pDataLen, &dest->free_space);
412
413 /* check structure */
414 if (memcmp(dest->signature, _chm_pmgi_marker, 4) != 0)
415 return FALSE;
416
417 return TRUE;
418 }
419
420 /* structure of LZXC reset table */
421 #define _CHM_LZXC_RESETTABLE_V1_LEN (0x28)
422 struct chmLzxcResetTable
423 {
424 UInt32 version;
425 UInt32 block_count;
426 UInt32 unknown;
427 UInt32 table_offset;
428 UInt64 uncompressed_len;
429 UInt64 compressed_len;
430 UInt64 block_len;
431 }; /* __attribute__ ((aligned (1))); */
432
433 static BOOL _unmarshal_lzxc_reset_table(unsigned char **pData,
434 unsigned int *pDataLen,
435 struct chmLzxcResetTable *dest)
436 {
437 /* we only know how to deal with a 0x28 byte structures */
438 if (*pDataLen != _CHM_LZXC_RESETTABLE_V1_LEN)
439 return FALSE;
440
441 /* unmarshal fields */
442 _unmarshal_uint32 (pData, pDataLen, &dest->version);
443 _unmarshal_uint32 (pData, pDataLen, &dest->block_count);
444 _unmarshal_uint32 (pData, pDataLen, &dest->unknown);
445 _unmarshal_uint32 (pData, pDataLen, &dest->table_offset);
446 _unmarshal_uint64 (pData, pDataLen, &dest->uncompressed_len);
447 _unmarshal_uint64 (pData, pDataLen, &dest->compressed_len);
448 _unmarshal_uint64 (pData, pDataLen, &dest->block_len);
449
450 /* check structure */
451 if (dest->version != 2)
452 return FALSE;
453
454 return TRUE;
455 }
456
457 /* structure of LZXC control data block */
458 #define _CHM_LZXC_MIN_LEN (0x18)
459 #define _CHM_LZXC_V2_LEN (0x1c)
460 struct chmLzxcControlData
461 {
462 UInt32 size; /* 0 */
463 char signature[4]; /* 4 (LZXC) */
464 UInt32 version; /* 8 */
465 UInt32 resetInterval; /* c */
466 UInt32 windowSize; /* 10 */
467 UInt32 windowsPerReset; /* 14 */
468 UInt32 unknown_18; /* 18 */
469 };
470
471 static BOOL _unmarshal_lzxc_control_data(unsigned char **pData,
472 unsigned int *pDataLen,
473 struct chmLzxcControlData *dest)
474 {
475 /* we want at least 0x18 bytes */
476 if (*pDataLen < _CHM_LZXC_MIN_LEN)
477 return FALSE;
478
479 /* unmarshal fields */
480 _unmarshal_uint32 (pData, pDataLen, &dest->size);
481 _unmarshal_char_array(pData, pDataLen, dest->signature, 4);
482 _unmarshal_uint32 (pData, pDataLen, &dest->version);
483 _unmarshal_uint32 (pData, pDataLen, &dest->resetInterval);
484 _unmarshal_uint32 (pData, pDataLen, &dest->windowSize);
485 _unmarshal_uint32 (pData, pDataLen, &dest->windowsPerReset);
486
487 if (*pDataLen >= _CHM_LZXC_V2_LEN)
488 _unmarshal_uint32 (pData, pDataLen, &dest->unknown_18);
489 else
490 dest->unknown_18 = 0;
491
492 if (dest->version == 2)
493 {
494 dest->resetInterval *= 0x8000;
495 dest->windowSize *= 0x8000;
496 }
497 if (dest->windowSize == 0 || dest->resetInterval == 0)
498 return FALSE;
499
500 /* for now, only support resetInterval a multiple of windowSize/2 */
501 if (dest->windowSize == 1)
502 return FALSE;
503 if ((dest->resetInterval % (dest->windowSize/2)) != 0)
504 return FALSE;
505
506 /* check structure */
507 if (memcmp(dest->signature, "LZXC", 4) != 0)
508 return FALSE;
509
510 return TRUE;
511 }
512
513 /* the structure used for chm file handles */
514 struct chmFile
515 {
516 HANDLE fd;
517
518 CRITICAL_SECTION mutex;
519 CRITICAL_SECTION lzx_mutex;
520 CRITICAL_SECTION cache_mutex;
521
522 UInt64 dir_offset;
523 UInt64 dir_len;
524 UInt64 data_offset;
525 Int32 index_root;
526 Int32 index_head;
527 UInt32 block_len;
528
529 UInt64 span;
530 struct chmUnitInfo rt_unit;
531 struct chmUnitInfo cn_unit;
532 struct chmLzxcResetTable reset_table;
533
534 /* LZX control data */
535 int compression_enabled;
536 UInt32 window_size;
537 UInt32 reset_interval;
538 UInt32 reset_blkcount;
539
540 /* decompressor state */
541 struct LZXstate *lzx_state;
542 int lzx_last_block;
543
544 /* cache for decompressed blocks */
545 UChar **cache_blocks;
546 Int64 *cache_block_indices;
547 Int32 cache_num_blocks;
548 };
549
550 /*
551 * utility functions local to this module
552 */
553
554 /* utility function to handle differences between {pread,read}(64)? */
555 static Int64 _chm_fetch_bytes(struct chmFile *h,
556 UChar *buf,
557 UInt64 os,
558 Int64 len)
559 {
560 Int64 readLen=0;
561 if (h->fd == CHM_NULL_FD)
562 return readLen;
563
564 CHM_ACQUIRE_LOCK(h->mutex);
565 /* NOTE: this might be better done with CreateFileMapping, et cetera... */
566 {
567 LARGE_INTEGER old_pos, new_pos;
568 DWORD actualLen=0;
569
570 /* awkward Win32 Seek/Tell */
571 new_pos.QuadPart = 0;
572 SetFilePointerEx( h->fd, new_pos, &old_pos, FILE_CURRENT );
573 new_pos.QuadPart = os;
574 SetFilePointerEx( h->fd, new_pos, NULL, FILE_BEGIN );
575
576 /* read the data */
577 if (ReadFile(h->fd,
578 buf,
579 (DWORD)len,
580 &actualLen,
581 NULL))
582 readLen = actualLen;
583 else
584 readLen = 0;
585
586 /* restore original position */
587 SetFilePointerEx( h->fd, old_pos, NULL, FILE_BEGIN );
588 }
589 CHM_RELEASE_LOCK(h->mutex);
590 return readLen;
591 }
592
593 /*
594 * set a parameter on the file handle.
595 * valid parameter types:
596 * CHM_PARAM_MAX_BLOCKS_CACHED:
597 * how many decompressed blocks should be cached? A simple
598 * caching scheme is used, wherein the index of the block is
599 * used as a hash value, and hash collision results in the
600 * invalidation of the previously cached block.
601 */
602 static void chm_set_param(struct chmFile *h,
603 int paramType,
604 int paramVal)
605 {
606 switch (paramType)
607 {
608 case CHM_PARAM_MAX_BLOCKS_CACHED:
609 CHM_ACQUIRE_LOCK(h->cache_mutex);
610 if (paramVal != h->cache_num_blocks)
611 {
612 UChar **newBlocks;
613 Int64 *newIndices;
614 int i;
615
616 /* allocate new cached blocks */
617 newBlocks = HeapAlloc(GetProcessHeap(), 0, paramVal * sizeof (UChar *));
618 newIndices = HeapAlloc(GetProcessHeap(), 0, paramVal * sizeof (UInt64));
619 for (i=0; i<paramVal; i++)
620 {
621 newBlocks[i] = NULL;
622 newIndices[i] = 0;
623 }
624
625 /* re-distribute old cached blocks */
626 if (h->cache_blocks)
627 {
628 for (i=0; i<h->cache_num_blocks; i++)
629 {
630 int newSlot = (int)(h->cache_block_indices[i] % paramVal);
631
632 if (h->cache_blocks[i])
633 {
634 /* in case of collision, destroy newcomer */
635 if (newBlocks[newSlot])
636 {
637 HeapFree(GetProcessHeap(), 0, h->cache_blocks[i]);
638 h->cache_blocks[i] = NULL;
639 }
640 else
641 {
642 newBlocks[newSlot] = h->cache_blocks[i];
643 newIndices[newSlot] =
644 h->cache_block_indices[i];
645 }
646 }
647 }
648
649 HeapFree(GetProcessHeap(), 0, h->cache_blocks);
650 HeapFree(GetProcessHeap(), 0, h->cache_block_indices);
651 }
652
653 /* now, set new values */
654 h->cache_blocks = newBlocks;
655 h->cache_block_indices = newIndices;
656 h->cache_num_blocks = paramVal;
657 }
658 CHM_RELEASE_LOCK(h->cache_mutex);
659 break;
660
661 default:
662 break;
663 }
664 }
665
666 /* open an ITS archive */
667 struct chmFile *chm_openW(const WCHAR *filename)
668 {
669 unsigned char sbuffer[256];
670 unsigned int sremain;
671 unsigned char *sbufpos;
672 struct chmFile *newHandle=NULL;
673 struct chmItsfHeader itsfHeader;
674 struct chmItspHeader itspHeader;
675 #if 0
676 struct chmUnitInfo uiSpan;
677 #endif
678 struct chmUnitInfo uiLzxc;
679 struct chmLzxcControlData ctlData;
680
681 /* allocate handle */
682 newHandle = HeapAlloc(GetProcessHeap(), 0, sizeof(struct chmFile));
683 newHandle->fd = CHM_NULL_FD;
684 newHandle->lzx_state = NULL;
685 newHandle->cache_blocks = NULL;
686 newHandle->cache_block_indices = NULL;
687 newHandle->cache_num_blocks = 0;
688
689 /* open file */
690 if ((newHandle->fd=CreateFileW(filename,
691 GENERIC_READ,
692 FILE_SHARE_READ,
693 NULL,
694 OPEN_EXISTING,
695 FILE_ATTRIBUTE_NORMAL,
696 NULL)) == CHM_NULL_FD)
697 {
698 HeapFree(GetProcessHeap(), 0, newHandle);
699 return NULL;
700 }
701
702 /* initialize mutexes, if needed */
703 InitializeCriticalSection(&newHandle->mutex);
704 newHandle->mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.mutex");
705 InitializeCriticalSection(&newHandle->lzx_mutex);
706 newHandle->lzx_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.lzx_mutex");
707 InitializeCriticalSection(&newHandle->cache_mutex);
708 newHandle->cache_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.cache_mutex");
709
710 /* read and verify header */
711 sremain = _CHM_ITSF_V3_LEN;
712 sbufpos = sbuffer;
713 if (_chm_fetch_bytes(newHandle, sbuffer, 0, sremain) != sremain ||
714 !_unmarshal_itsf_header(&sbufpos, &sremain, &itsfHeader))
715 {
716 chm_close(newHandle);
717 return NULL;
718 }
719
720 /* stash important values from header */
721 newHandle->dir_offset = itsfHeader.dir_offset;
722 newHandle->dir_len = itsfHeader.dir_len;
723 newHandle->data_offset = itsfHeader.data_offset;
724
725 /* now, read and verify the directory header chunk */
726 sremain = _CHM_ITSP_V1_LEN;
727 sbufpos = sbuffer;
728 if (_chm_fetch_bytes(newHandle, sbuffer,
729 itsfHeader.dir_offset, sremain) != sremain ||
730 !_unmarshal_itsp_header(&sbufpos, &sremain, &itspHeader))
731 {
732 chm_close(newHandle);
733 return NULL;
734 }
735
736 /* grab essential information from ITSP header */
737 newHandle->dir_offset += itspHeader.header_len;
738 newHandle->dir_len -= itspHeader.header_len;
739 newHandle->index_root = itspHeader.index_root;
740 newHandle->index_head = itspHeader.index_head;
741 newHandle->block_len = itspHeader.block_len;
742
743 /* if the index root is -1, this means we don't have any PMGI blocks.
744 * as a result, we must use the sole PMGL block as the index root
745 */
746 if (newHandle->index_root == -1)
747 newHandle->index_root = newHandle->index_head;
748
749 /* initialize cache */
750 chm_set_param(newHandle, CHM_PARAM_MAX_BLOCKS_CACHED,
751 CHM_MAX_BLOCKS_CACHED);
752
753 /* By default, compression is enabled. */
754 newHandle->compression_enabled = 1;
755
756 /* prefetch most commonly needed unit infos */
757 if (CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
758 _CHMU_RESET_TABLE,
759 &newHandle->rt_unit) ||
760 newHandle->rt_unit.space == CHM_COMPRESSED ||
761 CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
762 _CHMU_CONTENT,
763 &newHandle->cn_unit) ||
764 newHandle->cn_unit.space == CHM_COMPRESSED ||
765 CHM_RESOLVE_SUCCESS != chm_resolve_object(newHandle,
766 _CHMU_LZXC_CONTROLDATA,
767 &uiLzxc) ||
768 uiLzxc.space == CHM_COMPRESSED)
769 {
770 newHandle->compression_enabled = 0;
771 }
772
773 /* read reset table info */
774 if (newHandle->compression_enabled)
775 {
776 sremain = _CHM_LZXC_RESETTABLE_V1_LEN;
777 sbufpos = sbuffer;
778 if (chm_retrieve_object(newHandle, &newHandle->rt_unit, sbuffer,
779 0, sremain) != sremain ||
780 !_unmarshal_lzxc_reset_table(&sbufpos, &sremain,
781 &newHandle->reset_table))
782 {
783 newHandle->compression_enabled = 0;
784 }
785 }
786
787 /* read control data */
788 if (newHandle->compression_enabled)
789 {
790 sremain = (unsigned long)uiLzxc.length;
791 sbufpos = sbuffer;
792 if (chm_retrieve_object(newHandle, &uiLzxc, sbuffer,
793 0, sremain) != sremain ||
794 !_unmarshal_lzxc_control_data(&sbufpos, &sremain,
795 &ctlData))
796 {
797 newHandle->compression_enabled = 0;
798 }
799
800 newHandle->window_size = ctlData.windowSize;
801 newHandle->reset_interval = ctlData.resetInterval;
802
803 /* Jed, Mon Jun 28: Experimentally, it appears that the reset block count */
804 /* must be multiplied by this formerly unknown ctrl data field in */
805 /* order to decompress some files. */
806 #if 0
807 newHandle->reset_blkcount = newHandle->reset_interval /
808 (newHandle->window_size / 2);
809 #else
810 newHandle->reset_blkcount = newHandle->reset_interval /
811 (newHandle->window_size / 2) *
812 ctlData.windowsPerReset;
813 #endif
814 }
815
816 return newHandle;
817 }
818
819 /* Duplicate an ITS archive handle */
820 struct chmFile *chm_dup(struct chmFile *oldHandle)
821 {
822 struct chmFile *newHandle=NULL;
823
824 newHandle = HeapAlloc(GetProcessHeap(), 0, sizeof(struct chmFile));
825 *newHandle = *oldHandle;
826
827 /* duplicate fd handle */
828 DuplicateHandle(GetCurrentProcess(), oldHandle->fd,
829 GetCurrentProcess(), &(newHandle->fd),
830 0, FALSE, DUPLICATE_SAME_ACCESS);
831 newHandle->lzx_state = NULL;
832 newHandle->cache_blocks = NULL;
833 newHandle->cache_block_indices = NULL;
834 newHandle->cache_num_blocks = 0;
835
836 /* initialize mutexes, if needed */
837 InitializeCriticalSection(&newHandle->mutex);
838 newHandle->mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.mutex");
839 InitializeCriticalSection(&newHandle->lzx_mutex);
840 newHandle->lzx_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.lzx_mutex");
841 InitializeCriticalSection(&newHandle->cache_mutex);
842 newHandle->cache_mutex.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": chmFile.cache_mutex");
843
844 /* initialize cache */
845 chm_set_param(newHandle, CHM_PARAM_MAX_BLOCKS_CACHED,
846 CHM_MAX_BLOCKS_CACHED);
847
848 return newHandle;
849 }
850
851 /* close an ITS archive */
852 void chm_close(struct chmFile *h)
853 {
854 if (h != NULL)
855 {
856 if (h->fd != CHM_NULL_FD)
857 CHM_CLOSE_FILE(h->fd);
858 h->fd = CHM_NULL_FD;
859
860 h->mutex.DebugInfo->Spare[0] = 0;
861 DeleteCriticalSection(&h->mutex);
862 h->lzx_mutex.DebugInfo->Spare[0] = 0;
863 DeleteCriticalSection(&h->lzx_mutex);
864 h->cache_mutex.DebugInfo->Spare[0] = 0;
865 DeleteCriticalSection(&h->cache_mutex);
866
867 if (h->lzx_state)
868 LZXteardown(h->lzx_state);
869 h->lzx_state = NULL;
870
871 if (h->cache_blocks)
872 {
873 int i;
874 for (i=0; i<h->cache_num_blocks; i++)
875 {
876 HeapFree(GetProcessHeap(), 0, h->cache_blocks[i]);
877 }
878 HeapFree(GetProcessHeap(), 0, h->cache_blocks);
879 h->cache_blocks = NULL;
880 }
881
882 HeapFree(GetProcessHeap(), 0, h->cache_block_indices);
883 h->cache_block_indices = NULL;
884
885 HeapFree(GetProcessHeap(), 0, h);
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 BOOL _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 TRUE;
934 }
935
936 /* parse a PMGL entry into a chmUnitInfo struct; return 1 on success. */
937 static BOOL _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 FALSE;
945
946 /* parse path */
947 if (! _chm_parse_UTF8(pEntry, strLen, ui->path))
948 return FALSE;
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 TRUE;
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 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. Returns FALSE on failure */
1112 static BOOL _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 h->data_offset
1128 + h->rt_unit.start
1129 + h->reset_table.table_offset
1130 + block*8,
1131 remain) != remain ||
1132 !_unmarshal_uint64(&dummy, &remain, start))
1133 return FALSE;
1134
1135 /* unpack the end address */
1136 dummy = buffer;
1137 remain = 8;
1138 if (_chm_fetch_bytes(h, buffer,
1139 h->data_offset
1140 + h->rt_unit.start
1141 + h->reset_table.table_offset
1142 + block*8 + 8,
1143 remain) != remain ||
1144 !_unmarshal_int64(&dummy, &remain, len))
1145 return FALSE;
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 h->data_offset
1156 + h->rt_unit.start
1157 + h->reset_table.table_offset
1158 + block*8,
1159 remain) != remain ||
1160 !_unmarshal_uint64(&dummy, &remain, start))
1161 return FALSE;
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 TRUE;
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 interval align */
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] =
1215 HeapAlloc(GetProcessHeap(), 0,
1216 (unsigned int)(h->reset_table.block_len));
1217 lbuffer = h->cache_blocks[indexSlot];
1218
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)
1227 {
1228 #ifdef CHM_DEBUG
1229 fprintf(stderr, " (DECOMPRESS FAILED!)\n");
1230 #endif
1231 HeapFree(GetProcessHeap(), 0, cbuffer);
1232 return 0;
1233 }
1234
1235 h->lzx_last_block = (int)curBlockIdx;
1236 }
1237 }
1238 }
1239 else
1240 {
1241 if ((block % h->reset_blkcount) == 0)
1242 {
1243 #ifdef CHM_DEBUG
1244 fprintf(stderr, "***RESET (2)***\n");
1245 #endif
1246 LZXreset(h->lzx_state);
1247 }
1248 }
1249
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] =
1255 HeapAlloc(GetProcessHeap(), 0, ((unsigned int)h->reset_table.block_len));
1256 lbuffer = h->cache_blocks[indexSlot];
1257 *ubuffer = lbuffer;
1258
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)
1267 {
1268 #ifdef CHM_DEBUG
1269 fprintf(stderr, " (DECOMPRESS FAILED!)\n");
1270 #endif
1271 HeapFree(GetProcessHeap(), 0, cbuffer);
1272 return 0;
1273 }
1274 h->lzx_last_block = (int)block;
1275
1276 /* XXX: modify LZX routines to return the length of the data they
1277 * decompressed and return that instead, for an extra sanity check.
1278 */
1279 HeapFree(GetProcessHeap(), 0, cbuffer);
1280 return h->reset_table.block_len;
1281 }
1282
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)
1288 {
1289 UInt64 nBlock, nOffset;
1290 UInt64 nLen;
1291 UInt64 gotLen;
1292 UChar *ubuffer = NULL;
1293
1294 if (len <= 0)
1295 return 0;
1296
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;
1303
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)
1309 {
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;
1316 }
1317 CHM_RELEASE_LOCK(h->cache_mutex);
1318
1319 /* data request not satisfied, so... start up the decompressor machine */
1320 if (! h->lzx_state)
1321 {
1322 int window_size = ffs(h->window_size) - 1;
1323 h->lzx_last_block = -1;
1324 h->lzx_state = LZXinit(window_size);
1325 }
1326
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;
1334 }
1335
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)
1342 {
1343 /* must be valid file handle */
1344 if (h == NULL)
1345 return 0;
1346
1347 /* starting address must be in correct range */
1348 if (addr >= ui->length)
1349 return 0;
1350
1351 /* clip length */
1352 if (addr + len > ui->length)
1353 len = ui->length - addr;
1354
1355 /* if the file is uncompressed, it's simple */
1356 if (ui->space == CHM_UNCOMPRESSED)
1357 {
1358 /* read data */
1359 return _chm_fetch_bytes(h,
1360 buf,
1361 h->data_offset + ui->start + addr,
1362 len);
1363 }
1364
1365 /* else if the file is compressed, it's a little trickier */
1366 else /* ui->space == CHM_COMPRESSED */
1367 {
1368 Int64 swath=0, total=0;
1369
1370 /* if compression is not enabled for this file... */
1371 if (! h->compression_enabled)
1372 return total;
1373
1374 do {
1375
1376 /* swill another mouthful */
1377 swath = _chm_decompress_region(h, buf, ui->start + addr, len);
1378
1379 /* if we didn't get any... */
1380 if (swath == 0)
1381 return total;
1382
1383 /* update stats */
1384 total += swath;
1385 len -= swath;
1386 addr += swath;
1387 buf += swath;
1388
1389 } while (len != 0);
1390
1391 return total;
1392 }
1393 }
1394
1395 BOOL chm_enumerate_dir(struct chmFile *h,
1396 const WCHAR *prefix,
1397 int what,
1398 CHM_ENUMERATOR e,
1399 void *context)
1400 {
1401 /*
1402 * XXX: do this efficiently (i.e. using the tree index)
1403 */
1404
1405 Int32 curPage;
1406
1407 /* buffer to hold whatever page we're looking at */
1408 UChar *page_buf = HeapAlloc(GetProcessHeap(), 0, h->block_len);
1409 struct chmPmglHeader header;
1410 UChar *end;
1411 UChar *cur;
1412 unsigned int lenRemain;
1413
1414 /* set to TRUE once we've started */
1415 BOOL it_has_begun = FALSE;
1416
1417 /* the current ui */
1418 struct chmUnitInfo ui;
1419 int flag;
1420 UInt64 ui_path_len;
1421
1422 /* the length of the prefix */
1423 WCHAR prefixRectified[CHM_MAX_PATHLEN+1];
1424 int prefixLen;
1425 WCHAR lastPath[CHM_MAX_PATHLEN];
1426 int lastPathLen;
1427
1428 /* starting page */
1429 curPage = h->index_head;
1430
1431 /* initialize pathname state */
1432 lstrcpynW(prefixRectified, prefix, CHM_MAX_PATHLEN);
1433 prefixLen = strlenW(prefixRectified);
1434 if (prefixLen != 0)
1435 {
1436 if (prefixRectified[prefixLen-1] != '/')
1437 {
1438 prefixRectified[prefixLen] = '/';
1439 prefixRectified[prefixLen+1] = '\0';
1440 ++prefixLen;
1441 }
1442 }
1443 lastPath[0] = '\0';
1444 lastPathLen = -1;
1445
1446 /* until we have either returned or given up */
1447 while (curPage != -1)
1448 {
1449
1450 /* try to fetch the index page */
1451 if (_chm_fetch_bytes(h,
1452 page_buf,
1453 h->dir_offset + (UInt64)curPage*h->block_len,
1454 h->block_len) != h->block_len)
1455 {
1456 HeapFree(GetProcessHeap(), 0, page_buf);
1457 return FALSE;
1458 }
1459
1460 /* figure out start and end for this page */
1461 cur = page_buf;
1462 lenRemain = _CHM_PMGL_LEN;
1463 if (! _unmarshal_pmgl_header(&cur, &lenRemain, &header))
1464 {
1465 HeapFree(GetProcessHeap(), 0, page_buf);
1466 return FALSE;
1467 }
1468 end = page_buf + h->block_len - (header.free_space);
1469
1470 /* loop over this page */
1471 while (cur < end)
1472 {
1473 if (! _chm_parse_PMGL_entry(&cur, &ui))
1474 {
1475 HeapFree(GetProcessHeap(), 0, page_buf);
1476 return FALSE;
1477 }
1478
1479 /* check if we should start */
1480 if (! it_has_begun)
1481 {
1482 if (ui.length == 0 && strncmpiW(ui.path, prefixRectified, prefixLen) == 0)
1483 it_has_begun = TRUE;
1484 else
1485 continue;
1486
1487 if (ui.path[prefixLen] == '\0')
1488 continue;
1489 }
1490
1491 /* check if we should stop */
1492 else
1493 {
1494 if (strncmpiW(ui.path, prefixRectified, prefixLen) != 0)
1495 {
1496 HeapFree(GetProcessHeap(), 0, page_buf);
1497 return TRUE;
1498 }
1499 }
1500
1501 /* check if we should include this path */
1502 if (lastPathLen != -1)
1503 {
1504 if (strncmpiW(ui.path, lastPath, lastPathLen) == 0)
1505 continue;
1506 }
1507 strcpyW(lastPath, ui.path);
1508 lastPathLen = strlenW(lastPath);
1509
1510 /* get the length of the path */
1511 ui_path_len = strlenW(ui.path)-1;
1512
1513 /* check for DIRS */
1514 if (ui.path[ui_path_len] == '/' && !(what & CHM_ENUMERATE_DIRS))
1515 continue;
1516
1517 /* check for FILES */
1518 if (ui.path[ui_path_len] != '/' && !(what & CHM_ENUMERATE_FILES))
1519 continue;
1520
1521 /* check for NORMAL vs. META */
1522 if (ui.path[0] == '/')
1523 {
1524
1525 /* check for NORMAL vs. SPECIAL */
1526 if (ui.path[1] == '#' || ui.path[1] == '$')
1527 flag = CHM_ENUMERATE_SPECIAL;
1528 else
1529 flag = CHM_ENUMERATE_NORMAL;
1530 }
1531 else
1532 flag = CHM_ENUMERATE_META;
1533 if (! (what & flag))
1534 continue;
1535
1536 /* call the enumerator */
1537 {
1538 int status = (*e)(h, &ui, context);
1539 switch (status)
1540 {
1541 case CHM_ENUMERATOR_FAILURE:
1542 HeapFree(GetProcessHeap(), 0, page_buf);
1543 return FALSE;
1544 case CHM_ENUMERATOR_CONTINUE:
1545 break;
1546 case CHM_ENUMERATOR_SUCCESS:
1547 HeapFree(GetProcessHeap(), 0, page_buf);
1548 return TRUE;
1549 default:
1550 break;
1551 }
1552 }
1553 }
1554
1555 /* advance to next page */
1556 curPage = header.block_next;
1557 }
1558
1559 HeapFree(GetProcessHeap(), 0, page_buf);
1560 return TRUE;
1561 }