1056 lines
29 KiB
C++
1056 lines
29 KiB
C++
// Archive/ChmIn.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
// #include <stdio.h>
|
|
|
|
#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<ISequentialInStream> 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<CItem> &items = *(const CObjectVector<CItem> *)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;
|
|
}
|
|
|
|
}}
|