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