1 // Archive/ChmIn.cpp
2
3 #include "StdAfx.h"
4
5 #include "Common/IntToString.h"
6 #include "Common/UTFConvert.h"
7
8 #include "../../Common/LimitedStreams.h"
9
10 #include "ChmIn.h"
11
12 namespace NArchive{
13 namespace NChm{
14
15 // define CHM_LOW, if you want to see low level items
16 // #define CHM_LOW
17
18 static const GUID kChmLzxGuid =
19 { 0x7FC28940, 0x9D31, 0x11D0, 0x9B, 0x27, 0x00, 0xA0, 0xC9, 0x1E, 0x9C, 0x7C };
20 static const GUID kHelp2LzxGuid =
21 { 0x0A9007C6, 0x4076, 0x11D3, 0x87, 0x89, 0x00, 0x00, 0xF8, 0x10, 0x57, 0x54 };
22 static const GUID kDesGuid =
23 { 0x67F6E4A2, 0x60BF, 0x11D3, 0x85, 0x40, 0x00, 0xC0, 0x4F, 0x58, 0xC3, 0xCF };
24
AreGuidsEqual(REFGUID g1,REFGUID g2)25 static bool AreGuidsEqual(REFGUID g1, REFGUID g2)
26 {
27 if (g1.Data1 != g2.Data1 ||
28 g1.Data2 != g2.Data2 ||
29 g1.Data3 != g2.Data3)
30 return false;
31 for (int i = 0; i < 8; i++)
32 if (g1.Data4[i] != g2.Data4[i])
33 return false;
34 return true;
35 }
36
GetHex(Byte value)37 static char GetHex(Byte value)
38 {
39 return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
40 }
41
PrintByte(Byte b,AString & s)42 static void PrintByte(Byte b, AString &s)
43 {
44 s += GetHex(b >> 4);
45 s += GetHex(b & 0xF);
46 }
47
PrintUInt16(UInt16 v,AString & s)48 static void PrintUInt16(UInt16 v, AString &s)
49 {
50 PrintByte((Byte)(v >> 8), s);
51 PrintByte((Byte)v, s);
52 }
53
PrintUInt32(UInt32 v,AString & s)54 static void PrintUInt32(UInt32 v, AString &s)
55 {
56 PrintUInt16((UInt16)(v >> 16), s);
57 PrintUInt16((UInt16)v, s);
58 }
59
GetGuidString() const60 AString CMethodInfo::GetGuidString() const
61 {
62 AString s;
63 s += '{';
64 PrintUInt32(Guid.Data1, s);
65 s += '-';
66 PrintUInt16(Guid.Data2, s);
67 s += '-';
68 PrintUInt16(Guid.Data3, s);
69 s += '-';
70 PrintByte(Guid.Data4[0], s);
71 PrintByte(Guid.Data4[1], s);
72 s += '-';
73 for (int i = 2; i < 8; i++)
74 PrintByte(Guid.Data4[i], s);
75 s += '}';
76 return s;
77 }
78
IsLzx() const79 bool CMethodInfo::IsLzx() const
80 {
81 if (AreGuidsEqual(Guid, kChmLzxGuid))
82 return true;
83 return AreGuidsEqual(Guid, kHelp2LzxGuid);
84 }
85
IsDes() const86 bool CMethodInfo::IsDes() const
87 {
88 return AreGuidsEqual(Guid, kDesGuid);
89 }
90
GetName() const91 UString CMethodInfo::GetName() const
92 {
93 UString s;
94 if (IsLzx())
95 {
96 s = L"LZX:";
97 wchar_t temp[16];
98 ConvertUInt32ToString(LzxInfo.GetNumDictBits(), temp);
99 s += temp;
100 }
101 else
102 {
103 AString s2;
104 if (IsDes())
105 s2 = "DES";
106 else
107 {
108 s2 = GetGuidString();
109 if (ControlData.GetCapacity() > 0)
110 {
111 s2 += ':';
112 for (size_t i = 0; i < ControlData.GetCapacity(); i++)
113 PrintByte(ControlData[i], s2);
114 }
115 }
116 ConvertUTF8ToUnicode(s2, s);
117 }
118 return s;
119 }
120
IsLzx() const121 bool CSectionInfo::IsLzx() const
122 {
123 if (Methods.Size() != 1)
124 return false;
125 return Methods[0].IsLzx();
126 }
127
GetMethodName() const128 UString CSectionInfo::GetMethodName() const
129 {
130 UString s;
131 if (!IsLzx())
132 {
133 UString temp;
134 if (ConvertUTF8ToUnicode(Name, temp))
135 s += temp;
136 s += L": ";
137 }
138 for (int i = 0; i < Methods.Size(); i++)
139 {
140 if (i != 0)
141 s += L' ';
142 s += Methods[i].GetName();
143 }
144 return s;
145 }
146
ReadByte()147 Byte CInArchive::ReadByte()
148 {
149 Byte b;
150 if (!_inBuffer.ReadByte(b))
151 throw 1;
152 return b;
153 }
154
Skip(size_t size)155 void CInArchive::Skip(size_t size)
156 {
157 while (size-- != 0)
158 ReadByte();
159 }
160
ReadBytes(Byte * data,UInt32 size)161 void CInArchive::ReadBytes(Byte *data, UInt32 size)
162 {
163 for (UInt32 i = 0; i < size; i++)
164 data[i] = ReadByte();
165 }
166
ReadUInt16()167 UInt16 CInArchive::ReadUInt16()
168 {
169 UInt16 value = 0;
170 for (int i = 0; i < 2; i++)
171 value |= ((UInt16)(ReadByte()) << (8 * i));
172 return value;
173 }
174
ReadUInt32()175 UInt32 CInArchive::ReadUInt32()
176 {
177 UInt32 value = 0;
178 for (int i = 0; i < 4; i++)
179 value |= ((UInt32)(ReadByte()) << (8 * i));
180 return value;
181 }
182
ReadUInt64()183 UInt64 CInArchive::ReadUInt64()
184 {
185 UInt64 value = 0;
186 for (int i = 0; i < 8; i++)
187 value |= ((UInt64)(ReadByte()) << (8 * i));
188 return value;
189 }
190
ReadEncInt()191 UInt64 CInArchive::ReadEncInt()
192 {
193 UInt64 val = 0;;
194 for (int i = 0; i < 10; i++)
195 {
196 Byte b = ReadByte();
197 val |= (b & 0x7F);
198 if (b < 0x80)
199 return val;
200 val <<= 7;
201 }
202 throw 1;
203 }
204
ReadGUID(GUID & g)205 void CInArchive::ReadGUID(GUID &g)
206 {
207 g.Data1 = ReadUInt32();
208 g.Data2 = ReadUInt16();
209 g.Data3 = ReadUInt16();
210 ReadBytes(g.Data4, 8);
211 }
212
ReadString(int size,AString & s)213 void CInArchive::ReadString(int size, AString &s)
214 {
215 s.Empty();
216 while(size-- != 0)
217 {
218 char c = (char)ReadByte();
219 if (c == 0)
220 {
221 Skip(size);
222 return;
223 }
224 s += c;
225 }
226 }
227
ReadUString(int size,UString & s)228 void CInArchive::ReadUString(int size, UString &s)
229 {
230 s.Empty();
231 while(size-- != 0)
232 {
233 wchar_t c = ReadUInt16();
234 if (c == 0)
235 {
236 Skip(2 * size);
237 return;
238 }
239 s += c;
240 }
241 }
242
ReadChunk(IInStream * inStream,UInt64 pos,UInt64 size)243 HRESULT CInArchive::ReadChunk(IInStream *inStream, UInt64 pos, UInt64 size)
244 {
245 RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL));
246 CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
247 CMyComPtr<ISequentialInStream> limitedStream(streamSpec);
248 streamSpec->SetStream(inStream);
249 streamSpec->Init(size);
250 _inBuffer.SetStream(limitedStream);
251 _inBuffer.Init();
252 return S_OK;
253 }
254
ReadDirEntry(CDatabase & database)255 HRESULT CInArchive::ReadDirEntry(CDatabase &database)
256 {
257 CItem item;
258 UInt64 nameLength = ReadEncInt();
259 if (nameLength == 0 || nameLength >= 0x10000000)
260 return S_FALSE;
261 ReadString((int)nameLength, item.Name);
262 item.Section = ReadEncInt();
263 item.Offset = ReadEncInt();
264 item.Size = ReadEncInt();
265 database.Items.Add(item);
266 return S_OK;
267 }
268
OpenChm(IInStream * inStream,CDatabase & database)269 HRESULT CInArchive::OpenChm(IInStream *inStream, CDatabase &database)
270 {
271 UInt32 headerSize = ReadUInt32();
272 if (headerSize != 0x60)
273 return S_FALSE;
274 UInt32 unknown1 = ReadUInt32();
275 if (unknown1 != 0 && unknown1 != 1) // it's 0 in one .sll file
276 return S_FALSE;
277 /* UInt32 timeStamp = */ ReadUInt32();
278 // Considered as a big-endian DWORD, it appears to contain seconds (MSB) and
279 // fractional seconds (second byte).
280 // The third and fourth bytes may contain even more fractional bits.
281 // The 4 least significant bits in the last byte are constant.
282 /* UInt32 lang = */ ReadUInt32();
283 GUID g;
284 ReadGUID(g); // {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}
285 ReadGUID(g); // {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}
286 const int kNumSections = 2;
287 UInt64 sectionOffsets[kNumSections];
288 UInt64 sectionSizes[kNumSections];
289 int i;
290 for (i = 0; i < kNumSections; i++)
291 {
292 sectionOffsets[i] = ReadUInt64();
293 sectionSizes[i] = ReadUInt64();
294 }
295 // if (chmVersion == 3)
296 database.ContentOffset = ReadUInt64();
297 /*
298 else
299 database.ContentOffset = _startPosition + 0x58
300 */
301
302 /*
303 // Section 0
304 ReadChunk(inStream, sectionOffsets[0], sectionSizes[0]);
305 if (sectionSizes[0] != 0x18)
306 return S_FALSE;
307 ReadUInt32(); // unknown: 01FE
308 ReadUInt32(); // unknown: 0
309 UInt64 fileSize = ReadUInt64();
310 ReadUInt32(); // unknown: 0
311 ReadUInt32(); // unknown: 0
312 */
313
314 // Section 1: The Directory Listing
315 ReadChunk(inStream, sectionOffsets[1], sectionSizes[1]);
316 if (ReadUInt32() != NHeader::kItspSignature)
317 return S_FALSE;
318 if (ReadUInt32() != 1) // version
319 return S_FALSE;
320 /* UInt32 dirHeaderSize = */ ReadUInt32();
321 ReadUInt32(); // 0x0A (unknown)
322 UInt32 dirChunkSize = ReadUInt32(); // $1000
323 if (dirChunkSize < 32)
324 return S_FALSE;
325 /* UInt32 density = */ ReadUInt32(); // "Density" of quickref section, usually 2.
326 /* UInt32 depth = */ ReadUInt32(); // Depth of the index tree: 1 there is no index,
327 // 2 if there is one level of PMGI chunks.
328
329 /* UInt32 chunkNumber = */ ReadUInt32(); // Chunk number of root index chunk, -1 if there is none
330 // (though at least one file has 0 despite there being no
331 // index chunk, probably a bug.)
332 /* UInt32 firstPmglChunkNumber = */ ReadUInt32(); // Chunk number of first PMGL (listing) chunk
333 /* UInt32 lastPmglChunkNumber = */ ReadUInt32(); // Chunk number of last PMGL (listing) chunk
334 ReadUInt32(); // -1 (unknown)
335 UInt32 numDirChunks = ReadUInt32(); // Number of directory chunks (total)
336 /* UInt32 windowsLangId = */ ReadUInt32();
337 ReadGUID(g); // {5D02926A-212E-11D0-9DF9-00A0C922E6EC}
338 ReadUInt32(); // 0x54 (This is the length again)
339 ReadUInt32(); // -1 (unknown)
340 ReadUInt32(); // -1 (unknown)
341 ReadUInt32(); // -1 (unknown)
342
343 for (UInt32 ci = 0; ci < numDirChunks; ci++)
344 {
345 UInt64 chunkPos = _inBuffer.GetProcessedSize();
346 if (ReadUInt32() == NHeader::kPmglSignature)
347 {
348 // The quickref area is written backwards from the end of the chunk.
349 // One quickref entry exists for every n entries in the file, where n
350 // is calculated as 1 + (1 << quickref density). So for density = 2, n = 5.
351
352 UInt32 quickrefLength = ReadUInt32(); // Length of free space and/or quickref area at end of directory chunk
353 if (quickrefLength > dirChunkSize || quickrefLength < 2)
354 return S_FALSE;
355 ReadUInt32(); // Always 0
356 ReadUInt32(); // Chunk number of previous listing chunk when reading
357 // directory in sequence (-1 if this is the first listing chunk)
358 ReadUInt32(); // Chunk number of next listing chunk when reading
359 // directory in sequence (-1 if this is the last listing chunk)
360 int numItems = 0;
361 for (;;)
362 {
363 UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos;
364 UInt32 offsetLimit = dirChunkSize - quickrefLength;
365 if (offset > offsetLimit)
366 return S_FALSE;
367 if (offset == offsetLimit)
368 break;
369 RINOK(ReadDirEntry(database));
370 numItems++;
371 }
372 Skip(quickrefLength - 2);
373 if (ReadUInt16() != numItems)
374 return S_FALSE;
375 }
376 else
377 Skip(dirChunkSize - 4);
378 }
379 return S_OK;
380 }
381
OpenHelp2(IInStream * inStream,CDatabase & database)382 HRESULT CInArchive::OpenHelp2(IInStream *inStream, CDatabase &database)
383 {
384 if (ReadUInt32() != 1) // version
385 return S_FALSE;
386 if (ReadUInt32() != 0x28) // Location of header section table
387 return S_FALSE;
388 UInt32 numHeaderSections = ReadUInt32();
389 const int kNumHeaderSectionsMax = 5;
390 if (numHeaderSections != kNumHeaderSectionsMax)
391 return S_FALSE;
392 ReadUInt32(); // Length of post-header table
393 GUID g;
394 ReadGUID(g); // {0A9007C1-4076-11D3-8789-0000F8105754}
395
396 // header section table
397 UInt64 sectionOffsets[kNumHeaderSectionsMax];
398 UInt64 sectionSizes[kNumHeaderSectionsMax];
399 UInt32 i;
400 for (i = 0; i < numHeaderSections; i++)
401 {
402 sectionOffsets[i] = ReadUInt64();
403 sectionSizes[i] = ReadUInt64();
404 }
405
406 // Post-Header
407 ReadUInt32(); // 2
408 ReadUInt32(); // 0x98: offset to CAOL from beginning of post-header)
409 // ----- Directory information
410 ReadUInt64(); // Chunk number of top-level AOLI chunk in directory, or -1
411 ReadUInt64(); // Chunk number of first AOLL chunk in directory
412 ReadUInt64(); // Chunk number of last AOLL chunk in directory
413 ReadUInt64(); // 0 (unknown)
414 ReadUInt32(); // $2000 (Directory chunk size of directory)
415 ReadUInt32(); // Quickref density for main directory, usually 2
416 ReadUInt32(); // 0 (unknown)
417 ReadUInt32(); // Depth of main directory index tree
418 // 1 there is no index, 2 if there is one level of AOLI chunks.
419 ReadUInt64(); // 0 (unknown)
420 UInt64 numDirEntries = ReadUInt64(); // Number of directory entries
421 // ----- Directory Index Information
422 ReadUInt64(); // -1 (unknown, probably chunk number of top-level AOLI in directory index)
423 ReadUInt64(); // Chunk number of first AOLL chunk in directory index
424 ReadUInt64(); // Chunk number of last AOLL chunk in directory index
425 ReadUInt64(); // 0 (unknown)
426 ReadUInt32(); // $200 (Directory chunk size of directory index)
427 ReadUInt32(); // Quickref density for directory index, usually 2
428 ReadUInt32(); // 0 (unknown)
429 ReadUInt32(); // Depth of directory index index tree.
430 ReadUInt64(); // Possibly flags -- sometimes 1, sometimes 0.
431 ReadUInt64(); // Number of directory index entries (same as number of AOLL
432 // chunks in main directory)
433
434 // (The obvious guess for the following two fields, which recur in a number
435 // of places, is they are maximum sizes for the directory and directory index.
436 // However, I have seen no direct evidence that this is the case.)
437
438 ReadUInt32(); // $100000 (Same as field following chunk size in directory)
439 ReadUInt32(); // $20000 (Same as field following chunk size in directory index)
440
441 ReadUInt64(); // 0 (unknown)
442 if (ReadUInt32() != NHeader::kCaolSignature)
443 return S_FALSE;
444 if (ReadUInt32() != 2) // (Most likely a version number)
445 return S_FALSE;
446 UInt32 caolLength = ReadUInt32(); // $50 (Length of the CAOL section, which includes the ITSF section)
447 if (caolLength >= 0x2C)
448 {
449 /* UInt32 c7 = */ ReadUInt16(); // Unknown. Remains the same when identical files are built.
450 // Does not appear to be a checksum. Many files have
451 // 'HH' (HTML Help?) here, indicating this may be a compiler ID
452 // field. But at least one ITOL/ITLS compiler does not set this
453 // field to a constant value.
454 ReadUInt16(); // 0 (Unknown. Possibly part of 00A4 field)
455 ReadUInt32(); // Unknown. Two values have been seen -- $43ED, and 0.
456 ReadUInt32(); // $2000 (Directory chunk size of directory)
457 ReadUInt32(); // $200 (Directory chunk size of directory index)
458 ReadUInt32(); // $100000 (Same as field following chunk size in directory)
459 ReadUInt32(); // $20000 (Same as field following chunk size in directory index)
460 ReadUInt32(); // 0 (unknown)
461 ReadUInt32(); // 0 (Unknown)
462 if (caolLength == 0x2C)
463 {
464 database.ContentOffset = 0;
465 database.NewFormat = true;
466 }
467 else if (caolLength == 0x50)
468 {
469 ReadUInt32(); // 0 (Unknown)
470 if (ReadUInt32() != NHeader::kItsfSignature)
471 return S_FALSE;
472 if (ReadUInt32() != 4) // $4 (Version number -- CHM uses 3)
473 return S_FALSE;
474 if (ReadUInt32() != 0x20) // $20 (length of ITSF)
475 return S_FALSE;
476 UInt32 unknown = ReadUInt32();
477 if (unknown != 0 && unknown != 1) // = 0 for some HxW files, 1 in other cases;
478 return S_FALSE;
479 database.ContentOffset = _startPosition + ReadUInt64();
480 /* UInt32 timeStamp = */ ReadUInt32();
481 // A timestamp of some sort.
482 // Considered as a big-endian DWORD, it appears to contain
483 // seconds (MSB) and fractional seconds (second byte).
484 // The third and fourth bytes may contain even more fractional
485 // bits. The 4 least significant bits in the last byte are constant.
486 /* UInt32 lang = */ ReadUInt32(); // BE?
487 }
488 else
489 return S_FALSE;
490 }
491
492 /*
493 // Section 0
494 ReadChunk(inStream, _startPosition + sectionOffsets[0], sectionSizes[0]);
495 if (sectionSizes[0] != 0x18)
496 return S_FALSE;
497 ReadUInt32(); // unknown: 01FE
498 ReadUInt32(); // unknown: 0
499 UInt64 fileSize = ReadUInt64();
500 ReadUInt32(); // unknown: 0
501 ReadUInt32(); // unknown: 0
502 */
503
504 // Section 1: The Directory Listing
505 ReadChunk(inStream, _startPosition + sectionOffsets[1], sectionSizes[1]);
506 if (ReadUInt32() != NHeader::kIfcmSignature)
507 return S_FALSE;
508 if (ReadUInt32() != 1) // (probably a version number)
509 return S_FALSE;
510 UInt32 dirChunkSize = ReadUInt32(); // $2000
511 if (dirChunkSize < 64)
512 return S_FALSE;
513 ReadUInt32(); // $100000 (unknown)
514 ReadUInt32(); // -1 (unknown)
515 ReadUInt32(); // -1 (unknown)
516 UInt32 numDirChunks = ReadUInt32();
517 ReadUInt32(); // 0 (unknown, probably high word of above)
518
519 for (UInt32 ci = 0; ci < numDirChunks; ci++)
520 {
521 UInt64 chunkPos = _inBuffer.GetProcessedSize();
522 if (ReadUInt32() == NHeader::kAollSignature)
523 {
524 UInt32 quickrefLength = ReadUInt32(); // Length of quickref area at end of directory chunk
525 if (quickrefLength > dirChunkSize || quickrefLength < 2)
526 return S_FALSE;
527 ReadUInt64(); // Directory chunk number
528 // This must match physical position in file, that is
529 // the chunk size times the chunk number must be the
530 // offset from the end of the directory header.
531 ReadUInt64(); // Chunk number of previous listing chunk when reading
532 // directory in sequence (-1 if first listing chunk)
533 ReadUInt64(); // Chunk number of next listing chunk when reading
534 // directory in sequence (-1 if last listing chunk)
535 ReadUInt64(); // Number of first listing entry in this chunk
536 ReadUInt32(); // 1 (unknown -- other values have also been seen here)
537 ReadUInt32(); // 0 (unknown)
538
539 int numItems = 0;
540 for (;;)
541 {
542 UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos;
543 UInt32 offsetLimit = dirChunkSize - quickrefLength;
544 if (offset > offsetLimit)
545 return S_FALSE;
546 if (offset == offsetLimit)
547 break;
548 if (database.NewFormat)
549 {
550 UInt16 nameLength = ReadUInt16();
551 if (nameLength == 0)
552 return S_FALSE;
553 UString name;
554 ReadUString((int)nameLength, name);
555 AString s;
556 ConvertUnicodeToUTF8(name, s);
557 Byte b = ReadByte();
558 s += ' ';
559 PrintByte(b, s);
560 s += ' ';
561 UInt64 len = ReadEncInt();
562 // then number of items ?
563 // then length ?
564 // then some data (binary encoding?)
565 while (len-- != 0)
566 {
567 b = ReadByte();
568 PrintByte(b, s);
569 }
570 database.NewFormatString += s;
571 database.NewFormatString += "\r\n";
572 }
573 else
574 {
575 RINOK(ReadDirEntry(database));
576 }
577 numItems++;
578 }
579 Skip(quickrefLength - 2);
580 if (ReadUInt16() != numItems)
581 return S_FALSE;
582 if (numItems > numDirEntries)
583 return S_FALSE;
584 numDirEntries -= numItems;
585 }
586 else
587 Skip(dirChunkSize - 4);
588 }
589 return numDirEntries == 0 ? S_OK : S_FALSE;
590 }
591
DecompressStream(IInStream * inStream,const CDatabase & database,const AString & name)592 HRESULT CInArchive::DecompressStream(IInStream *inStream, const CDatabase &database, const AString &name)
593 {
594 int index = database.FindItem(name);
595 if (index < 0)
596 return S_FALSE;
597 const CItem &item = database.Items[index];
598 _chunkSize = item.Size;
599 return ReadChunk(inStream, database.ContentOffset + item.Offset, item.Size);
600 }
601
602
603 #define DATA_SPACE "::DataSpace/"
604 static const char *kNameList = DATA_SPACE "NameList";
605 static const char *kStorage = DATA_SPACE "Storage/";
606 static const char *kContent = "Content";
607 static const char *kControlData = "ControlData";
608 static const char *kSpanInfo = "SpanInfo";
609 static const char *kTransform = "Transform/";
610 static const char *kResetTable = "/InstanceData/ResetTable";
611 static const char *kTransformList = "List";
612
GetSectionPrefix(const AString & name)613 static AString GetSectionPrefix(const AString &name)
614 {
615 return AString(kStorage) + name + AString("/");
616 }
617
618 #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; }
619
CompareFiles(const int * p1,const int * p2,void * param)620 static int CompareFiles(const int *p1, const int *p2, void *param)
621 {
622 const CObjectVector<CItem> &items = *(const CObjectVector<CItem> *)param;
623 const CItem &item1 = items[*p1];
624 const CItem &item2 = items[*p2];
625 bool isDir1 = item1.IsDir();
626 bool isDir2 = item2.IsDir();
627 if (isDir1 && !isDir2)
628 return -1;
629 if (isDir2)
630 {
631 if (isDir1)
632 return MyCompare(*p1, *p2);
633 return 1;
634 }
635 RINOZ(MyCompare(item1.Section, item2.Section));
636 RINOZ(MyCompare(item1.Offset, item2.Offset));
637 RINOZ(MyCompare(item1.Size, item2.Size));
638 return MyCompare(*p1, *p2);
639 }
640
SetIndices()641 void CFilesDatabase::SetIndices()
642 {
643 for (int i = 0; i < Items.Size(); i++)
644 {
645 const CItem &item = Items[i];
646 if (item.IsUserItem() && item.Name.Length() != 1)
647 Indices.Add(i);
648 }
649 }
650
Sort()651 void CFilesDatabase::Sort()
652 {
653 Indices.Sort(CompareFiles, (void *)&Items);
654 }
655
Check()656 bool CFilesDatabase::Check()
657 {
658 UInt64 maxPos = 0;
659 UInt64 prevSection = 0;
660 for(int i = 0; i < Indices.Size(); i++)
661 {
662 const CItem &item = Items[Indices[i]];
663 if (item.Section == 0 || item.IsDir())
664 continue;
665 if (item.Section != prevSection)
666 {
667 prevSection = item.Section;
668 maxPos = 0;
669 continue;
670 }
671 if (item.Offset < maxPos)
672 return false;
673 maxPos = item.Offset + item.Size;
674 if (maxPos < item.Offset)
675 return false;
676 }
677 return true;
678 }
679
OpenHighLevel(IInStream * inStream,CFilesDatabase & database)680 HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database)
681 {
682 {
683 // The NameList file
684 RINOK(DecompressStream(inStream, database, kNameList));
685 /* UInt16 length = */ ReadUInt16();
686 UInt16 numSections = ReadUInt16();
687 for (int i = 0; i < numSections; i++)
688 {
689 CSectionInfo section;
690 UInt16 nameLength = ReadUInt16();
691 UString name;
692 ReadUString(nameLength, name);
693 if (ReadUInt16() != 0)
694 return S_FALSE;
695 if (!ConvertUnicodeToUTF8(name, section.Name))
696 return S_FALSE;
697 database.Sections.Add(section);
698 }
699 }
700
701 int i;
702 for (i = 1; i < database.Sections.Size(); i++)
703 {
704 CSectionInfo §ion = database.Sections[i];
705 AString sectionPrefix = GetSectionPrefix(section.Name);
706 {
707 // Content
708 int index = database.FindItem(sectionPrefix + kContent);
709 if (index < 0)
710 return S_FALSE;
711 const CItem &item = database.Items[index];
712 section.Offset = item.Offset;
713 section.CompressedSize = item.Size;
714 }
715 AString transformPrefix = sectionPrefix + kTransform;
716 if (database.Help2Format)
717 {
718 // Transform List
719 RINOK(DecompressStream(inStream, database, transformPrefix + kTransformList));
720 if ((_chunkSize & 0xF) != 0)
721 return S_FALSE;
722 int numGuids = (int)(_chunkSize / 0x10);
723 if (numGuids < 1)
724 return S_FALSE;
725 for (int i = 0; i < numGuids; i++)
726 {
727 CMethodInfo method;
728 ReadGUID(method.Guid);
729 section.Methods.Add(method);
730 }
731 }
732 else
733 {
734 CMethodInfo method;
735 method.Guid = kChmLzxGuid;
736 section.Methods.Add(method);
737 }
738
739 {
740 // Control Data
741 RINOK(DecompressStream(inStream, database, sectionPrefix + kControlData));
742 for (int mi = 0; mi < section.Methods.Size(); mi++)
743 {
744 CMethodInfo &method = section.Methods[mi];
745 UInt32 numDWORDS = ReadUInt32();
746 if (method.IsLzx())
747 {
748 if (numDWORDS < 5)
749 return S_FALSE;
750 if (ReadUInt32() != NHeader::kLzxcSignature)
751 return S_FALSE;
752 CLzxInfo &li = method.LzxInfo;
753 li.Version = ReadUInt32();
754 if (li.Version != 2 && li.Version != 3)
755 return S_FALSE;
756 li.ResetInterval = ReadUInt32();
757 li.WindowSize = ReadUInt32();
758 li.CacheSize = ReadUInt32();
759 if (
760 li.ResetInterval != 1 &&
761 li.ResetInterval != 2 &&
762 li.ResetInterval != 4 &&
763 li.ResetInterval != 8 &&
764 li.ResetInterval != 16 &&
765 li.ResetInterval != 32 &&
766 li.ResetInterval != 64)
767 return S_FALSE;
768 if (
769 li.WindowSize != 1 &&
770 li.WindowSize != 2 &&
771 li.WindowSize != 4 &&
772 li.WindowSize != 8 &&
773 li.WindowSize != 16 &&
774 li.WindowSize != 32 &&
775 li.WindowSize != 64)
776 return S_FALSE;
777 numDWORDS -= 5;
778 while (numDWORDS-- != 0)
779 ReadUInt32();
780 }
781 else
782 {
783 UInt32 numBytes = numDWORDS * 4;
784 method.ControlData.SetCapacity(numBytes);
785 ReadBytes(method.ControlData, numBytes);
786 }
787 }
788 }
789
790 {
791 // SpanInfo
792 RINOK(DecompressStream(inStream, database, sectionPrefix + kSpanInfo));
793 section.UncompressedSize = ReadUInt64();
794 }
795
796 // read ResetTable for LZX
797 for (int mi = 0; mi < section.Methods.Size(); mi++)
798 {
799 CMethodInfo &method = section.Methods[mi];
800 if (method.IsLzx())
801 {
802 // ResetTable;
803 RINOK(DecompressStream(inStream, database, transformPrefix +
804 method.GetGuidString() + kResetTable));
805 CResetTable &rt = method.LzxInfo.ResetTable;
806 if (_chunkSize < 4)
807 {
808 if (_chunkSize != 0)
809 return S_FALSE;
810 // ResetTable is empty in .chw files
811 if (section.UncompressedSize != 0)
812 return S_FALSE;
813 rt.UncompressedSize = 0;
814 rt.CompressedSize = 0;
815 rt.BlockSize = 0;
816 }
817 else
818 {
819 UInt32 ver = ReadUInt32(); // 2 unknown (possibly a version number)
820 if (ver != 2 && ver != 3)
821 return S_FALSE;
822 UInt32 numEntries = ReadUInt32();
823 if (ReadUInt32() != 8) // Size of table entry (bytes)
824 return S_FALSE;
825 if (ReadUInt32() != 0x28) // Length of table header
826 return S_FALSE;
827 rt.UncompressedSize = ReadUInt64();
828 rt.CompressedSize = ReadUInt64();
829 rt.BlockSize = ReadUInt64(); // 0x8000 block size for locations below
830 if (rt.BlockSize != 0x8000)
831 return S_FALSE;
832 rt.ResetOffsets.Reserve(numEntries);
833 for (UInt32 i = 0; i < numEntries; i++)
834 rt.ResetOffsets.Add(ReadUInt64());
835 }
836 }
837 }
838 }
839
840 database.SetIndices();
841 database.Sort();
842 return database.Check() ? S_OK : S_FALSE;
843 }
844
Open2(IInStream * inStream,const UInt64 * searchHeaderSizeLimit,CFilesDatabase & database)845 HRESULT CInArchive::Open2(IInStream *inStream,
846 const UInt64 *searchHeaderSizeLimit,
847 CFilesDatabase &database)
848 {
849 database.Clear();
850
851 RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &_startPosition));
852
853 database.Help2Format = false;
854 const UInt32 chmVersion = 3;
855 {
856 if (!_inBuffer.Create(1 << 14))
857 return E_OUTOFMEMORY;
858 _inBuffer.SetStream(inStream);
859 _inBuffer.Init();
860 UInt64 value = 0;
861 const int kSignatureSize = 8;
862 UInt64 hxsSignature = NHeader::GetHxsSignature();
863 UInt64 chmSignature = ((UInt64)chmVersion << 32)| NHeader::kItsfSignature;
864 UInt64 limit = 1 << 18;
865 if (searchHeaderSizeLimit)
866 if (limit > *searchHeaderSizeLimit)
867 limit = *searchHeaderSizeLimit;
868
869 for (;;)
870 {
871 Byte b;
872 if (!_inBuffer.ReadByte(b))
873 return S_FALSE;
874 value >>= 8;
875 value |= ((UInt64)b) << ((kSignatureSize - 1) * 8);
876 if (_inBuffer.GetProcessedSize() >= kSignatureSize)
877 {
878 if (value == chmSignature)
879 break;
880 if (value == hxsSignature)
881 {
882 database.Help2Format = true;
883 break;
884 }
885 if (_inBuffer.GetProcessedSize() > limit)
886 return S_FALSE;
887 }
888 }
889 _startPosition += _inBuffer.GetProcessedSize() - kSignatureSize;
890 }
891
892 if (database.Help2Format)
893 {
894 RINOK(OpenHelp2(inStream, database));
895 if (database.NewFormat)
896 return S_OK;
897 }
898 else
899 {
900 RINOK(OpenChm(inStream, database));
901 }
902
903 #ifndef CHM_LOW
904 try
905 {
906 HRESULT res = OpenHighLevel(inStream, database);
907 if (res == S_FALSE)
908 {
909 database.HighLevelClear();
910 return S_OK;
911 }
912 RINOK(res);
913 database.LowLevel = false;
914 }
915 catch(...)
916 {
917 return S_OK;
918 }
919 #endif
920 return S_OK;
921 }
922
Open(IInStream * inStream,const UInt64 * searchHeaderSizeLimit,CFilesDatabase & database)923 HRESULT CInArchive::Open(IInStream *inStream,
924 const UInt64 *searchHeaderSizeLimit,
925 CFilesDatabase &database)
926 {
927 try
928 {
929 HRESULT res = Open2(inStream, searchHeaderSizeLimit, database);
930 _inBuffer.ReleaseStream();
931 return res;
932 }
933 catch(...)
934 {
935 _inBuffer.ReleaseStream();
936 throw;
937 }
938 }
939
940 }}
941