// 7zOut.cpp #include "StdAfx.h" #include "../../../../C/7zCrc.h" #include "../../../Common/AutoPtr.h" #include "../../Common/StreamObjects.h" #include "7zOut.h" static HRESULT WriteBytes(ISequentialOutStream *stream, const void *data, size_t size) { while (size > 0) { UInt32 curSize = (UInt32)MyMin(size, (size_t)0xFFFFFFFF); UInt32 processedSize; RINOK(stream->Write(data, curSize, &processedSize)); if (processedSize == 0) return E_FAIL; data = (const void *)((const Byte *)data + processedSize); size -= processedSize; } return S_OK; } namespace NArchive { namespace N7z { HRESULT COutArchive::WriteDirect(const void *data, UInt32 size) { return ::WriteBytes(SeqStream, data, size); } HRESULT COutArchive::WriteSignature() { Byte buf[8]; memcpy(buf, kSignature, kSignatureSize); buf[kSignatureSize] = kMajorVersion; buf[kSignatureSize + 1] = 3; return WriteDirect(buf, 8); } #ifdef _7Z_VOL HRESULT COutArchive::WriteFinishSignature() { RINOK(WriteDirect(kFinishSignature, kSignatureSize)); CArchiveVersion av; av.Major = kMajorVersion; av.Minor = 2; RINOK(WriteDirectByte(av.Major)); return WriteDirectByte(av.Minor); } #endif static void SetUInt32(Byte *p, UInt32 d) { for (int i = 0; i < 4; i++, d >>= 8) p[i] = (Byte)d; } static void SetUInt64(Byte *p, UInt64 d) { for (int i = 0; i < 8; i++, d >>= 8) p[i] = (Byte)d; } HRESULT COutArchive::WriteStartHeader(const CStartHeader &h) { Byte buf[24]; SetUInt64(buf + 4, h.NextHeaderOffset); SetUInt64(buf + 12, h.NextHeaderSize); SetUInt32(buf + 20, h.NextHeaderCRC); SetUInt32(buf, CrcCalc(buf + 4, 20)); return WriteDirect(buf, 24); } #ifdef _7Z_VOL HRESULT COutArchive::WriteFinishHeader(const CFinishHeader &h) { CCRC crc; crc.UpdateUInt64(h.NextHeaderOffset); crc.UpdateUInt64(h.NextHeaderSize); crc.UpdateUInt32(h.NextHeaderCRC); crc.UpdateUInt64(h.ArchiveStartOffset); crc.UpdateUInt64(h.AdditionalStartBlockSize); RINOK(WriteDirectUInt32(crc.GetDigest())); RINOK(WriteDirectUInt64(h.NextHeaderOffset)); RINOK(WriteDirectUInt64(h.NextHeaderSize)); RINOK(WriteDirectUInt32(h.NextHeaderCRC)); RINOK(WriteDirectUInt64(h.ArchiveStartOffset)); return WriteDirectUInt64(h.AdditionalStartBlockSize); } #endif HRESULT COutArchive::Create(ISequentialOutStream *stream, bool endMarker) { Close(); #ifdef _7Z_VOL // endMarker = false; _endMarker = endMarker; #endif SeqStream = stream; if (!endMarker) { SeqStream.QueryInterface(IID_IOutStream, &Stream); if (!Stream) { return E_NOTIMPL; // endMarker = true; } } #ifdef _7Z_VOL if (endMarker) { /* CStartHeader sh; sh.NextHeaderOffset = (UInt32)(Int32)-1; sh.NextHeaderSize = (UInt32)(Int32)-1; sh.NextHeaderCRC = 0; WriteStartHeader(sh); */ } else #endif { if (!Stream) return E_FAIL; RINOK(WriteSignature()); RINOK(Stream->Seek(0, STREAM_SEEK_CUR, &_prefixHeaderPos)); } return S_OK; } void COutArchive::Close() { SeqStream.Release(); Stream.Release(); } HRESULT COutArchive::SkipPrefixArchiveHeader() { #ifdef _7Z_VOL if (_endMarker) return S_OK; #endif return Stream->Seek(24, STREAM_SEEK_CUR, NULL); } UInt64 COutArchive::GetPos() const { if (_countMode) return _countSize; if (_writeToStream) return _outByte.GetProcessedSize(); return _outByte2.GetPos(); } void COutArchive::WriteBytes(const void *data, size_t size) { if (_countMode) _countSize += size; else if (_writeToStream) { _outByte.WriteBytes(data, size); _crc = CrcUpdate(_crc, data, size); } else _outByte2.WriteBytes(data, size); } void COutArchive::WriteByte(Byte b) { if (_countMode) _countSize++; else if (_writeToStream) { _outByte.WriteByte(b); _crc = CRC_UPDATE_BYTE(_crc, b); } else _outByte2.WriteByte(b); } void COutArchive::WriteUInt32(UInt32 value) { for (int i = 0; i < 4; i++) { WriteByte((Byte)value); value >>= 8; } } void COutArchive::WriteUInt64(UInt64 value) { for (int i = 0; i < 8; i++) { WriteByte((Byte)value); value >>= 8; } } void COutArchive::WriteNumber(UInt64 value) { Byte firstByte = 0; Byte mask = 0x80; int i; for (i = 0; i < 8; i++) { if (value < ((UInt64(1) << ( 7 * (i + 1))))) { firstByte |= Byte(value >> (8 * i)); break; } firstByte |= mask; mask >>= 1; } WriteByte(firstByte); for (;i > 0; i--) { WriteByte((Byte)value); value >>= 8; } } static UInt32 GetBigNumberSize(UInt64 value) { int i; for (i = 1; i < 9; i++) if (value < (((UInt64)1 << (i * 7)))) break; return i; } #ifdef _7Z_VOL UInt32 COutArchive::GetVolHeadersSize(UInt64 dataSize, int nameLength, bool props) { UInt32 result = GetBigNumberSize(dataSize) * 2 + 41; if (nameLength != 0) { nameLength = (nameLength + 1) * 2; result += nameLength + GetBigNumberSize(nameLength) + 2; } if (props) { result += 20; } if (result >= 128) result++; result += kSignatureSize + 2 + kFinishHeaderSize; return result; } UInt64 COutArchive::GetVolPureSize(UInt64 volSize, int nameLength, bool props) { UInt32 headersSizeBase = COutArchive::GetVolHeadersSize(1, nameLength, props); int testSize; if (volSize > headersSizeBase) testSize = volSize - headersSizeBase; else testSize = 1; UInt32 headersSize = COutArchive::GetVolHeadersSize(testSize, nameLength, props); UInt64 pureSize = 1; if (volSize > headersSize) pureSize = volSize - headersSize; return pureSize; } #endif void COutArchive::WriteFolder(const CFolder &folder) { WriteNumber(folder.Coders.Size()); int i; for (i = 0; i < folder.Coders.Size(); i++) { const CCoderInfo &coder = folder.Coders[i]; { size_t propsSize = coder.Props.GetCapacity(); UInt64 id = coder.MethodID; int idSize; for (idSize = 1; idSize < sizeof(id); idSize++) if ((id >> (8 * idSize)) == 0) break; BYTE longID[15]; for (int t = idSize - 1; t >= 0 ; t--, id >>= 8) longID[t] = (Byte)(id & 0xFF); Byte b; b = (Byte)(idSize & 0xF); bool isComplex = !coder.IsSimpleCoder(); b |= (isComplex ? 0x10 : 0); b |= ((propsSize != 0) ? 0x20 : 0 ); WriteByte(b); WriteBytes(longID, idSize); if (isComplex) { WriteNumber(coder.NumInStreams); WriteNumber(coder.NumOutStreams); } if (propsSize == 0) continue; WriteNumber(propsSize); WriteBytes(coder.Props, propsSize); } } for (i = 0; i < folder.BindPairs.Size(); i++) { const CBindPair &bindPair = folder.BindPairs[i]; WriteNumber(bindPair.InIndex); WriteNumber(bindPair.OutIndex); } if (folder.PackStreams.Size() > 1) for (i = 0; i < folder.PackStreams.Size(); i++) { WriteNumber(folder.PackStreams[i]); } } void COutArchive::WriteBoolVector(const CBoolVector &boolVector) { Byte b = 0; Byte mask = 0x80; for (int i = 0; i < boolVector.Size(); i++) { if (boolVector[i]) b |= mask; mask >>= 1; if (mask == 0) { WriteByte(b); mask = 0x80; b = 0; } } if (mask != 0x80) WriteByte(b); } void COutArchive::WriteHashDigests( const CRecordVector &digestsDefined, const CRecordVector &digests) { int numDefined = 0; int i; for (i = 0; i < digestsDefined.Size(); i++) if (digestsDefined[i]) numDefined++; if (numDefined == 0) return; WriteByte(NID::kCRC); if (numDefined == digestsDefined.Size()) WriteByte(1); else { WriteByte(0); WriteBoolVector(digestsDefined); } for (i = 0; i < digests.Size(); i++) if (digestsDefined[i]) WriteUInt32(digests[i]); } void COutArchive::WritePackInfo( UInt64 dataOffset, const CRecordVector &packSizes, const CRecordVector &packCRCsDefined, const CRecordVector &packCRCs) { if (packSizes.IsEmpty()) return; WriteByte(NID::kPackInfo); WriteNumber(dataOffset); WriteNumber(packSizes.Size()); WriteByte(NID::kSize); for (int i = 0; i < packSizes.Size(); i++) WriteNumber(packSizes[i]); WriteHashDigests(packCRCsDefined, packCRCs); WriteByte(NID::kEnd); } void COutArchive::WriteUnpackInfo(const CObjectVector &folders) { if (folders.IsEmpty()) return; WriteByte(NID::kUnpackInfo); WriteByte(NID::kFolder); WriteNumber(folders.Size()); { WriteByte(0); for (int i = 0; i < folders.Size(); i++) WriteFolder(folders[i]); } WriteByte(NID::kCodersUnpackSize); int i; for (i = 0; i < folders.Size(); i++) { const CFolder &folder = folders[i]; for (int j = 0; j < folder.UnpackSizes.Size(); j++) WriteNumber(folder.UnpackSizes[j]); } CRecordVector unpackCRCsDefined; CRecordVector unpackCRCs; for (i = 0; i < folders.Size(); i++) { const CFolder &folder = folders[i]; unpackCRCsDefined.Add(folder.UnpackCRCDefined); unpackCRCs.Add(folder.UnpackCRC); } WriteHashDigests(unpackCRCsDefined, unpackCRCs); WriteByte(NID::kEnd); } void COutArchive::WriteSubStreamsInfo( const CObjectVector &folders, const CRecordVector &numUnpackStreamsInFolders, const CRecordVector &unpackSizes, const CRecordVector &digestsDefined, const CRecordVector &digests) { WriteByte(NID::kSubStreamsInfo); int i; for (i = 0; i < numUnpackStreamsInFolders.Size(); i++) { if (numUnpackStreamsInFolders[i] != 1) { WriteByte(NID::kNumUnpackStream); for (i = 0; i < numUnpackStreamsInFolders.Size(); i++) WriteNumber(numUnpackStreamsInFolders[i]); break; } } bool needFlag = true; CNum index = 0; for (i = 0; i < numUnpackStreamsInFolders.Size(); i++) for (CNum j = 0; j < numUnpackStreamsInFolders[i]; j++) { if (j + 1 != numUnpackStreamsInFolders[i]) { if (needFlag) WriteByte(NID::kSize); needFlag = false; WriteNumber(unpackSizes[index]); } index++; } CRecordVector digestsDefined2; CRecordVector digests2; int digestIndex = 0; for (i = 0; i < folders.Size(); i++) { int numSubStreams = (int)numUnpackStreamsInFolders[i]; if (numSubStreams == 1 && folders[i].UnpackCRCDefined) digestIndex++; else for (int j = 0; j < numSubStreams; j++, digestIndex++) { digestsDefined2.Add(digestsDefined[digestIndex]); digests2.Add(digests[digestIndex]); } } WriteHashDigests(digestsDefined2, digests2); WriteByte(NID::kEnd); } void COutArchive::SkipAlign(unsigned /* pos */, unsigned /* alignSize */) { return; } /* 7-Zip 4.50 - 4.58 contain BUG, so they do not support .7z archives with Unknown field. void COutArchive::SkipAlign(unsigned pos, unsigned alignSize) { pos += (unsigned)GetPos(); pos &= (alignSize - 1); if (pos == 0) return; unsigned skip = alignSize - pos; if (skip < 2) skip += alignSize; skip -= 2; WriteByte(NID::kDummy); WriteByte((Byte)skip); for (unsigned i = 0; i < skip; i++) WriteByte(0); } */ static inline unsigned Bv_GetSizeInBytes(const CBoolVector &v) { return ((unsigned)v.Size() + 7) / 8; } void COutArchive::WriteAlignedBoolHeader(const CBoolVector &v, int numDefined, Byte type, unsigned itemSize) { const unsigned bvSize = (numDefined == v.Size()) ? 0 : Bv_GetSizeInBytes(v); const UInt64 dataSize = (UInt64)numDefined * itemSize + bvSize + 2; SkipAlign(3 + (unsigned)bvSize + (unsigned)GetBigNumberSize(dataSize), itemSize); WriteByte(type); WriteNumber(dataSize); if (numDefined == v.Size()) WriteByte(1); else { WriteByte(0); WriteBoolVector(v); } WriteByte(0); } void COutArchive::WriteUInt64DefVector(const CUInt64DefVector &v, Byte type) { int numDefined = 0; int i; for (i = 0; i < v.Defined.Size(); i++) if (v.Defined[i]) numDefined++; if (numDefined == 0) return; WriteAlignedBoolHeader(v.Defined, numDefined, type, 8); for (i = 0; i < v.Defined.Size(); i++) if (v.Defined[i]) WriteUInt64(v.Values[i]); } HRESULT COutArchive::EncodeStream( DECL_EXTERNAL_CODECS_LOC_VARS CEncoder &encoder, const CByteBuffer &data, CRecordVector &packSizes, CObjectVector &folders) { CBufInStream *streamSpec = new CBufInStream; CMyComPtr stream = streamSpec; streamSpec->Init(data, data.GetCapacity()); CFolder folderItem; folderItem.UnpackCRCDefined = true; folderItem.UnpackCRC = CrcCalc(data, data.GetCapacity()); UInt64 dataSize64 = data.GetCapacity(); RINOK(encoder.Encode( EXTERNAL_CODECS_LOC_VARS stream, NULL, &dataSize64, folderItem, SeqStream, packSizes, NULL)) folders.Add(folderItem); return S_OK; } void COutArchive::WriteHeader( const CArchiveDatabase &db, const CHeaderOptions &headerOptions, UInt64 &headerOffset) { int i; UInt64 packedSize = 0; for (i = 0; i < db.PackSizes.Size(); i++) packedSize += db.PackSizes[i]; headerOffset = packedSize; WriteByte(NID::kHeader); // Archive Properties if (db.Folders.Size() > 0) { WriteByte(NID::kMainStreamsInfo); WritePackInfo(0, db.PackSizes, db.PackCRCsDefined, db.PackCRCs); WriteUnpackInfo(db.Folders); CRecordVector unpackSizes; CRecordVector digestsDefined; CRecordVector digests; for (i = 0; i < db.Files.Size(); i++) { const CFileItem &file = db.Files[i]; if (!file.HasStream) continue; unpackSizes.Add(file.Size); digestsDefined.Add(file.CrcDefined); digests.Add(file.Crc); } WriteSubStreamsInfo( db.Folders, db.NumUnpackStreamsVector, unpackSizes, digestsDefined, digests); WriteByte(NID::kEnd); } if (db.Files.IsEmpty()) { WriteByte(NID::kEnd); return; } WriteByte(NID::kFilesInfo); WriteNumber(db.Files.Size()); { /* ---------- Empty Streams ---------- */ CBoolVector emptyStreamVector; emptyStreamVector.Reserve(db.Files.Size()); int numEmptyStreams = 0; for (i = 0; i < db.Files.Size(); i++) if (db.Files[i].HasStream) emptyStreamVector.Add(false); else { emptyStreamVector.Add(true); numEmptyStreams++; } if (numEmptyStreams > 0) { WriteByte(NID::kEmptyStream); WriteNumber(Bv_GetSizeInBytes(emptyStreamVector)); WriteBoolVector(emptyStreamVector); CBoolVector emptyFileVector, antiVector; emptyFileVector.Reserve(numEmptyStreams); antiVector.Reserve(numEmptyStreams); CNum numEmptyFiles = 0, numAntiItems = 0; for (i = 0; i < db.Files.Size(); i++) { const CFileItem &file = db.Files[i]; if (!file.HasStream) { emptyFileVector.Add(!file.IsDir); if (!file.IsDir) numEmptyFiles++; bool isAnti = db.IsItemAnti(i); antiVector.Add(isAnti); if (isAnti) numAntiItems++; } } if (numEmptyFiles > 0) { WriteByte(NID::kEmptyFile); WriteNumber(Bv_GetSizeInBytes(emptyFileVector)); WriteBoolVector(emptyFileVector); } if (numAntiItems > 0) { WriteByte(NID::kAnti); WriteNumber(Bv_GetSizeInBytes(antiVector)); WriteBoolVector(antiVector); } } } { /* ---------- Names ---------- */ int numDefined = 0; size_t namesDataSize = 0; for (int i = 0; i < db.Files.Size(); i++) { const UString &name = db.Files[i].Name; if (!name.IsEmpty()) numDefined++; namesDataSize += (name.Length() + 1) * 2; } if (numDefined > 0) { namesDataSize++; SkipAlign(2 + GetBigNumberSize(namesDataSize), 2); WriteByte(NID::kName); WriteNumber(namesDataSize); WriteByte(0); for (int i = 0; i < db.Files.Size(); i++) { const UString &name = db.Files[i].Name; for (int t = 0; t <= name.Length(); t++) { wchar_t c = name[t]; WriteByte((Byte)c); WriteByte((Byte)(c >> 8)); } } } } if (headerOptions.WriteCTime) WriteUInt64DefVector(db.CTime, NID::kCTime); if (headerOptions.WriteATime) WriteUInt64DefVector(db.ATime, NID::kATime); if (headerOptions.WriteMTime) WriteUInt64DefVector(db.MTime, NID::kMTime); WriteUInt64DefVector(db.StartPos, NID::kStartPos); { /* ---------- Write Attrib ---------- */ CBoolVector boolVector; boolVector.Reserve(db.Files.Size()); int numDefined = 0; for (i = 0; i < db.Files.Size(); i++) { bool defined = db.Files[i].AttribDefined; boolVector.Add(defined); if (defined) numDefined++; } if (numDefined > 0) { WriteAlignedBoolHeader(boolVector, numDefined, NID::kWinAttributes, 4); for (i = 0; i < db.Files.Size(); i++) { const CFileItem &file = db.Files[i]; if (file.AttribDefined) WriteUInt32(file.Attrib); } } } WriteByte(NID::kEnd); // for files WriteByte(NID::kEnd); // for headers } HRESULT COutArchive::WriteDatabase( DECL_EXTERNAL_CODECS_LOC_VARS const CArchiveDatabase &db, const CCompressionMethodMode *options, const CHeaderOptions &headerOptions) { if (!db.CheckNumFiles()) return E_FAIL; UInt64 headerOffset; UInt32 headerCRC; UInt64 headerSize; if (db.IsEmpty()) { headerSize = 0; headerOffset = 0; headerCRC = CrcCalc(0, 0); } else { bool encodeHeaders = false; if (options != 0) if (options->IsEmpty()) options = 0; if (options != 0) if (options->PasswordIsDefined || headerOptions.CompressMainHeader) encodeHeaders = true; _outByte.SetStream(SeqStream); _outByte.Init(); _crc = CRC_INIT_VAL; _countMode = encodeHeaders; _writeToStream = true; _countSize = 0; WriteHeader(db, headerOptions, headerOffset); if (encodeHeaders) { CByteBuffer buf; buf.SetCapacity(_countSize); _outByte2.Init((Byte *)buf, _countSize); _countMode = false; _writeToStream = false; WriteHeader(db, headerOptions, headerOffset); if (_countSize != _outByte2.GetPos()) return E_FAIL; CCompressionMethodMode encryptOptions; encryptOptions.PasswordIsDefined = options->PasswordIsDefined; encryptOptions.Password = options->Password; CEncoder encoder(headerOptions.CompressMainHeader ? *options : encryptOptions); CRecordVector packSizes; CObjectVector folders; RINOK(EncodeStream( EXTERNAL_CODECS_LOC_VARS encoder, buf, packSizes, folders)); _writeToStream = true; if (folders.Size() == 0) throw 1; WriteID(NID::kEncodedHeader); WritePackInfo(headerOffset, packSizes, CRecordVector(), CRecordVector()); WriteUnpackInfo(folders); WriteByte(NID::kEnd); for (int i = 0; i < packSizes.Size(); i++) headerOffset += packSizes[i]; } RINOK(_outByte.Flush()); headerCRC = CRC_GET_DIGEST(_crc); headerSize = _outByte.GetProcessedSize(); } #ifdef _7Z_VOL if (_endMarker) { CFinishHeader h; h.NextHeaderSize = headerSize; h.NextHeaderCRC = headerCRC; h.NextHeaderOffset = UInt64(0) - (headerSize + 4 + kFinishHeaderSize); h.ArchiveStartOffset = h.NextHeaderOffset - headerOffset; h.AdditionalStartBlockSize = 0; RINOK(WriteFinishHeader(h)); return WriteFinishSignature(); } else #endif { CStartHeader h; h.NextHeaderSize = headerSize; h.NextHeaderCRC = headerCRC; h.NextHeaderOffset = headerOffset; RINOK(Stream->Seek(_prefixHeaderPos, STREAM_SEEK_SET, NULL)); return WriteStartHeader(h); } } void CArchiveDatabase::GetFile(int index, CFileItem &file, CFileItem2 &file2) const { file = Files[index]; file2.CTimeDefined = CTime.GetItem(index, file2.CTime); file2.ATimeDefined = ATime.GetItem(index, file2.ATime); file2.MTimeDefined = MTime.GetItem(index, file2.MTime); file2.StartPosDefined = StartPos.GetItem(index, file2.StartPos); file2.IsAnti = IsItemAnti(index); } void CArchiveDatabase::AddFile(const CFileItem &file, const CFileItem2 &file2) { int index = Files.Size(); CTime.SetItem(index, file2.CTimeDefined, file2.CTime); ATime.SetItem(index, file2.ATimeDefined, file2.ATime); MTime.SetItem(index, file2.MTimeDefined, file2.MTime); StartPos.SetItem(index, file2.StartPosDefined, file2.StartPos); SetItemAnti(index, file2.IsAnti); Files.Add(file); } }}