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 &section = 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