// Archive/ChmIn.cpp #include "StdAfx.h" // #include #include "../../../../C/CpuArch.h" #include "../../../Common/IntToString.h" #include "../../../Common/UTFConvert.h" #include "../../Common/LimitedStreams.h" #include "ChmIn.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) namespace NArchive { namespace NChm { static const UInt32 kSignature_ITSP = 0x50535449; static const UInt32 kSignature_PMGL = 0x4C474D50; static const UInt32 kSignature_LZXC = 0x43585A4C; static const UInt32 kSignature_IFCM = 0x4D434649; static const UInt32 kSignature_AOLL = 0x4C4C4F41; static const UInt32 kSignature_CAOL = 0x4C4F4143; static const UInt32 kSignature_ITSF = 0x46535449; static const UInt32 kSignature_ITOL = 0x4C4F5449; static const UInt32 kSignature_ITLS = 0x534C5449; struct CEnexpectedEndException {}; struct CHeaderErrorException {}; // define CHM_LOW, if you want to see low level items // #define CHM_LOW static const GUID kChmLzxGuid = { 0x7FC28940, 0x9D31, 0x11D0, { 0x9B, 0x27, 0x00, 0xA0, 0xC9, 0x1E, 0x9C, 0x7C } }; static const GUID kHelp2LzxGuid = { 0x0A9007C6, 0x4076, 0x11D3, { 0x87, 0x89, 0x00, 0x00, 0xF8, 0x10, 0x57, 0x54 } }; static const GUID kDesGuid = { 0x67F6E4A2, 0x60BF, 0x11D3, { 0x85, 0x40, 0x00, 0xC0, 0x4F, 0x58, 0xC3, 0xCF } }; static bool AreGuidsEqual(REFGUID g1, REFGUID g2) { if (g1.Data1 != g2.Data1 || g1.Data2 != g2.Data2 || g1.Data3 != g2.Data3) return false; for (int i = 0; i < 8; i++) if (g1.Data4[i] != g2.Data4[i]) return false; return true; } static char GetHex(unsigned v) { return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10))); } static void PrintByte(Byte b, AString &s) { s += GetHex(b >> 4); s += GetHex(b & 0xF); } static void PrintUInt16(UInt16 v, AString &s) { PrintByte((Byte)(v >> 8), s); PrintByte((Byte)v, s); } static void PrintUInt32(UInt32 v, AString &s) { PrintUInt16((UInt16)(v >> 16), s); PrintUInt16((UInt16)v, s); } AString CMethodInfo::GetGuidString() const { AString s; s += '{'; PrintUInt32(Guid.Data1, s); s += '-'; PrintUInt16(Guid.Data2, s); s += '-'; PrintUInt16(Guid.Data3, s); s += '-'; PrintByte(Guid.Data4[0], s); PrintByte(Guid.Data4[1], s); s += '-'; for (int i = 2; i < 8; i++) PrintByte(Guid.Data4[i], s); s += '}'; return s; } bool CMethodInfo::IsLzx() const { if (AreGuidsEqual(Guid, kChmLzxGuid)) return true; return AreGuidsEqual(Guid, kHelp2LzxGuid); } bool CMethodInfo::IsDes() const { return AreGuidsEqual(Guid, kDesGuid); } UString CMethodInfo::GetName() const { UString s; if (IsLzx()) { s.SetFromAscii("LZX:"); char temp[16]; ConvertUInt32ToString(LzxInfo.GetNumDictBits(), temp); s.AddAscii(temp); } else { AString s2; if (IsDes()) s2 = "DES"; else { s2 = GetGuidString(); if (ControlData.Size() > 0) { s2 += ':'; for (size_t i = 0; i < ControlData.Size(); i++) PrintByte(ControlData[i], s2); } } ConvertUTF8ToUnicode(s2, s); } return s; } bool CSectionInfo::IsLzx() const { if (Methods.Size() != 1) return false; return Methods[0].IsLzx(); } UString CSectionInfo::GetMethodName() const { UString s; if (!IsLzx()) { UString temp; if (ConvertUTF8ToUnicode(Name, temp)) s += temp; s.AddAscii(": "); } FOR_VECTOR (i, Methods) { if (i != 0) s.Add_Space(); s += Methods[i].GetName(); } return s; } Byte CInArchive::ReadByte() { Byte b; if (!_inBuffer.ReadByte(b)) throw CEnexpectedEndException(); return b; } void CInArchive::Skip(size_t size) { if (_inBuffer.Skip(size) != size) throw CEnexpectedEndException(); } void CInArchive::ReadBytes(Byte *data, UInt32 size) { if (_inBuffer.ReadBytes(data, size) != size) throw CEnexpectedEndException(); } UInt16 CInArchive::ReadUInt16() { Byte b0, b1; if (!_inBuffer.ReadByte(b0)) throw CEnexpectedEndException(); if (!_inBuffer.ReadByte(b1)) throw CEnexpectedEndException(); return (UInt16)(((UInt16)b1 << 8) | b0); } UInt32 CInArchive::ReadUInt32() { Byte p[4]; ReadBytes(p, 4); return Get32(p); } UInt64 CInArchive::ReadUInt64() { Byte p[8]; ReadBytes(p, 8); return Get64(p); } UInt64 CInArchive::ReadEncInt() { UInt64 val = 0; for (int i = 0; i < 9; i++) { Byte b = ReadByte(); val |= (b & 0x7F); if (b < 0x80) return val; val <<= 7; } throw CHeaderErrorException(); } void CInArchive::ReadGUID(GUID &g) { g.Data1 = ReadUInt32(); g.Data2 = ReadUInt16(); g.Data3 = ReadUInt16(); ReadBytes(g.Data4, 8); } void CInArchive::ReadString(unsigned size, AString &s) { s.Empty(); if (size != 0) { ReadBytes((Byte *)s.GetBuf(size), size); s.ReleaseBuf_CalcLen(size); } } void CInArchive::ReadUString(unsigned size, UString &s) { s.Empty(); while (size-- != 0) { wchar_t c = ReadUInt16(); if (c == 0) { Skip(2 * size); return; } s += c; } } HRESULT CInArchive::ReadChunk(IInStream *inStream, UInt64 pos, UInt64 size) { RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL)); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr limitedStream(streamSpec); streamSpec->SetStream(inStream); streamSpec->Init(size); m_InStreamRef = limitedStream; _inBuffer.SetStream(limitedStream); _inBuffer.Init(); return S_OK; } HRESULT CInArchive::ReadDirEntry(CDatabase &database) { CItem item; UInt64 nameLen = ReadEncInt(); if (nameLen == 0 || nameLen > (1 << 13)) return S_FALSE; ReadString((unsigned)nameLen, item.Name); item.Section = ReadEncInt(); item.Offset = ReadEncInt(); item.Size = ReadEncInt(); database.Items.Add(item); return S_OK; } HRESULT CInArchive::OpenChm(IInStream *inStream, CDatabase &database) { UInt32 headerSize = ReadUInt32(); if (headerSize != 0x60) return S_FALSE; database.PhySize = headerSize; UInt32 unknown1 = ReadUInt32(); if (unknown1 != 0 && unknown1 != 1) // it's 0 in one .sll file return S_FALSE; IsArc = true; /* UInt32 timeStamp = */ ReadUInt32(); // Considered as a big-endian DWORD, it appears to contain seconds (MSB) and // fractional seconds (second byte). // The third and fourth bytes may contain even more fractional bits. // The 4 least significant bits in the last byte are constant. /* UInt32 lang = */ ReadUInt32(); GUID g; ReadGUID(g); // {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC} ReadGUID(g); // {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC} const unsigned kNumSections = 2; UInt64 sectionOffsets[kNumSections]; UInt64 sectionSizes[kNumSections]; unsigned i; for (i = 0; i < kNumSections; i++) { sectionOffsets[i] = ReadUInt64(); sectionSizes[i] = ReadUInt64(); UInt64 end = sectionOffsets[i] + sectionSizes[i]; database.UpdatePhySize(end); } // if (chmVersion == 3) database.ContentOffset = ReadUInt64(); /* else database.ContentOffset = database.StartPosition + 0x58 */ // Section 0 ReadChunk(inStream, sectionOffsets[0], sectionSizes[0]); if (sectionSizes[0] < 0x18) return S_FALSE; if (ReadUInt32() != 0x01FE) return S_FALSE; ReadUInt32(); // unknown: 0 UInt64 fileSize = ReadUInt64(); database.UpdatePhySize(fileSize); ReadUInt32(); // unknown: 0 ReadUInt32(); // unknown: 0 // Section 1: The Directory Listing ReadChunk(inStream, sectionOffsets[1], sectionSizes[1]); if (ReadUInt32() != kSignature_ITSP) return S_FALSE; if (ReadUInt32() != 1) // version return S_FALSE; /* UInt32 dirHeaderSize = */ ReadUInt32(); ReadUInt32(); // 0x0A (unknown) UInt32 dirChunkSize = ReadUInt32(); // $1000 if (dirChunkSize < 32) return S_FALSE; /* UInt32 density = */ ReadUInt32(); // "Density" of quickref section, usually 2. /* UInt32 depth = */ ReadUInt32(); // Depth of the index tree: 1 there is no index, // 2 if there is one level of PMGI chunks. /* UInt32 chunkNumber = */ ReadUInt32(); // Chunk number of root index chunk, -1 if there is none // (though at least one file has 0 despite there being no // index chunk, probably a bug.) /* UInt32 firstPmglChunkNumber = */ ReadUInt32(); // Chunk number of first PMGL (listing) chunk /* UInt32 lastPmglChunkNumber = */ ReadUInt32(); // Chunk number of last PMGL (listing) chunk ReadUInt32(); // -1 (unknown) UInt32 numDirChunks = ReadUInt32(); // Number of directory chunks (total) /* UInt32 windowsLangId = */ ReadUInt32(); ReadGUID(g); // {5D02926A-212E-11D0-9DF9-00A0C922E6EC} ReadUInt32(); // 0x54 (This is the length again) ReadUInt32(); // -1 (unknown) ReadUInt32(); // -1 (unknown) ReadUInt32(); // -1 (unknown) for (UInt32 ci = 0; ci < numDirChunks; ci++) { UInt64 chunkPos = _inBuffer.GetProcessedSize(); if (ReadUInt32() == kSignature_PMGL) { // The quickref area is written backwards from the end of the chunk. // One quickref entry exists for every n entries in the file, where n // is calculated as 1 + (1 << quickref density). So for density = 2, n = 5. UInt32 quickrefLength = ReadUInt32(); // Len of free space and/or quickref area at end of directory chunk if (quickrefLength > dirChunkSize || quickrefLength < 2) return S_FALSE; ReadUInt32(); // Always 0 ReadUInt32(); // Chunk number of previous listing chunk when reading // directory in sequence (-1 if this is the first listing chunk) ReadUInt32(); // Chunk number of next listing chunk when reading // directory in sequence (-1 if this is the last listing chunk) unsigned numItems = 0; for (;;) { UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; UInt32 offsetLimit = dirChunkSize - quickrefLength; if (offset > offsetLimit) return S_FALSE; if (offset == offsetLimit) break; RINOK(ReadDirEntry(database)); numItems++; } Skip(quickrefLength - 2); unsigned rrr = ReadUInt16(); if (rrr != numItems) { // Lazarus 9-26-2 chm contains 0 here. if (rrr != 0) return S_FALSE; } } else Skip(dirChunkSize - 4); } return S_OK; } HRESULT CInArchive::OpenHelp2(IInStream *inStream, CDatabase &database) { if (ReadUInt32() != 1) // version return S_FALSE; if (ReadUInt32() != 0x28) // Location of header section table return S_FALSE; UInt32 numHeaderSections = ReadUInt32(); const unsigned kNumHeaderSectionsMax = 5; if (numHeaderSections != kNumHeaderSectionsMax) return S_FALSE; IsArc = true; ReadUInt32(); // Len of post-header table GUID g; ReadGUID(g); // {0A9007C1-4076-11D3-8789-0000F8105754} // header section table UInt64 sectionOffsets[kNumHeaderSectionsMax]; UInt64 sectionSizes[kNumHeaderSectionsMax]; UInt32 i; for (i = 0; i < numHeaderSections; i++) { sectionOffsets[i] = ReadUInt64(); sectionSizes[i] = ReadUInt64(); UInt64 end = sectionOffsets[i] + sectionSizes[i]; database.UpdatePhySize(end); } // Post-Header ReadUInt32(); // 2 ReadUInt32(); // 0x98: offset to CAOL from beginning of post-header) // ----- Directory information ReadUInt64(); // Chunk number of top-level AOLI chunk in directory, or -1 ReadUInt64(); // Chunk number of first AOLL chunk in directory ReadUInt64(); // Chunk number of last AOLL chunk in directory ReadUInt64(); // 0 (unknown) ReadUInt32(); // $2000 (Directory chunk size of directory) ReadUInt32(); // Quickref density for main directory, usually 2 ReadUInt32(); // 0 (unknown) ReadUInt32(); // Depth of main directory index tree // 1 there is no index, 2 if there is one level of AOLI chunks. ReadUInt64(); // 0 (unknown) UInt64 numDirEntries = ReadUInt64(); // Number of directory entries // ----- Directory Index Information ReadUInt64(); // -1 (unknown, probably chunk number of top-level AOLI in directory index) ReadUInt64(); // Chunk number of first AOLL chunk in directory index ReadUInt64(); // Chunk number of last AOLL chunk in directory index ReadUInt64(); // 0 (unknown) ReadUInt32(); // $200 (Directory chunk size of directory index) ReadUInt32(); // Quickref density for directory index, usually 2 ReadUInt32(); // 0 (unknown) ReadUInt32(); // Depth of directory index index tree. ReadUInt64(); // Possibly flags -- sometimes 1, sometimes 0. ReadUInt64(); // Number of directory index entries (same as number of AOLL // chunks in main directory) // (The obvious guess for the following two fields, which recur in a number // of places, is they are maximum sizes for the directory and directory index. // However, I have seen no direct evidence that this is the case.) ReadUInt32(); // $100000 (Same as field following chunk size in directory) ReadUInt32(); // $20000 (Same as field following chunk size in directory index) ReadUInt64(); // 0 (unknown) if (ReadUInt32() != kSignature_CAOL) return S_FALSE; if (ReadUInt32() != 2) // (Most likely a version number) return S_FALSE; UInt32 caolLength = ReadUInt32(); // $50 (Len of the CAOL section, which includes the ITSF section) if (caolLength >= 0x2C) { /* UInt32 c7 = */ ReadUInt16(); // Unknown. Remains the same when identical files are built. // Does not appear to be a checksum. Many files have // 'HH' (HTML Help?) here, indicating this may be a compiler ID // field. But at least one ITOL/ITLS compiler does not set this // field to a constant value. ReadUInt16(); // 0 (Unknown. Possibly part of 00A4 field) ReadUInt32(); // Unknown. Two values have been seen -- $43ED, and 0. ReadUInt32(); // $2000 (Directory chunk size of directory) ReadUInt32(); // $200 (Directory chunk size of directory index) ReadUInt32(); // $100000 (Same as field following chunk size in directory) ReadUInt32(); // $20000 (Same as field following chunk size in directory index) ReadUInt32(); // 0 (unknown) ReadUInt32(); // 0 (Unknown) if (caolLength == 0x2C) { // fprintf(stdout, "\n !!!NewFormat\n"); // fflush(stdout); database.ContentOffset = 0; // maybe we must add database.StartPosition here? database.NewFormat = true; } else if (caolLength == 0x50) { ReadUInt32(); // 0 (Unknown) if (ReadUInt32() != kSignature_ITSF) return S_FALSE; if (ReadUInt32() != 4) // $4 (Version number -- CHM uses 3) return S_FALSE; if (ReadUInt32() != 0x20) // $20 (length of ITSF) return S_FALSE; UInt32 unknown = ReadUInt32(); if (unknown != 0 && unknown != 1) // = 0 for some HxW files, 1 in other cases; return S_FALSE; database.ContentOffset = database.StartPosition + ReadUInt64(); /* UInt32 timeStamp = */ ReadUInt32(); // A timestamp of some sort. // Considered as a big-endian DWORD, it appears to contain // seconds (MSB) and fractional seconds (second byte). // The third and fourth bytes may contain even more fractional // bits. The 4 least significant bits in the last byte are constant. /* UInt32 lang = */ ReadUInt32(); // BE? } else return S_FALSE; } // Section 0 ReadChunk(inStream, database.StartPosition + sectionOffsets[0], sectionSizes[0]); if (sectionSizes[0] < 0x18) return S_FALSE; if (ReadUInt32() != 0x01FE) return S_FALSE; ReadUInt32(); // unknown: 0 UInt64 fileSize = ReadUInt64(); database.UpdatePhySize(fileSize); ReadUInt32(); // unknown: 0 ReadUInt32(); // unknown: 0 // Section 1: The Directory Listing ReadChunk(inStream, database.StartPosition + sectionOffsets[1], sectionSizes[1]); if (ReadUInt32() != kSignature_IFCM) return S_FALSE; if (ReadUInt32() != 1) // (probably a version number) return S_FALSE; UInt32 dirChunkSize = ReadUInt32(); // $2000 if (dirChunkSize < 64) return S_FALSE; ReadUInt32(); // $100000 (unknown) ReadUInt32(); // -1 (unknown) ReadUInt32(); // -1 (unknown) UInt32 numDirChunks = ReadUInt32(); ReadUInt32(); // 0 (unknown, probably high word of above) for (UInt32 ci = 0; ci < numDirChunks; ci++) { UInt64 chunkPos = _inBuffer.GetProcessedSize(); if (ReadUInt32() == kSignature_AOLL) { UInt32 quickrefLength = ReadUInt32(); // Len of quickref area at end of directory chunk if (quickrefLength > dirChunkSize || quickrefLength < 2) return S_FALSE; ReadUInt64(); // Directory chunk number // This must match physical position in file, that is // the chunk size times the chunk number must be the // offset from the end of the directory header. ReadUInt64(); // Chunk number of previous listing chunk when reading // directory in sequence (-1 if first listing chunk) ReadUInt64(); // Chunk number of next listing chunk when reading // directory in sequence (-1 if last listing chunk) ReadUInt64(); // Number of first listing entry in this chunk ReadUInt32(); // 1 (unknown -- other values have also been seen here) ReadUInt32(); // 0 (unknown) unsigned numItems = 0; for (;;) { UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; UInt32 offsetLimit = dirChunkSize - quickrefLength; if (offset > offsetLimit) return S_FALSE; if (offset == offsetLimit) break; if (database.NewFormat) { UInt16 nameLen = ReadUInt16(); if (nameLen == 0) return S_FALSE; UString name; ReadUString((unsigned)nameLen, name); AString s; ConvertUnicodeToUTF8(name, s); Byte b = ReadByte(); s.Add_Space(); PrintByte(b, s); s.Add_Space(); UInt64 len = ReadEncInt(); // then number of items ? // then length ? // then some data (binary encoding?) while (len-- != 0) { b = ReadByte(); PrintByte(b, s); } database.NewFormatString += s; database.NewFormatString += "\r\n"; } else { RINOK(ReadDirEntry(database)); } numItems++; } Skip(quickrefLength - 2); if (ReadUInt16() != numItems) return S_FALSE; if (numItems > numDirEntries) return S_FALSE; numDirEntries -= numItems; } else Skip(dirChunkSize - 4); } return numDirEntries == 0 ? S_OK : S_FALSE; } HRESULT CInArchive::DecompressStream(IInStream *inStream, const CDatabase &database, const AString &name) { int index = database.FindItem(name); if (index < 0) return S_FALSE; const CItem &item = database.Items[index]; _chunkSize = item.Size; return ReadChunk(inStream, database.ContentOffset + item.Offset, item.Size); } #define DATA_SPACE "::DataSpace/" static const char *kNameList = DATA_SPACE "NameList"; static const char *kStorage = DATA_SPACE "Storage/"; static const char *kContent = "Content"; static const char *kControlData = "ControlData"; static const char *kSpanInfo = "SpanInfo"; static const char *kTransform = "Transform/"; static const char *kResetTable = "/InstanceData/ResetTable"; static const char *kTransformList = "List"; static AString GetSectionPrefix(const AString &name) { AString s = kStorage; s += name; s += '/'; return s; } #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } static int CompareFiles(const unsigned *p1, const unsigned *p2, void *param) { const CObjectVector &items = *(const CObjectVector *)param; const CItem &item1 = items[*p1]; const CItem &item2 = items[*p2]; bool isDir1 = item1.IsDir(); bool isDir2 = item2.IsDir(); if (isDir1 && !isDir2) return -1; if (isDir2) { if (!isDir1) return 1; } else { RINOZ(MyCompare(item1.Section, item2.Section)); RINOZ(MyCompare(item1.Offset, item2.Offset)); RINOZ(MyCompare(item1.Size, item2.Size)); } return MyCompare(*p1, *p2); } void CFilesDatabase::SetIndices() { FOR_VECTOR (i, Items) { const CItem &item = Items[i]; if (item.IsUserItem() && item.Name.Len() != 1) Indices.Add(i); } } void CFilesDatabase::Sort() { Indices.Sort(CompareFiles, (void *)&Items); } bool CFilesDatabase::Check() { UInt64 maxPos = 0; UInt64 prevSection = 0; FOR_VECTOR (i, Indices) { const CItem &item = Items[Indices[i]]; if (item.Section == 0 || item.IsDir()) continue; if (item.Section != prevSection) { prevSection = item.Section; maxPos = 0; continue; } if (item.Offset < maxPos) return false; maxPos = item.Offset + item.Size; if (maxPos < item.Offset) return false; } return true; } bool CFilesDatabase::CheckSectionRefs() { FOR_VECTOR (i, Indices) { const CItem &item = Items[Indices[i]]; if (item.Section == 0 || item.IsDir()) continue; if (item.Section >= Sections.Size()) return false; } return true; } static int inline GetLog(UInt32 num) { for (int i = 0; i < 32; i++) if (((UInt32)1 << i) == num) return i; return -1; } HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) { { // The NameList file RINOK(DecompressStream(inStream, database, kNameList)); /* UInt16 length = */ ReadUInt16(); UInt16 numSections = ReadUInt16(); for (unsigned i = 0; i < numSections; i++) { CSectionInfo section; UInt16 nameLen = ReadUInt16(); UString name; ReadUString(nameLen, name); if (ReadUInt16() != 0) return S_FALSE; ConvertUnicodeToUTF8(name, section.Name); // if (!ConvertUnicodeToUTF8(name, section.Name)) return S_FALSE; database.Sections.Add(section); } } unsigned si; for (si = 1; si < database.Sections.Size(); si++) { CSectionInfo §ion = database.Sections[si]; AString sectionPrefix = GetSectionPrefix(section.Name); { // Content int index = database.FindItem(sectionPrefix + kContent); if (index < 0) return S_FALSE; const CItem &item = database.Items[index]; section.Offset = item.Offset; section.CompressedSize = item.Size; } AString transformPrefix = sectionPrefix + kTransform; if (database.Help2Format) { // Transform List RINOK(DecompressStream(inStream, database, transformPrefix + kTransformList)); if ((_chunkSize & 0xF) != 0) return S_FALSE; unsigned numGuids = (unsigned)(_chunkSize / 0x10); if (numGuids < 1) return S_FALSE; for (unsigned i = 0; i < numGuids; i++) { CMethodInfo method; ReadGUID(method.Guid); section.Methods.Add(method); } } else { CMethodInfo method; method.Guid = kChmLzxGuid; section.Methods.Add(method); } { // Control Data RINOK(DecompressStream(inStream, database, sectionPrefix + kControlData)); FOR_VECTOR (mi, section.Methods) { CMethodInfo &method = section.Methods[mi]; UInt32 numDWORDS = ReadUInt32(); if (method.IsLzx()) { if (numDWORDS < 5) return S_FALSE; if (ReadUInt32() != kSignature_LZXC) return S_FALSE; CLzxInfo &li = method.LzxInfo; li.Version = ReadUInt32(); if (li.Version != 2 && li.Version != 3) return S_FALSE; { // There is bug in VC6, if we use function call as parameter for inline function UInt32 val32 = ReadUInt32(); int n = GetLog(val32); if (n < 0 || n > 16) return S_FALSE; li.ResetIntervalBits = n; } { UInt32 val32 = ReadUInt32(); int n = GetLog(val32); if (n < 0 || n > 16) return S_FALSE; li.WindowSizeBits = n; } li.CacheSize = ReadUInt32(); numDWORDS -= 5; while (numDWORDS-- != 0) ReadUInt32(); } else { UInt32 numBytes = numDWORDS * 4; method.ControlData.Alloc(numBytes); ReadBytes(method.ControlData, numBytes); } } } { // SpanInfo RINOK(DecompressStream(inStream, database, sectionPrefix + kSpanInfo)); section.UncompressedSize = ReadUInt64(); } // read ResetTable for LZX FOR_VECTOR (mi, section.Methods) { CMethodInfo &method = section.Methods[mi]; if (method.IsLzx()) { // ResetTable; RINOK(DecompressStream(inStream, database, transformPrefix + method.GetGuidString() + kResetTable)); CResetTable &rt = method.LzxInfo.ResetTable; if (_chunkSize < 4) { if (_chunkSize != 0) return S_FALSE; // ResetTable is empty in .chw files if (section.UncompressedSize != 0) return S_FALSE; rt.UncompressedSize = 0; rt.CompressedSize = 0; // rt.BlockSize = 0; } else { UInt32 ver = ReadUInt32(); // 2 unknown (possibly a version number) if (ver != 2 && ver != 3) return S_FALSE; UInt32 numEntries = ReadUInt32(); const unsigned kEntrySize = 8; if (ReadUInt32() != kEntrySize) return S_FALSE; const unsigned kRtHeaderSize = 4 * 4 + 8 * 3; if (ReadUInt32() != kRtHeaderSize) return S_FALSE; if (kRtHeaderSize + kEntrySize * (UInt64)numEntries != _chunkSize) return S_FALSE; rt.UncompressedSize = ReadUInt64(); rt.CompressedSize = ReadUInt64(); UInt64 blockSize = ReadUInt64(); if (blockSize != kBlockSize) return S_FALSE; UInt64 numBlocks = (rt.UncompressedSize + kBlockSize + 1) / kBlockSize; if (numEntries != numBlocks && numEntries != numBlocks + 1) return S_FALSE; rt.ResetOffsets.ClearAndReserve(numEntries); for (UInt32 i = 0; i < numEntries; i++) { UInt64 v = ReadUInt64(); if (i != 0 && v < rt.ResetOffsets[i - 1]) return S_FALSE; rt.ResetOffsets.AddInReserved(v); } if (numEntries != 0) if (rt.ResetOffsets[0] != 0) return S_FALSE; if (numEntries == numBlocks + 1) { // Lazarus 9-26-2 chm contains additional entty if (rt.ResetOffsets.Back() != rt.CompressedSize) return S_FALSE; } } } } } database.SetIndices(); database.Sort(); return database.Check() ? S_OK : S_FALSE; } HRESULT CInArchive::Open2(IInStream *inStream, const UInt64 *searchHeaderSizeLimit, CFilesDatabase &database) { IsArc = false; HeadersError = false; UnexpectedEnd = false; UnsupportedFeature = false; database.Clear(); database.Help2Format = _help2; const UInt32 chmVersion = 3; RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &database.StartPosition)); if (!_inBuffer.Create(1 << 14)) return E_OUTOFMEMORY; _inBuffer.SetStream(inStream); _inBuffer.Init(); if (_help2) { const unsigned kSignatureSize = 8; const UInt64 signature = ((UInt64)kSignature_ITLS << 32) | kSignature_ITOL; UInt64 limit = 1 << 18; if (searchHeaderSizeLimit) if (limit > *searchHeaderSizeLimit) limit = *searchHeaderSizeLimit; UInt64 val = 0; for (;;) { Byte b; if (!_inBuffer.ReadByte(b)) return S_FALSE; val >>= 8; val |= ((UInt64)b) << ((kSignatureSize - 1) * 8); if (_inBuffer.GetProcessedSize() >= kSignatureSize) { if (val == signature) break; if (_inBuffer.GetProcessedSize() > limit) return S_FALSE; } } database.StartPosition += _inBuffer.GetProcessedSize() - kSignatureSize; RINOK(OpenHelp2(inStream, database)); if (database.NewFormat) return S_OK; } else { if (ReadUInt32() != kSignature_ITSF) return S_FALSE; if (ReadUInt32() != chmVersion) return S_FALSE; RINOK(OpenChm(inStream, database)); } #ifndef CHM_LOW try { try { HRESULT res = OpenHighLevel(inStream, database); if (res == S_FALSE) { UnsupportedFeature = true; database.HighLevelClear(); return S_OK; } RINOK(res); if (!database.CheckSectionRefs()) HeadersError = true; database.LowLevel = false; } catch(...) { database.HighLevelClear(); throw; } } // catch(const CInBufferException &e) { return e.ErrorCode; } catch(CEnexpectedEndException &) { UnexpectedEnd = true; } catch(CHeaderErrorException &) { HeadersError = true; } catch(...) { throw; } #endif return S_OK; } HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *searchHeaderSizeLimit, CFilesDatabase &database) { try { try { HRESULT res = Open2(inStream, searchHeaderSizeLimit, database); m_InStreamRef.Release(); return res; } catch(...) { m_InStreamRef.Release(); throw; } } catch(const CInBufferException &e) { return e.ErrorCode; } catch(CEnexpectedEndException &) { UnexpectedEnd = true; } catch(CHeaderErrorException &) { HeadersError = true; } return S_FALSE; } }}