// Archive/WimIn.cpp #include "StdAfx.h" #include "../../../../C/CpuArch.h" #include "Common/IntToString.h" #include "../../Common/StreamUtils.h" #include "../../Common/StreamObjects.h" #include "../../Common/LimitedStreams.h" #include "../Common/OutStreamWithSha1.h" #include "WimIn.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) namespace NArchive { namespace NWim { namespace NXpress { class CDecoderFlusher { CDecoder *m_Decoder; public: bool NeedFlush; CDecoderFlusher(CDecoder *decoder): m_Decoder(decoder), NeedFlush(true) {} ~CDecoderFlusher() { if (NeedFlush) m_Decoder->Flush(); m_Decoder->ReleaseStreams(); } }; HRESULT CDecoder::CodeSpec(UInt32 outSize) { { Byte levels[kMainTableSize]; for (unsigned i = 0; i < kMainTableSize; i += 2) { Byte b = m_InBitStream.DirectReadByte(); levels[i] = b & 0xF; levels[i + 1] = b >> 4; } if (!m_MainDecoder.SetCodeLengths(levels)) return S_FALSE; } while (outSize > 0) { UInt32 number = m_MainDecoder.DecodeSymbol(&m_InBitStream); if (number < 256) { m_OutWindowStream.PutByte((Byte)number); outSize--; } else { if (number >= kMainTableSize) return S_FALSE; UInt32 posLenSlot = number - 256; UInt32 posSlot = posLenSlot / kNumLenSlots; UInt32 len = posLenSlot % kNumLenSlots; UInt32 distance = (1 << posSlot) - 1 + m_InBitStream.ReadBits(posSlot); if (len == kNumLenSlots - 1) { len = m_InBitStream.DirectReadByte(); if (len == 0xFF) { len = m_InBitStream.DirectReadByte(); len |= (UInt32)m_InBitStream.DirectReadByte() << 8; } else len += kNumLenSlots - 1; } len += kMatchMinLen; UInt32 locLen = (len <= outSize ? len : outSize); if (!m_OutWindowStream.CopyBlock(distance, locLen)) return S_FALSE; len -= locLen; outSize -= locLen; if (len != 0) return S_FALSE; } } return S_OK; } const UInt32 kDictSize = (1 << kNumPosSlots); HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, UInt32 outSize) { if (!m_OutWindowStream.Create(kDictSize) || !m_InBitStream.Create(1 << 16)) return E_OUTOFMEMORY; CDecoderFlusher flusher(this); m_InBitStream.SetStream(inStream); m_OutWindowStream.SetStream(outStream); m_InBitStream.Init(); m_OutWindowStream.Init(false); RINOK(CodeSpec(outSize)); flusher.NeedFlush = false; return Flush(); } HRESULT CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, UInt32 outSize) { try { return CodeReal(inStream, outStream, outSize); } catch(const CInBufferException &e) { return e.ErrorCode; } \ catch(const CLzOutWindowException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } } HRESULT CUnpacker::Unpack(IInStream *inStream, const CResource &resource, bool lzxMode, ISequentialOutStream *outStream, ICompressProgressInfo *progress) { RINOK(inStream->Seek(resource.Offset, STREAM_SEEK_SET, NULL)); CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream(); CMyComPtr limitedStream = limitedStreamSpec; limitedStreamSpec->SetStream(inStream); if (!copyCoder) { copyCoderSpec = new NCompress::CCopyCoder; copyCoder = copyCoderSpec; } if (!resource.IsCompressed()) { if (resource.PackSize != resource.UnpackSize) return S_FALSE; limitedStreamSpec->Init(resource.PackSize); return copyCoder->Code(limitedStreamSpec, outStream, NULL, NULL, progress); } if (resource.UnpackSize == 0) return S_OK; UInt64 numChunks = (resource.UnpackSize + kChunkSize - 1) >> kChunkSizeBits; unsigned entrySize = ((resource.UnpackSize > (UInt64)1 << 32) ? 8 : 4); UInt64 sizesBufSize64 = entrySize * (numChunks - 1); size_t sizesBufSize = (size_t)sizesBufSize64; if (sizesBufSize != sizesBufSize64) return E_OUTOFMEMORY; if (sizesBufSize > sizesBuf.GetCapacity()) { sizesBuf.Free(); sizesBuf.SetCapacity(sizesBufSize); } RINOK(ReadStream_FALSE(inStream, (Byte *)sizesBuf, sizesBufSize)); const Byte *p = (const Byte *)sizesBuf; if (lzxMode && !lzxDecoder) { lzxDecoderSpec = new NCompress::NLzx::CDecoder(true); lzxDecoder = lzxDecoderSpec; RINOK(lzxDecoderSpec->SetParams(kChunkSizeBits)); } UInt64 baseOffset = resource.Offset + sizesBufSize64; UInt64 outProcessed = 0; for (UInt32 i = 0; i < (UInt32)numChunks; i++) { UInt64 offset = 0; if (i > 0) { offset = (entrySize == 4) ? Get32(p): Get64(p); p += entrySize; } UInt64 nextOffset = resource.PackSize - sizesBufSize64; if (i + 1 < (UInt32)numChunks) nextOffset = (entrySize == 4) ? Get32(p): Get64(p); if (nextOffset < offset) return S_FALSE; RINOK(inStream->Seek(baseOffset + offset, STREAM_SEEK_SET, NULL)); UInt64 inSize = nextOffset - offset; limitedStreamSpec->Init(inSize); if (progress) { RINOK(progress->SetRatioInfo(&offset, &outProcessed)); } UInt32 outSize = kChunkSize; if (outProcessed + outSize > resource.UnpackSize) outSize = (UInt32)(resource.UnpackSize - outProcessed); UInt64 outSize64 = outSize; if (inSize == outSize) { RINOK(copyCoder->Code(limitedStreamSpec, outStream, NULL, &outSize64, NULL)); } else { if (lzxMode) { lzxDecoderSpec->SetKeepHistory(false); RINOK(lzxDecoder->Code(limitedStreamSpec, outStream, NULL, &outSize64, NULL)); } else { RINOK(xpressDecoder.Code(limitedStreamSpec, outStream, outSize)); } } outProcessed += outSize; } return S_OK; } HRESULT CUnpacker::Unpack(IInStream *inStream, const CResource &resource, bool lzxMode, ISequentialOutStream *outStream, ICompressProgressInfo *progress, Byte *digest) { COutStreamWithSha1 *shaStreamSpec = new COutStreamWithSha1(); CMyComPtr shaStream = shaStreamSpec; shaStreamSpec->SetStream(outStream); shaStreamSpec->Init(digest != NULL); HRESULT result = Unpack(inStream, resource, lzxMode, shaStream, progress); if (digest) shaStreamSpec->Final(digest); return result; } static HRESULT UnpackData(IInStream *inStream, const CResource &resource, bool lzxMode, CByteBuffer &buf, Byte *digest) { size_t size = (size_t)resource.UnpackSize; if (size != resource.UnpackSize) return E_OUTOFMEMORY; buf.Free(); buf.SetCapacity(size); CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream(); CMyComPtr outStream = outStreamSpec; outStreamSpec->Init((Byte *)buf, size); CUnpacker unpacker; return unpacker.Unpack(inStream, resource, lzxMode, outStream, NULL, digest); } void CResource::Parse(const Byte *p) { Flags = p[7]; PackSize = Get64(p) & (((UInt64)1 << 56) - 1); Offset = Get64(p + 8); UnpackSize = Get64(p + 16); } #define GetResource(p, res) res.Parse(p) static void GetStream(bool oldVersion, const Byte *p, CStreamInfo &s) { s.Resource.Parse(p); if (oldVersion) { s.PartNumber = 1; s.Id = Get32(p + 24); s.RefCount = Get32(p + 28); memcpy(s.Hash, p + 32, kHashSize); } else { s.PartNumber = Get16(p + 24); s.RefCount = Get32(p + 26); memcpy(s.Hash, p + 30, kHashSize); } } static const wchar_t *kLongPath = L"[LongPath]"; UString CDatabase::GetItemPath(const int index1) const { int size = 0; int index = index1; int newLevel; for (newLevel = 0;; newLevel = 1) { const CItem &item = Items[index]; index = item.Parent; if (index >= 0 || !SkipRoot) size += item.Name.Length() + newLevel; if (index < 0) break; if ((UInt32)size >= ((UInt32)1 << 16)) return kLongPath; } wchar_t temp[16]; int imageLen = 0; if (ShowImageNumber) { ConvertUInt32ToString(-1 - index, temp); imageLen = MyStringLen(temp); size += imageLen + 1; } if ((UInt32)size >= ((UInt32)1 << 16)) return kLongPath; UString path; wchar_t *s = path.GetBuffer(size); s[size] = 0; if (ShowImageNumber) { memcpy(s, temp, imageLen * sizeof(wchar_t)); s[imageLen] = WCHAR_PATH_SEPARATOR; } index = index1; for (newLevel = 0;; newLevel = 1) { const CItem &item = Items[index]; index = item.Parent; if (index >= 0 || !SkipRoot) { if (newLevel) s[--size] = WCHAR_PATH_SEPARATOR; size -= item.Name.Length(); memcpy(s + size, item.Name, sizeof(wchar_t) * item.Name.Length()); } if (index < 0) { path.ReleaseBuffer(); return path; } } } static void GetFileTimeFromMem(const Byte *p, FILETIME *ft) { ft->dwLowDateTime = Get32(p); ft->dwHighDateTime = Get32(p + 4); } static HRESULT ReadName(const Byte *p, int size, UString &dest) { if (size == 0) return S_OK; if (Get16(p + size) != 0) return S_FALSE; wchar_t *s = dest.GetBuffer(size / 2); for (int i = 0; i <= size; i += 2) *s++ = Get16(p + i); dest.ReleaseBuffer(); return S_OK; } HRESULT CDatabase::ParseDirItem(size_t pos, int parent) { if ((pos & 7) != 0) return S_FALSE; int prevIndex = -1; for (int numItems = 0;; numItems++) { if (OpenCallback) { UInt64 numFiles = Items.Size(); if ((numFiles & 0x3FF) == 0) { RINOK(OpenCallback->SetCompleted(&numFiles, NULL)); } } size_t rem = DirSize - pos; if (pos < DirStartOffset || pos > DirSize || rem < 8) return S_FALSE; const Byte *p = DirData + pos; UInt64 len = Get64(p); if (len == 0) { if (parent < 0 && numItems != 1) SkipRoot = false; DirProcessed += 8; return S_OK; } if ((len & 7) != 0 || rem < len) return S_FALSE; if (!IsOldVersion) if (len < 0x28) return S_FALSE; DirProcessed += (size_t)len; if (DirProcessed > DirSize) return S_FALSE; int extraOffset = 0; if (IsOldVersion) { if (len < 0x40 || (/* Get32(p + 12) == 0 && */ Get32(p + 0x14) != 0)) { extraOffset = 0x10; } } else if (Get64(p + 8) == 0) extraOffset = 0x24; if (extraOffset) { if (prevIndex == -1) return S_FALSE; UInt32 fileNameLen = Get16(p + extraOffset); if ((fileNameLen & 1) != 0) return S_FALSE; /* Probably different versions of ImageX can use different number of additional ZEROs. So we don't use exact check. */ UInt32 fileNameLen2 = (fileNameLen == 0 ? fileNameLen : fileNameLen + 2); if (((extraOffset + 2 + fileNameLen2 + 6) & ~7) > len) return S_FALSE; UString name; RINOK(ReadName(p + extraOffset + 2, fileNameLen, name)); CItem &prevItem = Items[prevIndex]; if (name.IsEmpty() && !prevItem.HasStream()) { if (IsOldVersion) prevItem.Id = Get32(p + 8); else memcpy(prevItem.Hash, p + 0x10, kHashSize); } else { CItem item; item.Name = prevItem.Name + L':' + name; item.CTime = prevItem.CTime; item.ATime = prevItem.ATime; item.MTime = prevItem.MTime; if (IsOldVersion) { item.Id = Get32(p + 8); memset(item.Hash, 0, kHashSize); } else memcpy(item.Hash, p + 0x10, kHashSize); item.Attrib = 0; item.Order = Order++; item.Parent = parent; Items.Add(item); } pos += (size_t)len; continue; } UInt32 dirRecordSize = IsOldVersion ? kDirRecordSizeOld : kDirRecordSize; if (len < dirRecordSize) return S_FALSE; CItem item; item.Attrib = Get32(p + 8); // item.SecurityId = Get32(p + 0xC); UInt64 subdirOffset = Get64(p + 0x10); UInt32 timeOffset = IsOldVersion ? 0x18: 0x28; GetFileTimeFromMem(p + timeOffset, &item.CTime); GetFileTimeFromMem(p + timeOffset + 8, &item.ATime); GetFileTimeFromMem(p + timeOffset + 16, &item.MTime); if (IsOldVersion) { item.Id = Get32(p + 0x10); memset(item.Hash, 0, kHashSize); } else { memcpy(item.Hash, p + 0x40, kHashSize); } // UInt32 numStreams = Get16(p + dirRecordSize - 6); UInt32 shortNameLen = Get16(p + dirRecordSize - 4); UInt32 fileNameLen = Get16(p + dirRecordSize - 2); if ((shortNameLen & 1) != 0 || (fileNameLen & 1) != 0) return S_FALSE; UInt32 shortNameLen2 = (shortNameLen == 0 ? shortNameLen : shortNameLen + 2); UInt32 fileNameLen2 = (fileNameLen == 0 ? fileNameLen : fileNameLen + 2); if (((dirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7) > len) return S_FALSE; p += dirRecordSize; RINOK(ReadName(p, fileNameLen, item.Name)); RINOK(ReadName(p + fileNameLen2, shortNameLen, item.ShortName)); if (parent < 0 && (shortNameLen || fileNameLen || !item.IsDir())) SkipRoot = false; /* // there are some extra data for some files. p -= dirRecordSize; p += ((dirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7); if (((dirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7) != len) p = p; */ /* if (parent >= 0) { UString s = GetItemPath(parent) + L"\\" + item.Name; printf("\n%s %8x %S", item.IsDir() ? "D" : " ", (int)subdirOffset, (const wchar_t *)s); } */ if (fileNameLen == 0 && item.IsDir() && !item.HasStream()) item.Attrib = 0x10; // some swm archives have system/hidden attributes for root item.Parent = parent; prevIndex = Items.Add(item); if (item.IsDir() && subdirOffset != 0) { RINOK(ParseDirItem((size_t)subdirOffset, prevIndex)); } Items[prevIndex].Order = Order++; pos += (size_t)len; } } HRESULT CDatabase::ParseImageDirs(const CByteBuffer &buf, int parent) { DirData = buf; DirSize = buf.GetCapacity(); size_t pos = 0; if (DirSize < 8) return S_FALSE; const Byte *p = DirData; UInt32 totalLength = Get32(p); if (IsOldVersion) { for (pos = 4;; pos += 8) { if (pos + 4 > DirSize) return S_FALSE; UInt32 n = Get32(p + pos); if (n == 0) break; if (pos + 8 > DirSize) return S_FALSE; totalLength += Get32(p + pos + 4); if (totalLength > DirSize) return S_FALSE; } pos += totalLength + 4; pos = (pos + 7) & ~(size_t)7; if (pos > DirSize) return S_FALSE; } else { // UInt32 numEntries = Get32(p + 4); pos += 8; { /* CRecordVector entryLens; UInt64 sum = 0; for (UInt32 i = 0; i < numEntries; i++) { if (pos + 8 > DirSize) return S_FALSE; UInt64 len = Get64(p + pos); entryLens.Add(len); sum += len; pos += 8; } pos += (size_t)sum; // skip security descriptors while ((pos & 7) != 0) pos++; if (pos != totalLength) return S_FALSE; */ if (totalLength == 0) pos = 8; else if (totalLength < 8) return S_FALSE; else pos = totalLength; } } DirStartOffset = DirProcessed = pos; RINOK(ParseDirItem(pos, parent)); if (DirProcessed == DirSize) return S_OK; /* Original program writes additional 8 bytes (END_OF_ROOT_FOLDER), but reference to that folder is empty */ if (DirProcessed == DirSize - 8 && DirProcessed - DirStartOffset == 112 && Get64(p + DirSize - 8) == 0) return S_OK; return S_FALSE; } HRESULT CHeader::Parse(const Byte *p) { UInt32 headerSize = Get32(p + 8); Version = Get32(p + 0x0C); Flags = Get32(p + 0x10); if (!IsSupported()) return S_FALSE; ChunkSize = Get32(p + 0x14); if (ChunkSize != kChunkSize && ChunkSize != 0) return S_FALSE; int offset; if (IsOldVersion()) { if (headerSize != 0x60) return S_FALSE; memset(Guid, 0, 16); offset = 0x18; PartNumber = 1; NumParts = 1; } else { if (headerSize < 0x74) return S_FALSE; memcpy(Guid, p + 0x18, 16); PartNumber = Get16(p + 0x28); NumParts = Get16(p + 0x2A); offset = 0x2C; if (IsNewVersion()) { NumImages = Get32(p + offset); offset += 4; } } GetResource(p + offset, OffsetResource); GetResource(p + offset + 0x18, XmlResource); GetResource(p + offset + 0x30, MetadataResource); if (IsNewVersion()) { if (headerSize < 0xD0) return S_FALSE; BootIndex = Get32(p + 0x48); IntegrityResource.Parse(p + offset + 0x4C); } return S_OK; } const Byte kSignature[kSignatureSize] = { 'M', 'S', 'W', 'I', 'M', 0, 0, 0 }; HRESULT ReadHeader(IInStream *inStream, CHeader &h) { Byte p[kHeaderSizeMax]; RINOK(ReadStream_FALSE(inStream, p, kHeaderSizeMax)); if (memcmp(p, kSignature, kSignatureSize) != 0) return S_FALSE; return h.Parse(p); } static HRESULT ReadStreams(bool oldVersion, IInStream *inStream, const CHeader &h, CDatabase &db) { CByteBuffer offsetBuf; RINOK(UnpackData(inStream, h.OffsetResource, h.IsLzxMode(), offsetBuf, NULL)); size_t i; size_t streamInfoSize = oldVersion ? kStreamInfoSize + 2 : kStreamInfoSize; for (i = 0; offsetBuf.GetCapacity() - i >= streamInfoSize; i += streamInfoSize) { CStreamInfo s; GetStream(oldVersion, (const Byte *)offsetBuf + i, s); if (s.PartNumber == h.PartNumber) db.Streams.Add(s); } return (i == offsetBuf.GetCapacity()) ? S_OK : S_FALSE; } static bool IsEmptySha(const Byte *data) { for (int i = 0; i < kHashSize; i++) if (data[i] != 0) return false; return true; } HRESULT CDatabase::Open(IInStream *inStream, const CHeader &h, CByteBuffer &xml, IArchiveOpenCallback *openCallback) { OpenCallback = openCallback; IsOldVersion = h.IsOldVersion(); RINOK(UnpackData(inStream, h.XmlResource, h.IsLzxMode(), xml, NULL)); RINOK(ReadStreams(h.IsOldVersion(), inStream, h, *this)); bool needBootMetadata = !h.MetadataResource.IsEmpty(); Order = 0; if (h.PartNumber == 1) { int imageIndex = 1; for (int i = 0; i < Streams.Size(); i++) { // if (imageIndex > 1) break; const CStreamInfo &si = Streams[i]; if (!si.Resource.IsMetadata() || si.PartNumber != h.PartNumber) continue; Byte hash[kHashSize]; CByteBuffer metadata; RINOK(UnpackData(inStream, si.Resource, h.IsLzxMode(), metadata, hash)); if (memcmp(hash, si.Hash, kHashSize) != 0 && !(h.IsOldVersion() && IsEmptySha(si.Hash))) return S_FALSE; NumImages++; RINOK(ParseImageDirs(metadata, -(int)(++imageIndex))); if (needBootMetadata) if (h.MetadataResource.Offset == si.Resource.Offset) needBootMetadata = false; } } if (needBootMetadata) { CByteBuffer metadata; RINOK(UnpackData(inStream, h.MetadataResource, h.IsLzxMode(), metadata, NULL)); RINOK(ParseImageDirs(metadata, -1)); NumImages++; } return S_OK; } static int CompareStreamsByPos(const CStreamInfo *p1, const CStreamInfo *p2, void * /* param */) { int res = MyCompare(p1->PartNumber, p2->PartNumber); if (res != 0) return res; return MyCompare(p1->Resource.Offset, p2->Resource.Offset); } static int CompareIDs(const int *p1, const int *p2, void *param) { const CRecordVector &streams = *(const CRecordVector *)param; return MyCompare(streams[*p1].Id, streams[*p2].Id); } static int CompareHashRefs(const int *p1, const int *p2, void *param) { const CRecordVector &streams = *(const CRecordVector *)param; return memcmp(streams[*p1].Hash, streams[*p2].Hash, kHashSize); } static int FindId(const CRecordVector &streams, const CIntVector &sortedByHash, UInt32 id) { int left = 0, right = streams.Size(); while (left != right) { int mid = (left + right) / 2; int streamIndex = sortedByHash[mid]; UInt32 id2 = streams[streamIndex].Id; if (id == id2) return streamIndex; if (id < id2) right = mid; else left = mid + 1; } return -1; } static int FindHash(const CRecordVector &streams, const CIntVector &sortedByHash, const Byte *hash) { int left = 0, right = streams.Size(); while (left != right) { int mid = (left + right) / 2; int streamIndex = sortedByHash[mid]; UInt32 i; const Byte *hash2 = streams[streamIndex].Hash; for (i = 0; i < kHashSize; i++) if (hash[i] != hash2[i]) break; if (i == kHashSize) return streamIndex; if (hash[i] < hash2[i]) right = mid; else left = mid + 1; } return -1; } static int CompareItems(const int *a1, const int *a2, void *param) { const CObjectVector &items = ((CDatabase *)param)->Items; const CItem &i1 = items[*a1]; const CItem &i2 = items[*a2]; if (i1.IsDir() != i2.IsDir()) return i1.IsDir() ? 1 : -1; int res = MyCompare(i1.StreamIndex, i2.StreamIndex); if (res != 0) return res; return MyCompare(i1.Order, i2.Order); } HRESULT CDatabase::Sort(bool skipRootDir) { Streams.Sort(CompareStreamsByPos, NULL); { CIntVector sortedByHash; { for (int i = 0; i < Streams.Size(); i++) sortedByHash.Add(i); if (IsOldVersion) sortedByHash.Sort(CompareIDs, &Streams); else sortedByHash.Sort(CompareHashRefs, &Streams); } for (int i = 0; i < Items.Size(); i++) { CItem &item = Items[i]; item.StreamIndex = -1; if (item.HasStream()) if (IsOldVersion) item.StreamIndex = FindId(Streams, sortedByHash, item.Id); else item.StreamIndex = FindHash(Streams, sortedByHash, item.Hash); } } { CRecordVector used; int i; for (i = 0; i < Streams.Size(); i++) { const CStreamInfo &s = Streams[i]; used.Add(s.Resource.IsMetadata() && s.PartNumber == 1); // used.Add(false); } for (i = 0; i < Items.Size(); i++) { CItem &item = Items[i]; if (item.StreamIndex >= 0) used[item.StreamIndex] = true; } for (i = 0; i < Streams.Size(); i++) if (!used[i]) { CItem item; item.StreamIndex = i; item.HasMetadata = false; Items.Add(item); } } SortedItems.Reserve(Items.Size()); for (int i = (skipRootDir ? 1 : 0); i < Items.Size(); i++) SortedItems.Add(i); SortedItems.Sort(CompareItems, this); return S_OK; } }}