// WimHandlerOut.cpp #include "StdAfx.h" #include "../../../../C/CpuArch.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Windows/PropVariant.h" #include "Windows/Time.h" #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamUtils.h" #include "../../Crypto/RandGen.h" #include "../../Crypto/Sha1.h" #include "WimHandler.h" using namespace NWindows; namespace NArchive { namespace NWim { struct CSha1Hash { Byte Hash[kHashSize]; }; struct CHashList { CRecordVector Digests; CIntVector Sorted; int AddUnique(const CSha1Hash &h); }; int CHashList::AddUnique(const CSha1Hash &h) { int left = 0, right = Sorted.Size(); while (left != right) { int mid = (left + right) / 2; int index = Sorted[mid]; UInt32 i; const Byte *hash2 = Digests[index].Hash; for (i = 0; i < kHashSize; i++) if (h.Hash[i] != hash2[i]) break; if (i == kHashSize) return index; if (h.Hash[i] < hash2[i]) right = mid; else left = mid + 1; } Sorted.Insert(left, Digests.Add(h)); return -1; } struct CUpdateItem { UString Name; UInt64 Size; FILETIME CTime; FILETIME ATime; FILETIME MTime; UInt32 Attrib; bool IsDir; int HashIndex; CUpdateItem(): HashIndex(-1) {} }; struct CDir { int Index; UString Name; CObjectVector Dirs; CIntVector Files; CDir(): Index(-1) {} bool IsLeaf() const { return Index >= 0; } UInt64 GetNumDirs() const; UInt64 GetNumFiles() const; CDir* AddDir(CObjectVector &items, const UString &name, int index); }; UInt64 CDir::GetNumDirs() const { UInt64 num = Dirs.Size(); for (int i = 0; i < Dirs.Size(); i++) num += Dirs[i].GetNumDirs(); return num; } UInt64 CDir::GetNumFiles() const { UInt64 num = Files.Size(); for (int i = 0; i < Dirs.Size(); i++) num += Dirs[i].GetNumFiles(); return num; } CDir* CDir::AddDir(CObjectVector &items, const UString &name, int index) { int left = 0, right = Dirs.Size(); while (left != right) { int mid = (left + right) / 2; CDir &d = Dirs[mid]; int compare = name.CompareNoCase(d.IsLeaf() ? items[Dirs[mid].Index].Name : d.Name); if (compare == 0) { if (index >= 0) d.Index = index; return &d; } if (compare < 0) right = mid; else left = mid + 1; } Dirs.Insert(left, CDir()); CDir &d = Dirs[left]; d.Index = index; if (index < 0) d.Name = name; return &d; } STDMETHODIMP COutHandler::GetFileTimeType(UInt32 *type) { *type = NFileTimeType::kWindows; return S_OK; } static HRESULT GetTime(IArchiveUpdateCallback *callback, int index, PROPID propID, FILETIME &ft) { ft.dwLowDateTime = ft.dwHighDateTime = 0; NCOM::CPropVariant prop; RINOK(callback->GetProperty(index, propID, &prop)); if (prop.vt == VT_FILETIME) ft = prop.filetime; else if (prop.vt != VT_EMPTY) return E_INVALIDARG; return S_OK; } #define Set16(p, d) SetUi16(p, d) #define Set32(p, d) SetUi32(p, d) #define Set64(p, d) SetUi64(p, d) void CResource::WriteTo(Byte *p) const { Set64(p, PackSize); p[7] = Flags; Set64(p + 8, Offset); Set64(p + 16, UnpackSize); } void CHeader::WriteTo(Byte *p) const { memcpy(p, kSignature, kSignatureSize); Set32(p + 8, kHeaderSizeMax); Set32(p + 0xC, Version); Set32(p + 0x10, Flags); Set32(p + 0x14, ChunkSize); memcpy(p + 0x18, Guid, 16); Set16(p + 0x28, PartNumber); Set16(p + 0x2A, NumParts); Set32(p + 0x2C, NumImages); OffsetResource.WriteTo(p + 0x30); XmlResource.WriteTo(p + 0x48); MetadataResource.WriteTo(p + 0x60); IntegrityResource.WriteTo(p + 0x7C); Set32(p + 0x78, BootIndex); memset(p + 0x94, 0, 60); } void CStreamInfo::WriteTo(Byte *p) const { Resource.WriteTo(p); Set16(p + 0x18, PartNumber); Set32(p + 0x1A, RefCount); memcpy(p + 0x1E, Hash, kHashSize); } class CInStreamWithSha1: public ISequentialInStream, public CMyUnknownImp { CMyComPtr _stream; UInt64 _size; NCrypto::NSha1::CContext _sha; public: MY_UNKNOWN_IMP1(IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialInStream *stream) { _stream = stream; } void Init() { _size = 0; _sha.Init(); } void ReleaseStream() { _stream.Release(); } UInt64 GetSize() const { return _size; } void Final(Byte *digest) { _sha.Final(digest); } }; STDMETHODIMP CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize) { UInt32 realProcessedSize; HRESULT result = _stream->Read(data, size, &realProcessedSize); _size += realProcessedSize; _sha.Update((const Byte *)data, realProcessedSize); if (processedSize != NULL) *processedSize = realProcessedSize; return result; } static void SetFileTimeToMem(Byte *p, const FILETIME &ft) { Set32(p, ft.dwLowDateTime); Set32(p + 4, ft.dwHighDateTime); } static size_t WriteItem(const CUpdateItem &item, Byte *p, const Byte *hash) { int fileNameLen = item.Name.Length() * 2; int fileNameLen2 = (fileNameLen == 0 ? fileNameLen : fileNameLen + 2); size_t totalLen = ((kDirRecordSize + fileNameLen2 + 6) & ~7); if (p) { memset(p, 0, totalLen); Set64(p, totalLen); Set64(p + 8, item.Attrib); Set32(p + 0xC, (UInt32)(Int32)-1); // item.SecurityId // Set64(p + 0x10, 0); // subdirOffset SetFileTimeToMem(p + 0x28, item.CTime); SetFileTimeToMem(p + 0x30, item.ATime); SetFileTimeToMem(p + 0x38, item.MTime); if (hash) memcpy(p + 0x40, hash, kHashSize); /* else memset(p + 0x40, 0, kHashSize); */ // Set16(p + 98, 0); // shortNameLen Set16(p + 100, (UInt16)fileNameLen); for (int i = 0; i * 2 < fileNameLen; i++) Set16(p + kDirRecordSize + i * 2, item.Name[i]); } return totalLen; } static void WriteTree(const CDir &tree, CRecordVector &digests, CUpdateItem &defaultDirItem, CObjectVector &updateItems, Byte *dest, size_t &pos) { int i; for (i = 0; i < tree.Files.Size(); i++) { const CUpdateItem &ui = updateItems[tree.Files[i]]; pos += WriteItem(ui, dest ? dest + pos : NULL, ui.HashIndex >= 0 ? digests[ui.HashIndex].Hash : NULL); } size_t posStart = pos; for (i = 0; i < tree.Dirs.Size(); i++) { const CDir &subfolder = tree.Dirs[i]; CUpdateItem *item = &defaultDirItem; if (subfolder.IsLeaf()) item = &updateItems[subfolder.Index]; else defaultDirItem.Name = subfolder.Name; pos += WriteItem(*item, NULL, NULL); } if (dest) Set64(dest + pos, 0); pos += 8; for (i = 0; i < tree.Dirs.Size(); i++) { const CDir &subfolder = tree.Dirs[i]; if (dest) { CUpdateItem *item = &defaultDirItem; if (subfolder.IsLeaf()) item = &updateItems[subfolder.Index]; else defaultDirItem.Name = subfolder.Name; size_t len = WriteItem(*item, dest + posStart, NULL); Set64(dest + posStart + 0x10, pos); posStart += len; } WriteTree(subfolder, digests, defaultDirItem, updateItems, dest, pos); } } static void AddTag(AString &s, const char *name, const AString &value) { s += "<"; s += name; s += ">"; s += value; s += ""; } static void AddTagUInt64(AString &s, const char *name, UInt64 value) { char temp[32]; ConvertUInt64ToString(value, temp); AddTag(s, name, temp); } static AString TimeToXml(FILETIME &ft) { AString res; char temp[16] = { '0', 'x' }; ConvertUInt32ToHexWithZeros(ft.dwHighDateTime, temp + 2); AddTag(res, "HIGHPART", temp); ConvertUInt32ToHexWithZeros(ft.dwLowDateTime, temp + 2); AddTag(res, "LOWPART", temp); return res; } void CHeader::SetDefaultFields(bool useLZX) { Version = kWimVersion; Flags = NHeaderFlags::kRpFix; ChunkSize = 0; if (useLZX) { Flags |= NHeaderFlags::kCompression | NHeaderFlags::kLZX; ChunkSize = kChunkSize; } g_RandomGenerator.Generate(Guid, 16); PartNumber = 1; NumParts = 1; NumImages = 1; BootIndex = 0; OffsetResource.Clear(); XmlResource.Clear(); MetadataResource.Clear(); IntegrityResource.Clear(); } static HRESULT UpdateArchive(ISequentialOutStream *seqOutStream, CDir &rootFolder, CObjectVector &updateItems, IArchiveUpdateCallback *callback) { CMyComPtr outStream; RINOK(seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStream)); if (!outStream) return E_NOTIMPL; UInt64 complexity = 0; int i; for (i = 0; i < updateItems.Size(); i++) complexity += updateItems[i].Size; RINOK(callback->SetTotal(complexity)); NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(callback, true); complexity = 0; bool useCompression = false; CHeader header; header.SetDefaultFields(useCompression); Byte buf[kHeaderSizeMax]; header.WriteTo(buf); RINOK(WriteStream(outStream, buf, kHeaderSizeMax)); CHashList hashes; CObjectVector streams; UInt64 curPos = kHeaderSizeMax; UInt64 unpackTotalSize = 0; for (i = 0; i < updateItems.Size(); i++) { lps->InSize = lps->OutSize = complexity; RINOK(lps->SetCur()); CUpdateItem &ui = updateItems[i]; if (ui.IsDir || ui.Size == 0) continue; CInStreamWithSha1 *inShaStreamSpec = new CInStreamWithSha1; CMyComPtr inShaStream = inShaStreamSpec; { CMyComPtr fileInStream; HRESULT res = callback->GetStream(i, &fileInStream); if (res != S_FALSE) { RINOK(res); inShaStreamSpec->SetStream(fileInStream); fileInStream.Release(); inShaStreamSpec->Init(); UInt64 offsetBlockSize = 0; if (useCompression) { for (UInt64 t = kChunkSize; t < ui.Size; t += kChunkSize) { Byte buf[8]; SetUi32(buf, (UInt32)t); RINOK(WriteStream(outStream, buf, 4)); offsetBlockSize += 4; } } RINOK(copyCoder->Code(inShaStream, outStream, NULL, NULL, progress)); ui.Size = copyCoderSpec->TotalSize; CSha1Hash hash; unpackTotalSize += ui.Size; UInt64 packSize = offsetBlockSize + ui.Size; inShaStreamSpec->Final(hash.Hash); int index = hashes.AddUnique(hash); if (index >= 0) { ui.HashIndex = index; streams[index].RefCount++; outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos); outStream->SetSize(curPos); } else { ui.HashIndex = hashes.Digests.Size() - 1; CStreamInfo s; s.Resource.PackSize = packSize; s.Resource.Offset = curPos; s.Resource.UnpackSize = ui.Size; s.Resource.Flags = 0; if (useCompression) s.Resource.Flags = NResourceFlags::Compressed; s.PartNumber = 1; s.RefCount = 1; memcpy(s.Hash, hash.Hash, kHashSize); streams.Add(s); curPos += packSize; } } fileInStream.Release(); complexity += ui.Size; RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); } } CUpdateItem ri; FILETIME ft; NTime::GetCurUtcFileTime(ft); ri.MTime = ri.ATime = ri.CTime = ft; ri.Attrib = FILE_ATTRIBUTE_DIRECTORY; const UInt32 kSecuritySize = 8; size_t pos = kSecuritySize; WriteTree(rootFolder, hashes.Digests, ri, updateItems, NULL, pos); CByteBuffer meta; meta.SetCapacity(pos); // we can write 0 here only if there is no security data, imageX does it, // but some programs expect size = 8 Set32((Byte *)meta, 8); // size of security data Set32((Byte *)meta + 4, 0); // num security entries pos = kSecuritySize; WriteTree(rootFolder, hashes.Digests, ri, updateItems, (Byte *)meta, pos); { NCrypto::NSha1::CContext sha; sha.Init(); sha.Update((const Byte *)meta, pos); CSha1Hash digest; sha.Final(digest.Hash); CStreamInfo s; s.Resource.PackSize = pos; s.Resource.Offset = curPos; s.Resource.UnpackSize = pos; s.Resource.Flags = NResourceFlags::kMetadata; s.PartNumber = 1; s.RefCount = 1; memcpy(s.Hash, digest.Hash, kHashSize); streams.Add(s); RINOK(WriteStream(outStream, (const Byte *)meta, pos)); meta.Free(); curPos += pos; } header.OffsetResource.UnpackSize = header.OffsetResource.PackSize = (UInt64)streams.Size() * kStreamInfoSize; header.OffsetResource.Offset = curPos; header.OffsetResource.Flags = NResourceFlags::kMetadata; for (i = 0; i < streams.Size(); i++) { Byte buf[kStreamInfoSize]; streams[i].WriteTo(buf); RINOK(WriteStream(outStream, buf, kStreamInfoSize)); curPos += kStreamInfoSize; } AString xml = ""; AddTagUInt64(xml, "TOTALBYTES", curPos); xml += "1"; AddTagUInt64(xml, "DIRCOUNT", rootFolder.GetNumDirs()); AddTagUInt64(xml, "FILECOUNT", rootFolder.GetNumFiles()); AddTagUInt64(xml, "TOTALBYTES", unpackTotalSize); NTime::GetCurUtcFileTime(ft); AddTag(xml, "CREATIONTIME", TimeToXml(ft)); AddTag(xml, "LASTMODIFICATIONTIME", TimeToXml(ft)); xml += ""; size_t xmlSize = (xml.Length() + 1) * 2; meta.SetCapacity(xmlSize); Set16((Byte *)meta, 0xFEFF); for (i = 0; i < xml.Length(); i++) Set16((Byte *)meta + 2 + i * 2, xml[i]); RINOK(WriteStream(outStream, (const Byte *)meta, xmlSize)); meta.Free(); header.XmlResource.UnpackSize = header.XmlResource.PackSize = xmlSize; header.XmlResource.Offset = curPos; header.XmlResource.Flags = NResourceFlags::kMetadata; outStream->Seek(0, STREAM_SEEK_SET, NULL); header.WriteTo(buf); return WriteStream(outStream, buf, kHeaderSizeMax); } STDMETHODIMP COutHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *callback) { COM_TRY_BEGIN CObjectVector updateItems; CDir tree; tree.Dirs.Add(CDir()); CDir &rootFolder = tree.Dirs.Back(); for (UInt32 i = 0; i < numItems; i++) { CUpdateItem ui; Int32 newData, newProps; UInt32 indexInArchive; if (!callback) return E_FAIL; RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)); { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidIsDir, &prop)); if (prop.vt == VT_EMPTY) ui.IsDir = false; else if (prop.vt != VT_BOOL) return E_INVALIDARG; else ui.IsDir = (prop.boolVal != VARIANT_FALSE); } { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidAttrib, &prop)); if (prop.vt == VT_EMPTY) ui.Attrib = (ui.IsDir ? FILE_ATTRIBUTE_DIRECTORY : 0); else if (prop.vt != VT_UI4) return E_INVALIDARG; else ui.Attrib = prop.ulVal; } RINOK(GetTime(callback, i, kpidCTime, ui.CTime)); RINOK(GetTime(callback, i, kpidATime, ui.ATime)); RINOK(GetTime(callback, i, kpidMTime, ui.MTime)); { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidSize, &prop)); if (prop.vt != VT_UI8) return E_INVALIDARG; ui.Size = prop.uhVal.QuadPart; } UString path; NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidPath, &prop)); if (prop.vt == VT_BSTR) path = prop.bstrVal; else if (prop.vt != VT_EMPTY) return E_INVALIDARG; CDir *curItem = &rootFolder; int len = path.Length(); UString fileName; for (int j = 0; j < len; j++) { wchar_t c = path[j]; if (c == WCHAR_PATH_SEPARATOR || c == L'/') { curItem = curItem->AddDir(updateItems, fileName, -1); fileName.Empty(); } else fileName += c; } ui.Name = fileName; updateItems.Add(ui); if (ui.IsDir) curItem->AddDir(updateItems, fileName, (int)i); else curItem->Files.Add(i); } return UpdateArchive(outStream, tree, updateItems, callback); COM_TRY_END } }}