// NtfsHandler.cpp #include "StdAfx.h" // #define SHOW_DEBUG_INFO // #define SHOW_DEBUG_INFO2 #if defined(SHOW_DEBUG_INFO) || defined(SHOW_DEBUG_INFO2) #include #endif #include "../../../C/CpuArch.h" #include "Common/Buffer.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Common/MyCom.h" #include "Common/StringConvert.h" #include "Windows/PropVariant.h" #include "Windows/Time.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #include "Common/DummyOutStream.h" #ifdef SHOW_DEBUG_INFO #define PRF(x) x #else #define PRF(x) #endif #ifdef SHOW_DEBUG_INFO2 #define PRF2(x) x #else #define PRF2(x) #endif #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) #define G16(p, dest) dest = Get16(p); #define G32(p, dest) dest = Get32(p); #define G64(p, dest) dest = Get64(p); namespace NArchive { namespace Ntfs { static const UInt32 kNumSysRecs = 16; static const UInt32 kRecIndex_Volume = 3; static const UInt32 kRecIndex_BadClus = 8; struct CHeader { Byte SectorSizeLog; Byte ClusterSizeLog; // Byte MediaType; UInt32 NumHiddenSectors; UInt64 NumClusters; UInt64 MftCluster; UInt64 SerialNumber; UInt16 SectorsPerTrack; UInt16 NumHeads; UInt64 GetPhySize() const { return NumClusters << ClusterSizeLog; } UInt32 ClusterSize() const { return (UInt32)1 << ClusterSizeLog; } bool Parse(const Byte *p); }; static int GetLog(UInt32 num) { for (int i = 0; i < 31; i++) if (((UInt32)1 << i) == num) return i; return -1; } bool CHeader::Parse(const Byte *p) { if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA) return false; int codeOffset = 0; switch (p[0]) { case 0xE9: codeOffset = 3 + (Int16)Get16(p + 1); break; case 0xEB: if (p[2] != 0x90) return false; codeOffset = 2 + (signed char)p[1]; break; default: return false; } Byte sectorsPerClusterLog; if (memcmp(p + 3, "NTFS ", 8) != 0) return false; { int s = GetLog(Get16(p + 11)); if (s < 9 || s > 12) return false; SectorSizeLog = (Byte)s; s = GetLog(p[13]); if (s < 0) return false; sectorsPerClusterLog = (Byte)s; ClusterSizeLog = SectorSizeLog + sectorsPerClusterLog; } for (int i = 14; i < 21; i++) if (p[i] != 0) return false; // MediaType = p[21]; if (Get16(p + 22) != 0) // NumFatSectors return false; G16(p + 24, SectorsPerTrack); G16(p + 26, NumHeads); G32(p + 28, NumHiddenSectors); if (Get32(p + 32) != 0) // NumSectors32 return false; // DriveNumber = p[0x24]; if (p[0x25] != 0) // CurrentHead return false; /* NTFS-HDD: p[0x26] = 0x80 NTFS-FLASH: p[0x26] = 0 */ if (p[0x26] != 0x80 && p[0x26] != 0) // ExtendedBootSig return false; if (p[0x27] != 0) // reserved return false; UInt64 numSectors = Get64(p + 0x28); NumClusters = numSectors >> sectorsPerClusterLog; G64(p + 0x30, MftCluster); // G64(p + 0x38, Mft2Cluster); G64(p + 0x48, SerialNumber); UInt32 numClustersInMftRec; UInt32 numClustersInIndexBlock; G32(p + 0x40, numClustersInMftRec); G32(p + 0x44, numClustersInIndexBlock); return (numClustersInMftRec < 256 && numClustersInIndexBlock < 256); } struct CMftRef { UInt64 Val; UInt64 GetIndex() const { return Val & (((UInt64)1 << 48) - 1); } UInt16 GetNumber() const { return (UInt16)(Val >> 48); } bool IsBaseItself() const { return Val == 0; } }; #define ATNAME(n) ATTR_TYPE_ ## n #define DEF_ATTR_TYPE(v, n) ATNAME(n) = v enum { DEF_ATTR_TYPE(0x00, UNUSED), DEF_ATTR_TYPE(0x10, STANDARD_INFO), DEF_ATTR_TYPE(0x20, ATTRIBUTE_LIST), DEF_ATTR_TYPE(0x30, FILE_NAME), DEF_ATTR_TYPE(0x40, OBJECT_ID), DEF_ATTR_TYPE(0x50, SECURITY_DESCRIPTOR), DEF_ATTR_TYPE(0x60, VOLUME_NAME), DEF_ATTR_TYPE(0x70, VOLUME_INFO), DEF_ATTR_TYPE(0x80, DATA), DEF_ATTR_TYPE(0x90, INDEX_ROOT), DEF_ATTR_TYPE(0xA0, INDEX_ALLOCATION), DEF_ATTR_TYPE(0xB0, BITMAP), DEF_ATTR_TYPE(0xC0, REPARSE_POINT), DEF_ATTR_TYPE(0xD0, EA_INFO), DEF_ATTR_TYPE(0xE0, EA), DEF_ATTR_TYPE(0xF0, PROPERTY_SET), DEF_ATTR_TYPE(0x100, LOGGED_UTILITY_STREAM), DEF_ATTR_TYPE(0x1000, FIRST_USER_DEFINED_ATTRIBUTE) }; static const Byte kFileNameType_Posix = 0; static const Byte kFileNameType_Win32 = 1; static const Byte kFileNameType_Dos = 2; static const Byte kFileNameType_Win32Dos = 3; struct CFileNameAttr { CMftRef ParentDirRef; // UInt64 CTime; // UInt64 MTime; // UInt64 ThisRecMTime; // UInt64 ATime; // UInt64 AllocatedSize; // UInt64 DataSize; // UInt16 PackedEaSize; UString Name; UInt32 Attrib; Byte NameType; bool IsDos() const { return NameType == kFileNameType_Dos; } bool Parse(const Byte *p, unsigned size); }; static void GetString(const Byte *p, unsigned length, UString &res) { wchar_t *s = res.GetBuffer(length); for (unsigned i = 0; i < length; i++) s[i] = Get16(p + i * 2); s[length] = 0; res.ReleaseBuffer(); } bool CFileNameAttr::Parse(const Byte *p, unsigned size) { if (size < 0x42) return false; G64(p + 0x00, ParentDirRef.Val); // G64(p + 0x08, CTime); // G64(p + 0x10, MTime); // G64(p + 0x18, ThisRecMTime); // G64(p + 0x20, ATime); // G64(p + 0x28, AllocatedSize); // G64(p + 0x30, DataSize); G32(p + 0x38, Attrib); // G16(p + 0x3C, PackedEaSize); NameType = p[0x41]; unsigned length = p[0x40]; if (0x42 + length > size) return false; GetString(p + 0x42, length, Name); return true; } struct CSiAttr { UInt64 CTime; UInt64 MTime; // UInt64 ThisRecMTime; UInt64 ATime; UInt32 Attrib; /* UInt32 MaxVersions; UInt32 Version; UInt32 ClassId; UInt32 OwnerId; UInt32 SecurityId; UInt64 QuotaCharged; */ bool Parse(const Byte *p, unsigned size); }; bool CSiAttr::Parse(const Byte *p, unsigned size) { if (size < 0x24) return false; G64(p + 0x00, CTime); G64(p + 0x08, MTime); // G64(p + 0x10, ThisRecMTime); G64(p + 0x18, ATime); G32(p + 0x20, Attrib); return true; } static const UInt64 kEmptyExtent = (UInt64)(Int64)-1; struct CExtent { UInt64 Virt; UInt64 Phy; bool IsEmpty() const { return Phy == kEmptyExtent; } }; struct CVolInfo { Byte MajorVer; Byte MinorVer; // UInt16 Flags; bool Parse(const Byte *p, unsigned size); }; bool CVolInfo::Parse(const Byte *p, unsigned size) { if (size < 12) return false; MajorVer = p[8]; MinorVer = p[9]; // Flags = Get16(p + 10); return true; } struct CAttr { UInt32 Type; // UInt32 Length; UString Name; // UInt16 Flags; // UInt16 Instance; CByteBuffer Data; Byte NonResident; // Non-Resident Byte CompressionUnit; UInt64 LowVcn; UInt64 HighVcn; UInt64 AllocatedSize; UInt64 Size; UInt64 PackSize; UInt64 InitializedSize; // Resident // UInt16 ResidentFlags; bool IsCompressionUnitSupported() const { return CompressionUnit == 0 || CompressionUnit == 4; } UInt32 Parse(const Byte *p, unsigned size); bool ParseFileName(CFileNameAttr &a) const { return a.Parse(Data, (unsigned)Data.GetCapacity()); } bool ParseSi(CSiAttr &a) const { return a.Parse(Data, (unsigned)Data.GetCapacity()); } bool ParseVolInfo(CVolInfo &a) const { return a.Parse(Data, (unsigned)Data.GetCapacity()); } bool ParseExtents(CRecordVector &extents, UInt64 numClustersMax, int compressionUnit) const; UInt64 GetSize() const { return NonResident ? Size : Data.GetCapacity(); } UInt64 GetPackSize() const { if (!NonResident) return Data.GetCapacity(); if (CompressionUnit != 0) return PackSize; return AllocatedSize; } }; #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } static int CompareAttr(void *const *elem1, void *const *elem2, void *) { const CAttr &a1 = *(*((const CAttr **)elem1)); const CAttr &a2 = *(*((const CAttr **)elem2)); RINOZ(MyCompare(a1.Type, a2.Type)); RINOZ(MyCompare(a1.Name, a2.Name)); return MyCompare(a1.LowVcn, a2.LowVcn); } UInt32 CAttr::Parse(const Byte *p, unsigned size) { if (size < 4) return 0; G32(p, Type); if (Type == 0xFFFFFFFF) return 4; if (size < 0x18) return 0; PRF(printf(" T=%2X", Type)); UInt32 length = Get32(p + 0x04); PRF(printf(" L=%3d", length)); if (length > size) return 0; NonResident = p[0x08]; { int nameLength = p[9]; UInt32 nameOffset = Get16(p + 0x0A); if (nameLength != 0) { if (nameOffset + nameLength * 2 > length) return 0; GetString(p + nameOffset, nameLength, Name); PRF(printf(" N=%S", Name)); } } // G16(p + 0x0C, Flags); // G16(p + 0x0E, Instance); // PRF(printf(" F=%4X", Flags)); // PRF(printf(" Inst=%d", Instance)); UInt32 dataSize; UInt32 offs; if (NonResident) { if (length < 0x40) return 0; PRF(printf(" NR")); G64(p + 0x10, LowVcn); G64(p + 0x18, HighVcn); G64(p + 0x28, AllocatedSize); G64(p + 0x30, Size); G64(p + 0x38, InitializedSize); G16(p + 0x20, offs); CompressionUnit = p[0x22]; PackSize = Size; if (CompressionUnit != 0) { if (length < 0x48) return 0; G64(p + 0x40, PackSize); PRF(printf(" PS=%I64x", PackSize)); } // PRF(printf("\n")); PRF(printf(" ASize=%4I64d", AllocatedSize)); PRF(printf(" Size=%I64d", Size)); PRF(printf(" IS=%I64d", InitializedSize)); PRF(printf(" Low=%I64d", LowVcn)); PRF(printf(" High=%I64d", HighVcn)); PRF(printf(" CU=%d", (int)CompressionUnit)); dataSize = length - offs; } else { if (length < 0x18) return 0; PRF(printf(" RES")); dataSize = Get32(p + 0x10); PRF(printf(" dataSize=%3d", dataSize)); offs = Get16(p + 0x14); // G16(p + 0x16, ResidentFlags); // PRF(printf(" ResFlags=%4X", ResidentFlags)); } if (offs > length || dataSize > length || length - dataSize < offs) return 0; Data.SetCapacity(dataSize); memcpy(Data, p + offs, dataSize); #ifdef SHOW_DEBUG_INFO PRF(printf(" : ")); for (unsigned i = 0; i < Data.GetCapacity(); i++) { PRF(printf(" %02X", (int)Data[i])); } #endif return length; } bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, int compressionUnit) const { const Byte *p = Data; unsigned size = (unsigned)Data.GetCapacity(); UInt64 vcn = LowVcn; UInt64 lcn = 0; UInt64 highVcn1 = HighVcn + 1; if (LowVcn != extents.Back().Virt || highVcn1 > (UInt64)1 << 63) return false; extents.DeleteBack(); PRF2(printf("\n# ParseExtents # LowVcn = %4I64X # HighVcn = %4I64X", LowVcn, HighVcn)); while (size > 0) { Byte b = *p++; size--; if (b == 0) break; UInt32 num = b & 0xF; if (num == 0 || num > 8 || num > size) return false; int i; UInt64 vSize = p[num - 1]; for (i = (int)num - 2; i >= 0; i--) vSize = (vSize << 8) | p[i]; if (vSize == 0) return false; p += num; size -= num; if ((highVcn1 - vcn) < vSize) return false; num = (b >> 4) & 0xF; if (num > 8 || num > size) return false; CExtent e; e.Virt = vcn; if (num == 0) { if (compressionUnit == 0) return false; e.Phy = kEmptyExtent; } else { Int64 v = (signed char)p[num - 1]; for (i = (int)num - 2; i >= 0; i--) v = (v << 8) | p[i]; p += num; size -= num; lcn += v; if (lcn > numClustersMax) return false; e.Phy = lcn; } extents.Add(e); vcn += vSize; } CExtent e; e.Phy = kEmptyExtent; e.Virt = vcn; extents.Add(e); return (highVcn1 == vcn); } static const UInt64 kEmptyTag = (UInt64)(Int64)-1; static const int kNumCacheChunksLog = 1; static const UInt32 kNumCacheChunks = (1 << kNumCacheChunksLog); class CInStream: public IInStream, public CMyUnknownImp { UInt64 _virtPos; UInt64 _physPos; UInt64 _curRem; bool _sparseMode; size_t _compressedPos; UInt64 _tags[kNumCacheChunks]; int _chunkSizeLog; CByteBuffer _inBuf; CByteBuffer _outBuf; public: CMyComPtr Stream; UInt64 Size; UInt64 InitializedSize; int BlockSizeLog; int CompressionUnit; bool InUse; CRecordVector Extents; HRESULT SeekToPhys() { return Stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } UInt32 GetCuSize() const { return (UInt32)1 << (BlockSizeLog + CompressionUnit); } HRESULT InitAndSeek(int compressionUnit) { CompressionUnit = compressionUnit; if (compressionUnit != 0) { UInt32 cuSize = GetCuSize(); _inBuf.SetCapacity(cuSize); _chunkSizeLog = BlockSizeLog + CompressionUnit; _outBuf.SetCapacity(kNumCacheChunks << _chunkSizeLog); } for (int i = 0; i < kNumCacheChunks; i++) _tags[i] = kEmptyTag; _sparseMode = false; _curRem = 0; _virtPos = 0; _physPos = 0; const CExtent &e = Extents[0]; if (!e.IsEmpty()) _physPos = e.Phy << BlockSizeLog; return SeekToPhys(); } MY_UNKNOWN_IMP1(IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; static size_t Lznt1Dec(Byte *dest, size_t outBufLim, size_t destLen, const Byte *src, size_t srcLen) { size_t destSize = 0; while (destSize < destLen) { if (srcLen < 2 || (destSize & 0xFFF) != 0) break; UInt32 v = Get16(src); if (v == 0) break; src += 2; srcLen -= 2; UInt32 comprSize = (v & 0xFFF) + 1; if (comprSize > srcLen) break; srcLen -= comprSize; if ((v & 0x8000) == 0) { if (comprSize != (1 << 12)) break; memcpy(dest + destSize, src, comprSize); src += comprSize; destSize += comprSize; } else { if (destSize + (1 << 12) > outBufLim || (src[0] & 1) != 0) return 0; int numDistBits = 4; UInt32 sbOffset = 0; UInt32 pos = 0; do { comprSize--; for (UInt32 mask = src[pos++] | 0x100; mask > 1 && comprSize > 0; mask >>= 1) { if ((mask & 1) == 0) { if (sbOffset >= (1 << 12)) return 0; dest[destSize++] = src[pos++]; sbOffset++; comprSize--; } else { if (comprSize < 2) return 0; UInt32 v = Get16(src + pos); pos += 2; comprSize -= 2; while (((sbOffset - 1) >> numDistBits) != 0) numDistBits++; UInt32 len = (v & (0xFFFF >> numDistBits)) + 3; if (sbOffset + len > (1 << 12)) return 0; UInt32 dist = (v >> (16 - numDistBits)); if (dist >= sbOffset) return 0; Int32 offs = -1 - dist; Byte *p = dest + destSize; for (UInt32 t = 0; t < len; t++) p[t] = p[t + offs]; destSize += len; sbOffset += len; } } } while (comprSize > 0); src += pos; } } return destSize; } STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize != NULL) *processedSize = 0; if (_virtPos >= Size) return (Size == _virtPos) ? S_OK: E_FAIL; if (size == 0) return S_OK; UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; if (_virtPos >= InitializedSize) { memset((Byte *)data, 0, size); _virtPos += size; *processedSize = size; return S_OK; } rem = InitializedSize - _virtPos; if (size > rem) size = (UInt32)rem; while (_curRem == 0) { UInt64 cacheTag = _virtPos >> _chunkSizeLog; UInt32 cacheIndex = (UInt32)cacheTag & (kNumCacheChunks - 1); if (_tags[cacheIndex] == cacheTag) { UInt32 chunkSize = (UInt32)1 << _chunkSizeLog; UInt32 offset = (UInt32)_virtPos & (chunkSize - 1); UInt32 cur = MyMin(chunkSize - offset, size); memcpy(data, _outBuf + (cacheIndex << _chunkSizeLog) + offset, cur); *processedSize = cur; _virtPos += cur; return S_OK; } PRF2(printf("\nVirtPos = %6d", _virtPos)); UInt32 comprUnitSize = (UInt32)1 << CompressionUnit; UInt64 virtBlock = _virtPos >> BlockSizeLog; UInt64 virtBlock2 = virtBlock & ~((UInt64)comprUnitSize - 1); int left = 0, right = Extents.Size(); for (;;) { int mid = (left + right) / 2; if (mid == left) break; if (virtBlock2 < Extents[mid].Virt) right = mid; else left = mid; } bool isCompressed = false; UInt64 virtBlock2End = virtBlock2 + comprUnitSize; if (CompressionUnit != 0) for (int i = left; i < Extents.Size(); i++) { const CExtent &e = Extents[i]; if (e.Virt >= virtBlock2End) break; if (e.IsEmpty()) { isCompressed = true; break; } } int i; for (i = left; Extents[i + 1].Virt <= virtBlock; i++); _sparseMode = false; if (!isCompressed) { const CExtent &e = Extents[i]; UInt64 newPos = (e.Phy << BlockSizeLog) + _virtPos - (e.Virt << BlockSizeLog); if (newPos != _physPos) { _physPos = newPos; RINOK(SeekToPhys()); } UInt64 next = Extents[i + 1].Virt; if (next > virtBlock2End) next &= ~((UInt64)comprUnitSize - 1); next <<= BlockSizeLog; if (next > Size) next = Size; _curRem = next - _virtPos; break; } bool thereArePhy = false; for (int i2 = left; i2 < Extents.Size(); i2++) { const CExtent &e = Extents[i2]; if (e.Virt >= virtBlock2End) break; if (!e.IsEmpty()) { thereArePhy = true; break; } } if (!thereArePhy) { _curRem = (Extents[i + 1].Virt << BlockSizeLog) - _virtPos; _sparseMode = true; break; } size_t offs = 0; UInt64 curVirt = virtBlock2; for (i = left; i < Extents.Size(); i++) { const CExtent &e = Extents[i]; if (e.IsEmpty()) break; if (e.Virt >= virtBlock2End) return S_FALSE; UInt64 newPos = (e.Phy + (curVirt - e.Virt)) << BlockSizeLog; if (newPos != _physPos) { _physPos = newPos; RINOK(SeekToPhys()); } UInt64 numChunks = Extents[i + 1].Virt - curVirt; if (curVirt + numChunks > virtBlock2End) numChunks = virtBlock2End - curVirt; size_t compressed = (size_t)numChunks << BlockSizeLog; RINOK(ReadStream_FALSE(Stream, _inBuf + offs, compressed)); curVirt += numChunks; _physPos += compressed; offs += compressed; } size_t destLenMax = GetCuSize(); size_t destLen = destLenMax; UInt64 rem = Size - (virtBlock2 << BlockSizeLog); if (destLen > rem) destLen = (size_t)rem; Byte *dest = _outBuf + (cacheIndex << _chunkSizeLog); size_t destSizeRes = Lznt1Dec(dest, destLenMax, destLen, _inBuf, offs); _tags[cacheIndex] = cacheTag; // some files in Vista have destSize > destLen if (destSizeRes < destLen) { memset(dest, 0, destLenMax); if (InUse) return S_FALSE; } } if (size > _curRem) size = (UInt32)_curRem; HRESULT res = S_OK; if (_sparseMode) memset(data, 0, size); else { res = Stream->Read(data, size, &size); _physPos += size; } if (processedSize != NULL) *processedSize = size; _virtPos += size; _curRem -= size; return res; } STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { UInt64 newVirtPos = offset; switch(seekOrigin) { case STREAM_SEEK_SET: break; case STREAM_SEEK_CUR: newVirtPos += _virtPos; break; case STREAM_SEEK_END: newVirtPos += Size; break; default: return STG_E_INVALIDFUNCTION; } if (_virtPos != newVirtPos) _curRem = 0; _virtPos = newVirtPos; if (newPosition) *newPosition = newVirtPos; return S_OK; } class CByteBufStream: public IInStream, public CMyUnknownImp { UInt64 _virtPos; public: CByteBuffer Buf; void Init() { _virtPos = 0; } MY_UNKNOWN_IMP1(IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; STDMETHODIMP CByteBufStream::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize != NULL) *processedSize = 0; if (_virtPos >= Buf.GetCapacity()) return (_virtPos == Buf.GetCapacity()) ? S_OK: E_FAIL; UInt64 rem = Buf.GetCapacity() - _virtPos; if (rem < size) size = (UInt32)rem; memcpy(data, Buf + (size_t)_virtPos, size); if (processedSize != NULL) *processedSize = size; _virtPos += size; return S_OK; } STDMETHODIMP CByteBufStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { switch(seekOrigin) { case STREAM_SEEK_SET: _virtPos = offset; break; case STREAM_SEEK_CUR: _virtPos += offset; break; case STREAM_SEEK_END: _virtPos = Buf.GetCapacity() + offset; break; default: return STG_E_INVALIDFUNCTION; } if (newPosition) *newPosition = _virtPos; return S_OK; } static HRESULT DataParseExtents(int clusterSizeLog, const CObjectVector &attrs, int attrIndex, int attrIndexLim, UInt64 numPhysClusters, CRecordVector &Extents) { CExtent e; e.Virt = 0; e.Phy = kEmptyExtent; Extents.Add(e); const CAttr &attr0 = attrs[attrIndex]; if (attr0.AllocatedSize < attr0.Size || (attrs[attrIndexLim - 1].HighVcn + 1) != (attr0.AllocatedSize >> clusterSizeLog) || (attr0.AllocatedSize & ((1 << clusterSizeLog) - 1)) != 0) return S_FALSE; for (int i = attrIndex; i < attrIndexLim; i++) if (!attrs[i].ParseExtents(Extents, numPhysClusters, attr0.CompressionUnit)) return S_FALSE; UInt64 packSizeCalc = 0; for (int k = 0; k < Extents.Size(); k++) { CExtent &e = Extents[k]; if (!e.IsEmpty()) packSizeCalc += (Extents[k + 1].Virt - e.Virt) << clusterSizeLog; PRF2(printf("\nSize = %4I64X", Extents[k + 1].Virt - e.Virt)); PRF2(printf(" Pos = %4I64X", e.Phy)); } if (attr0.CompressionUnit != 0) { if (packSizeCalc != attr0.PackSize) return S_FALSE; } else { if (packSizeCalc != attr0.AllocatedSize) return S_FALSE; } return S_OK; } struct CDataRef { int Start; int Num; }; static const UInt32 kMagic_FILE = 0x454c4946; static const UInt32 kMagic_BAAD = 0x44414142; struct CMftRec { UInt32 Magic; // UInt64 Lsn; UInt16 SeqNumber; UInt16 Flags; // UInt16 LinkCount; // UInt16 NextAttrInstance; CMftRef BaseMftRef; // UInt32 ThisRecNumber; UInt32 MyNumNameLinks; CObjectVector DataAttrs; CObjectVector FileNames; CRecordVector DataRefs; CSiAttr SiAttr; void MoveAttrsFrom(CMftRec &src) { DataAttrs += src.DataAttrs; FileNames += src.FileNames; src.DataAttrs.ClearAndFree(); src.FileNames.ClearAndFree(); } UInt64 GetPackSize() const { UInt64 res = 0; for (int i = 0; i < DataRefs.Size(); i++) res += DataAttrs[DataRefs[i].Start].GetPackSize(); return res; } bool Parse(Byte *p, int sectorSizeLog, UInt32 numSectors, UInt32 recNumber, CObjectVector *attrs); bool IsEmpty() const { return (Magic <= 2); } bool IsFILE() const { return (Magic == kMagic_FILE); } bool IsBAAD() const { return (Magic == kMagic_BAAD); } bool InUse() const { return (Flags & 1) != 0; } bool IsDir() const { return (Flags & 2) != 0; } void ParseDataNames(); HRESULT GetStream(IInStream *mainStream, int dataIndex, int clusterSizeLog, UInt64 numPhysClusters, IInStream **stream) const; int GetNumExtents(int dataIndex, int clusterSizeLog, UInt64 numPhysClusters) const; UInt64 GetSize(int dataIndex) const { return DataAttrs[DataRefs[dataIndex].Start].GetSize(); } CMftRec(): MyNumNameLinks(0) {} }; void CMftRec::ParseDataNames() { DataRefs.Clear(); DataAttrs.Sort(CompareAttr, 0); for (int i = 0; i < DataAttrs.Size();) { CDataRef ref; ref.Start = i; for (i++; i < DataAttrs.Size(); i++) if (DataAttrs[ref.Start].Name != DataAttrs[i].Name) break; ref.Num = i - ref.Start; DataRefs.Add(ref); } } HRESULT CMftRec::GetStream(IInStream *mainStream, int dataIndex, int clusterSizeLog, UInt64 numPhysClusters, IInStream **destStream) const { *destStream = 0; CByteBufStream *streamSpec = new CByteBufStream; CMyComPtr streamTemp = streamSpec; if (dataIndex < 0) return E_FAIL; if (dataIndex < DataRefs.Size()) { const CDataRef &ref = DataRefs[dataIndex]; int numNonResident = 0; int i; for (i = ref.Start; i < ref.Start + ref.Num; i++) if (DataAttrs[i].NonResident) numNonResident++; const CAttr &attr0 = DataAttrs[ref.Start]; if (numNonResident != 0 || ref.Num != 1) { if (numNonResident != ref.Num || !attr0.IsCompressionUnitSupported()) return S_FALSE; CInStream *streamSpec = new CInStream; CMyComPtr streamTemp = streamSpec; RINOK(DataParseExtents(clusterSizeLog, DataAttrs, ref.Start, ref.Start + ref.Num, numPhysClusters, streamSpec->Extents)); streamSpec->Size = attr0.Size; streamSpec->InitializedSize = attr0.InitializedSize; streamSpec->Stream = mainStream; streamSpec->BlockSizeLog = clusterSizeLog; streamSpec->InUse = InUse(); RINOK(streamSpec->InitAndSeek(attr0.CompressionUnit)); *destStream = streamTemp.Detach(); return S_OK; } streamSpec->Buf = attr0.Data; } streamSpec->Init(); *destStream = streamTemp.Detach(); return S_OK; } int CMftRec::GetNumExtents(int dataIndex, int clusterSizeLog, UInt64 numPhysClusters) const { if (dataIndex < 0) return 0; { const CDataRef &ref = DataRefs[dataIndex]; int numNonResident = 0; int i; for (i = ref.Start; i < ref.Start + ref.Num; i++) if (DataAttrs[i].NonResident) numNonResident++; const CAttr &attr0 = DataAttrs[ref.Start]; if (numNonResident != 0 || ref.Num != 1) { if (numNonResident != ref.Num || !attr0.IsCompressionUnitSupported()) return 0; // error; CRecordVector extents; if (DataParseExtents(clusterSizeLog, DataAttrs, ref.Start, ref.Start + ref.Num, numPhysClusters, extents) != S_OK) return 0; // error; return extents.Size() - 1; } // if (attr0.Data.GetCapacity() != 0) // return 1; return 0; } } bool CMftRec::Parse(Byte *p, int sectorSizeLog, UInt32 numSectors, UInt32 recNumber, CObjectVector *attrs) { G32(p, Magic); if (!IsFILE()) return IsEmpty() || IsBAAD(); UInt32 usaOffset; UInt32 numUsaItems; G16(p + 0x04, usaOffset); G16(p + 0x06, numUsaItems); if ((usaOffset & 1) != 0 || usaOffset + numUsaItems * 2 > ((UInt32)1 << sectorSizeLog) - 2 || numUsaItems == 0 || numUsaItems - 1 != numSectors) return false; UInt16 usn = Get16(p + usaOffset); // PRF(printf("\nusn = %d", usn)); for (UInt32 i = 1; i < numUsaItems; i++) { void *pp = p + (i << sectorSizeLog) - 2; if (Get16(pp) != usn) return false; SetUi16(pp, Get16(p + usaOffset + i * 2)); } // G64(p + 0x08, Lsn); G16(p + 0x10, SeqNumber); // G16(p + 0x12, LinkCount); // PRF(printf(" L=%d", LinkCount)); UInt32 attrOffs = Get16(p + 0x14); G16(p + 0x16, Flags); PRF(printf(" F=%4X", Flags)); UInt32 bytesInUse = Get32(p + 0x18); UInt32 bytesAlloc = Get32(p + 0x1C); G64(p + 0x20, BaseMftRef.Val); if (BaseMftRef.Val != 0) { PRF(printf(" BaseRef=%d", (int)BaseMftRef.Val)); // return false; // Check it; } // G16(p + 0x28, NextAttrInstance); if (usaOffset >= 0x30) if (Get32(p + 0x2C) != recNumber) // NTFS 3.1+ return false; UInt32 limit = numSectors << sectorSizeLog; if (attrOffs >= limit || (attrOffs & 7) != 0 || bytesInUse > limit || bytesAlloc != limit) return false; for (UInt32 t = attrOffs; t < limit;) { CAttr attr; // PRF(printf("\n %2d:", Attrs.Size())); PRF(printf("\n")); UInt32 length = attr.Parse(p + t, limit - t); if (length == 0 || limit - t < length) return false; t += length; if (attr.Type == 0xFFFFFFFF) break; switch(attr.Type) { case ATTR_TYPE_FILE_NAME: { CFileNameAttr fna; if (!attr.ParseFileName(fna)) return false; FileNames.Add(fna); PRF(printf(" flags = %4x", (int)fna.NameType)); PRF(printf("\n %S", fna.Name)); break; } case ATTR_TYPE_STANDARD_INFO: if (!attr.ParseSi(SiAttr)) return false; break; case ATTR_TYPE_DATA: DataAttrs.Add(attr); break; default: if (attrs) attrs->Add(attr); break; } } return true; } struct CItem { int RecIndex; int DataIndex; CMftRef ParentRef; UString Name; UInt32 Attrib; bool IsDir() const { return (DataIndex < 0); } }; struct CDatabase { CHeader Header; CObjectVector Items; CObjectVector Recs; CMyComPtr InStream; IArchiveOpenCallback *OpenCallback; CByteBuffer ByteBuf; CObjectVector VolAttrs; ~CDatabase() { ClearAndClose(); } void Clear(); void ClearAndClose(); UString GetItemPath(Int32 index) const; HRESULT Open(); HRESULT ReadDir(Int32 parent, UInt32 cluster, int level); HRESULT SeekToCluster(UInt64 cluster); int FindMtfRec(const CMftRef &ref) const { UInt64 val = ref.GetIndex(); int left = 0, right = Items.Size(); while (left != right) { int mid = (left + right) / 2; UInt64 midValue = Items[mid].RecIndex; if (val == midValue) return mid; if (val < midValue) right = mid; else left = mid + 1; } return -1; } }; HRESULT CDatabase::SeekToCluster(UInt64 cluster) { return InStream->Seek(cluster << Header.ClusterSizeLog, STREAM_SEEK_SET, NULL); } void CDatabase::Clear() { Items.Clear(); Recs.Clear(); } void CDatabase::ClearAndClose() { Clear(); InStream.Release(); } #define MY_DIR_PREFIX(x) L"[" x L"]" WSTRING_PATH_SEPARATOR UString CDatabase::GetItemPath(Int32 index) const { const CItem *item = &Items[index]; UString name = item->Name; for (int j = 0; j < 256; j++) { CMftRef ref = item->ParentRef; index = FindMtfRec(ref); if (ref.GetIndex() == 5) return name; if (index < 0 || Recs[Items[index].RecIndex].SeqNumber != ref.GetNumber()) return MY_DIR_PREFIX(L"UNKNOWN") + name; item = &Items[index]; name = item->Name + WCHAR_PATH_SEPARATOR + name; } return MY_DIR_PREFIX(L"BAD") + name; } HRESULT CDatabase::Open() { Clear(); static const UInt32 kHeaderSize = 512; Byte buf[kHeaderSize]; RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)); if (!Header.Parse(buf)) return S_FALSE; UInt64 fileSize; RINOK(InStream->Seek(0, STREAM_SEEK_END, &fileSize)); if (fileSize < Header.GetPhySize()) return S_FALSE; SeekToCluster(Header.MftCluster); CMftRec mftRec; UInt32 numSectorsInRec; int recSizeLog; CMyComPtr mftStream; { UInt32 blockSize = 1 << 12; ByteBuf.SetCapacity(blockSize); RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize)); UInt32 allocSize = Get32(ByteBuf + 0x1C); recSizeLog = GetLog(allocSize); if (recSizeLog < Header.SectorSizeLog) return false; numSectorsInRec = 1 << (recSizeLog - Header.SectorSizeLog); if (!mftRec.Parse(ByteBuf, Header.SectorSizeLog, numSectorsInRec, NULL, 0)) return S_FALSE; if (!mftRec.IsFILE()) return S_FALSE; mftRec.ParseDataNames(); if (mftRec.DataRefs.IsEmpty()) return S_FALSE; RINOK(mftRec.GetStream(InStream, 0, Header.ClusterSizeLog, Header.NumClusters, &mftStream)); if (!mftStream) return S_FALSE; } UInt64 mftSize = mftRec.DataAttrs[0].Size; if ((mftSize >> 4) > Header.GetPhySize()) return S_FALSE; UInt64 numFiles = mftSize >> recSizeLog; if (numFiles > (1 << 30)) return S_FALSE; if (OpenCallback) { RINOK(OpenCallback->SetTotal(&numFiles, &mftSize)); } const UInt32 kBufSize = (1 << 15); if (kBufSize < (1 << recSizeLog)) return S_FALSE; ByteBuf.SetCapacity((size_t)kBufSize); Recs.Reserve((int)numFiles); for (UInt64 pos64 = 0;;) { if (OpenCallback) { UInt64 numFiles = Recs.Size(); if ((numFiles & 0x3FF) == 0) { RINOK(OpenCallback->SetCompleted(&numFiles, &pos64)); } } UInt32 readSize = kBufSize; UInt64 rem = mftSize - pos64; if (readSize > rem) readSize = (UInt32)rem; if (readSize < ((UInt32)1 << recSizeLog)) break; RINOK(ReadStream_FALSE(mftStream, ByteBuf, (size_t)readSize)); pos64 += readSize; for (int i = 0; ((UInt32)(i + 1) << recSizeLog) <= readSize; i++) { PRF(printf("\n---------------------")); PRF(printf("\n%5d:", Recs.Size())); Byte *p = ByteBuf + ((UInt32)i << recSizeLog); CMftRec rec; if (!rec.Parse(p, Header.SectorSizeLog, numSectorsInRec, (UInt32)Recs.Size(), (Recs.Size() == kRecIndex_Volume) ? &VolAttrs: NULL)) return S_FALSE; Recs.Add(rec); } } int i; for (i = 0; i < Recs.Size(); i++) { CMftRec &rec = Recs[i]; if (!rec.BaseMftRef.IsBaseItself()) { UInt64 refIndex = rec.BaseMftRef.GetIndex(); if (refIndex > (UInt32)Recs.Size()) return S_FALSE; CMftRec &refRec = Recs[(int)refIndex]; bool moveAttrs = (refRec.SeqNumber == rec.BaseMftRef.GetNumber() && refRec.BaseMftRef.IsBaseItself()); if (rec.InUse() && refRec.InUse()) { if (!moveAttrs) return S_FALSE; } else if (rec.InUse() || refRec.InUse()) moveAttrs = false; if (moveAttrs) refRec.MoveAttrsFrom(rec); } } for (i = 0; i < Recs.Size(); i++) Recs[i].ParseDataNames(); for (i = 0; i < Recs.Size(); i++) { CMftRec &rec = Recs[i]; if (!rec.IsFILE() || !rec.BaseMftRef.IsBaseItself()) continue; int numNames = 0; // printf("\n%4d: ", i); for (int t = 0; t < rec.FileNames.Size(); t++) { const CFileNameAttr &fna = rec.FileNames[t]; // printf("%4d %S | ", (int)fna.NameType, fna.Name); if (fna.IsDos()) continue; int numDatas = rec.DataRefs.Size(); // For hard linked files we show substreams only for first Name. if (numDatas > 1 && numNames > 0) numDatas = 1; numNames++; if (rec.IsDir()) { CItem item; item.Name = fna.Name; item.RecIndex = i; item.DataIndex = -1; item.ParentRef = fna.ParentDirRef; item.Attrib = rec.SiAttr.Attrib | 0x10; // item.Attrib = fna.Attrib; Items.Add(item); } for (int di = 0; di < numDatas; di++) { CItem item; item.Name = fna.Name; item.Attrib = rec.SiAttr.Attrib; const UString &subName = rec.DataAttrs[rec.DataRefs[di].Start].Name; if (!subName.IsEmpty()) { // $BadClus:$Bad is sparse file for all clusters. So we skip it. if (i == kRecIndex_BadClus && subName == L"$Bad") continue; item.Name += L":"; item.Name += subName; item.Attrib = fna.Attrib; } PRF(printf("\n%3d", i)); PRF(printf(" attrib=%2x", rec.SiAttr.Attrib)); PRF(printf(" %S", item.Name)); item.RecIndex = i; item.DataIndex = di; item.ParentRef = fna.ParentDirRef; Items.Add(item); rec.MyNumNameLinks++; } } rec.FileNames.ClearAndFree(); } return S_OK; } class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp, CDatabase { public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { COM_TRY_BEGIN IInStream *stream2; const CItem &item = Items[index]; const CMftRec &rec = Recs[item.RecIndex]; HRESULT res = rec.GetStream(InStream, item.DataIndex, Header.ClusterSizeLog, Header.NumClusters, &stream2); *stream = (ISequentialInStream *)stream2; return res; COM_TRY_END } static const STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidIsDir, VT_BOOL}, { NULL, kpidSize, VT_UI8}, { NULL, kpidPackSize, VT_UI8}, { NULL, kpidMTime, VT_FILETIME}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidATime, VT_FILETIME}, { NULL, kpidAttrib, VT_UI4}, { NULL, kpidLinks, VT_UI4}, { NULL, kpidNumBlocks, VT_UI4} }; static const STATPROPSTG kArcProps[] = { { NULL, kpidVolumeName, VT_BSTR}, { NULL, kpidFileSystem, VT_BSTR}, { NULL, kpidClusterSize, VT_UI4}, { NULL, kpidPhySize, VT_UI8}, { NULL, kpidHeadersSize, VT_UI8}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidSectorSize, VT_UI4}, { NULL, kpidId, VT_UI8} // { NULL, kpidSectorsPerTrack, VT_UI4}, // { NULL, kpidNumHeads, VT_UI4}, // { NULL, kpidHiddenSectors, VT_UI4} }; IMP_IInArchive_Props IMP_IInArchive_ArcProps static void NtfsTimeToProp(UInt64 t, NWindows::NCOM::CPropVariant &prop) { FILETIME ft; ft.dwLowDateTime = (DWORD)t; ft.dwHighDateTime = (DWORD)(t >> 32); prop = ft; } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CMftRec *volRec = (Recs.Size() > kRecIndex_Volume ? &Recs[kRecIndex_Volume] : NULL); switch(propID) { case kpidClusterSize: prop = Header.ClusterSize(); break; case kpidPhySize: prop = Header.GetPhySize(); break; /* case kpidHeadersSize: { UInt64 val = 0; for (int i = 0; i < kNumSysRecs; i++) { printf("\n%2d: %8I64d ", i, Recs[i].GetPackSize()); if (i == 8) i = i val += Recs[i].GetPackSize(); } prop = val; break; } */ case kpidCTime: if (volRec) NtfsTimeToProp(volRec->SiAttr.CTime, prop); break;break; case kpidVolumeName: { for (int i = 0; i < VolAttrs.Size(); i++) { const CAttr &attr = VolAttrs[i]; if (attr.Type == ATTR_TYPE_VOLUME_NAME) { UString name; GetString(attr.Data, (int)attr.Data.GetCapacity() / 2, name); prop = name; break; } } break; } case kpidFileSystem: { AString s = "NTFS"; for (int i = 0; i < VolAttrs.Size(); i++) { const CAttr &attr = VolAttrs[i]; if (attr.Type == ATTR_TYPE_VOLUME_INFO) { CVolInfo vi; if (attr.ParseVolInfo(vi)) { s += ' '; char temp[16]; ConvertUInt32ToString(vi.MajorVer, temp); s += temp; s += '.'; ConvertUInt32ToString(vi.MinorVer, temp); s += temp; } break; } } prop = s; break; } case kpidSectorSize: prop = (UInt32)1 << Header.SectorSizeLog; break; case kpidId: prop = Header.SerialNumber; break; // case kpidMediaType: prop = Header.MediaType; break; // case kpidSectorsPerTrack: prop = Header.SectorsPerTrack; break; // case kpidNumHeads: prop = Header.NumHeads; break; // case kpidHiddenSectors: prop = Header.NumHiddenSectors; break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItem &item = Items[index]; const CMftRec &rec = Recs[item.RecIndex]; const CAttr *data= NULL; if (item.DataIndex >= 0) data = &rec.DataAttrs[rec.DataRefs[item.DataIndex].Start]; switch(propID) { case kpidPath: { UString name = GetItemPath(index); const wchar_t *prefix = NULL; if (!rec.InUse()) prefix = MY_DIR_PREFIX(L"DELETED"); else if (item.RecIndex < kNumSysRecs) prefix = MY_DIR_PREFIX(L"SYSTEM"); if (prefix) name = prefix + name; prop = name; break; } case kpidIsDir: prop = item.IsDir(); break; case kpidMTime: NtfsTimeToProp(rec.SiAttr.MTime, prop); break; case kpidCTime: NtfsTimeToProp(rec.SiAttr.CTime, prop); break; case kpidATime: NtfsTimeToProp(rec.SiAttr.ATime, prop); break; case kpidAttrib: prop = item.Attrib; break; case kpidLinks: prop = rec.MyNumNameLinks; break; case kpidSize: if (data) prop = data->GetSize(); break; case kpidPackSize: if (data) prop = data->GetPackSize(); break; case kpidNumBlocks: if (data) prop = (UInt32)rec.GetNumExtents(item.DataIndex, Header.ClusterSizeLog, Header.NumClusters); break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN { OpenCallback = callback; InStream = stream; HRESULT res; try { res = CDatabase::Open(); if (res == S_OK) return S_OK; } catch(...) { Close(); throw; } Close(); return res; } COM_TRY_END } STDMETHODIMP CHandler::Close() { ClearAndClose(); return S_OK; } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)-1); if (allFilesMode) numItems = Items.Size(); if (numItems == 0) return S_OK; UInt32 i; UInt64 totalSize = 0; for (i = 0; i < numItems; i++) { const CItem &item = Items[allFilesMode ? i : indices[i]]; const CMftRec &rec = Recs[item.RecIndex]; if (!rec.IsDir()) totalSize += rec.GetSize(item.DataIndex); } RINOK(extractCallback->SetTotal(totalSize)); UInt64 totalPackSize; totalSize = totalPackSize = 0; CByteBuffer buf; UInt32 clusterSize = Header.ClusterSize(); buf.SetCapacity(clusterSize); NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CDummyOutStream *outStreamSpec = new CDummyOutStream; CMyComPtr outStream(outStreamSpec); for (i = 0; i < numItems; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; Int32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); const CItem &item = Items[index]; if (item.IsDir()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); outStreamSpec->SetStream(realOutStream); realOutStream.Release(); outStreamSpec->Init(); const CMftRec &rec = Recs[item.RecIndex]; const CAttr &data = rec.DataAttrs[rec.DataRefs[item.DataIndex].Start]; int res = NExtract::NOperationResult::kDataError; { CMyComPtr inStream; HRESULT hres = rec.GetStream(InStream, item.DataIndex, Header.ClusterSizeLog, Header.NumClusters, &inStream); if (hres == S_FALSE) res = NExtract::NOperationResult::kUnSupportedMethod; else { RINOK(hres); if (inStream) { HRESULT hres = copyCoder->Code(inStream, outStream, NULL, NULL, progress); if (hres != S_OK && hres != S_FALSE) { RINOK(hres); } if (/* copyCoderSpec->TotalSize == item.GetSize() && */ hres == S_OK) res = NExtract::NOperationResult::kOK; } } } totalPackSize += data.GetPackSize(); totalSize += data.GetSize(); outStreamSpec->ReleaseStream(); RINOK(extractCallback->SetOperationResult(res)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = Items.Size(); return S_OK; } static IInArchive *CreateArc() { return new CHandler; } static CArcInfo g_ArcInfo = { L"NTFS", L"ntfs img", 0, 0xD9, { 'N', 'T', 'F', 'S', ' ', ' ', ' ', ' ', 0 }, 9, false, CreateArc, 0 }; REGISTER_ARC(Fat) }}