| // FatHandler.cpp |
| |
| #include "StdAfx.h" |
| |
| // #include <stdio.h> |
| |
| #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/LimitedStreams.h" |
| #include "../Common/ProgressUtils.h" |
| #include "../Common/RegisterArc.h" |
| #include "../Common/StreamUtils.h" |
| |
| #include "../Compress/CopyCoder.h" |
| |
| #include "Common/DummyOutStream.h" |
| |
| #define Get16(p) GetUi16(p) |
| #define Get32(p) GetUi32(p) |
| |
| #define PRF(x) /* x */ |
| |
| namespace NArchive { |
| namespace NFat { |
| |
| static const UInt32 kFatItemUsedByDirMask = (UInt32)1 << 31; |
| |
| struct CHeader |
| { |
| UInt32 NumSectors; |
| UInt16 NumReservedSectors; |
| Byte NumFats; |
| UInt32 NumFatSectors; |
| UInt32 RootDirSector; |
| UInt32 NumRootDirSectors; |
| UInt32 DataSector; |
| |
| UInt32 FatSize; |
| UInt32 BadCluster; |
| |
| Byte NumFatBits; |
| Byte SectorSizeLog; |
| Byte SectorsPerClusterLog; |
| Byte ClusterSizeLog; |
| |
| UInt16 SectorsPerTrack; |
| UInt16 NumHeads; |
| UInt32 NumHiddenSectors; |
| |
| bool VolFieldsDefined; |
| |
| UInt32 VolId; |
| // Byte VolName[11]; |
| // Byte FileSys[8]; |
| |
| // Byte OemName[5]; |
| Byte MediaType; |
| |
| // 32-bit FAT |
| UInt16 Flags; |
| UInt16 FsInfoSector; |
| UInt32 RootCluster; |
| |
| bool IsFat32() const { return NumFatBits == 32; } |
| UInt64 GetPhySize() const { return (UInt64)NumSectors << SectorSizeLog; } |
| UInt32 SectorSize() const { return (UInt32)1 << SectorSizeLog; } |
| UInt32 ClusterSize() const { return (UInt32)1 << ClusterSizeLog; } |
| UInt32 ClusterToSector(UInt32 c) const { return DataSector + ((c - 2) << SectorsPerClusterLog); } |
| UInt32 IsEoc(UInt32 c) const { return c > BadCluster; } |
| UInt32 IsEocAndUnused(UInt32 c) const { return c > BadCluster && (c & kFatItemUsedByDirMask) == 0; } |
| UInt32 IsValidCluster(UInt32 c) const { return c >= 2 && c < FatSize; } |
| UInt32 SizeToSectors(UInt32 size) const { return (size + SectorSize() - 1) >> SectorSizeLog; } |
| UInt32 CalcFatSizeInSectors() const { return SizeToSectors((FatSize * (NumFatBits / 4) + 1) / 2); } |
| |
| UInt32 GetFatSector() const |
| { |
| UInt32 index = (IsFat32() && (Flags & 0x80) != 0) ? (Flags & 0xF) : 0; |
| if (index > NumFats) |
| index = 0; |
| return NumReservedSectors + index * NumFatSectors; |
| } |
| |
| UInt64 GetFilePackSize(UInt32 unpackSize) const |
| { |
| UInt64 mask = ClusterSize() - 1; |
| return (unpackSize + mask) & ~mask; |
| } |
| |
| UInt32 GetNumClusters(UInt32 size) const |
| { return (UInt32)(((UInt64)size + ClusterSize() - 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; |
| } |
| { |
| 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; |
| } |
| |
| NumReservedSectors = Get16(p + 14); |
| if (NumReservedSectors == 0) |
| return false; |
| |
| NumFats = p[16]; |
| if (NumFats < 1 || NumFats > 4) |
| return false; |
| |
| UInt16 numRootDirEntries = Get16(p + 17); |
| if (numRootDirEntries == 0) |
| { |
| if (codeOffset < 90) |
| return false; |
| NumFatBits = 32; |
| NumRootDirSectors = 0; |
| } |
| else |
| { |
| if (codeOffset < 62) |
| return false; |
| NumFatBits = 0; |
| UInt32 mask = (1 << (SectorSizeLog - 5)) - 1; |
| if ((numRootDirEntries & mask) != 0) |
| return false; |
| NumRootDirSectors = (numRootDirEntries + mask) >> (SectorSizeLog - 5); |
| } |
| |
| NumSectors = Get16(p + 19); |
| if (NumSectors == 0) |
| NumSectors = Get32(p + 32); |
| else if (IsFat32()) |
| return false; |
| |
| MediaType = p[21]; |
| NumFatSectors = Get16(p + 22); |
| SectorsPerTrack = Get16(p + 24); |
| NumHeads = Get16(p + 26); |
| NumHiddenSectors = Get32(p + 28); |
| |
| // memcpy(OemName, p + 3, 5); |
| |
| p += 36; |
| if (IsFat32()) |
| { |
| if (NumFatSectors != 0) |
| return false; |
| NumFatSectors = Get32(p); |
| if (NumFatSectors >= (1 << 24)) |
| return false; |
| |
| Flags = Get16(p + 4); |
| if (Get16(p + 6) != 0) |
| return false; |
| RootCluster = Get32(p + 8); |
| FsInfoSector = Get16(p + 12); |
| for (int i = 16; i < 28; i++) |
| if (p[i] != 0) |
| return false; |
| p += 28; |
| } |
| |
| // DriveNumber = p[0]; |
| VolFieldsDefined = (p[2] == 0x29); // ExtendedBootSig |
| VolId = Get32(p + 3); |
| // memcpy(VolName, p + 7, 11); |
| // memcpy(FileSys, p + 18, 8); |
| |
| if (NumFatSectors == 0) |
| return false; |
| RootDirSector = NumReservedSectors + NumFatSectors * NumFats; |
| DataSector = RootDirSector + NumRootDirSectors; |
| if (NumSectors < DataSector) |
| return false; |
| UInt32 numDataSectors = NumSectors - DataSector; |
| UInt32 numClusters = numDataSectors >> SectorsPerClusterLog; |
| |
| BadCluster = 0x0FFFFFF7; |
| if (numClusters < 0xFFF5) |
| { |
| if (NumFatBits == 32) |
| return false; |
| NumFatBits = (numClusters < 0xFF5) ? 12: 16; |
| BadCluster &= ((1 << NumFatBits) - 1); |
| } |
| else if (NumFatBits != 32) |
| return false; |
| |
| FatSize = numClusters + 2; |
| if (FatSize > BadCluster || CalcFatSizeInSectors() > NumFatSectors) |
| return false; |
| return true; |
| } |
| |
| struct CItem |
| { |
| UString UName; |
| char DosName[11]; |
| Byte CTime2; |
| UInt32 CTime; |
| UInt32 MTime; |
| UInt16 ADate; |
| Byte Attrib; |
| Byte Flags; |
| UInt32 Size; |
| UInt32 Cluster; |
| Int32 Parent; |
| |
| // NT uses Flags to store Low Case status |
| bool NameIsLow() const { return (Flags & 0x8) != 0; } |
| bool ExtIsLow() const { return (Flags & 0x10) != 0; } |
| bool IsDir() const { return (Attrib & 0x10) != 0; } |
| UString GetShortName() const; |
| UString GetName() const; |
| UString GetVolName() const; |
| }; |
| |
| static int CopyAndTrim(char *dest, const char *src, int size, bool toLower) |
| { |
| int i; |
| memcpy(dest, src, size); |
| if (toLower) |
| for (i = 0; i < size; i++) |
| { |
| char c = dest[i]; |
| if (c >= 'A' && c <= 'Z') |
| dest[i] = c + 0x20; |
| } |
| for (i = size - 1; i >= 0 && dest[i] == ' '; i--); |
| return i + 1; |
| } |
| |
| static UString FatStringToUnicode(const char *s) |
| { |
| return MultiByteToUnicodeString(s, CP_OEMCP); |
| } |
| |
| UString CItem::GetShortName() const |
| { |
| char s[16]; |
| int i = CopyAndTrim(s, DosName, 8, NameIsLow()); |
| s[i++] = '.'; |
| int j = CopyAndTrim(s + i, DosName + 8, 3, ExtIsLow()); |
| if (j == 0) |
| j--; |
| s[i + j] = 0; |
| return FatStringToUnicode(s); |
| } |
| |
| UString CItem::GetName() const |
| { |
| if (!UName.IsEmpty()) |
| return UName; |
| return GetShortName(); |
| } |
| |
| UString CItem::GetVolName() const |
| { |
| if (!UName.IsEmpty()) |
| return UName; |
| char s[12]; |
| int i = CopyAndTrim(s, DosName, 11, false); |
| s[i] = 0; |
| return FatStringToUnicode(s); |
| } |
| |
| struct CDatabase |
| { |
| CHeader Header; |
| CObjectVector<CItem> Items; |
| UInt32 *Fat; |
| CMyComPtr<IInStream> InStream; |
| IArchiveOpenCallback *OpenCallback; |
| |
| UInt32 NumFreeClusters; |
| bool VolItemDefined; |
| CItem VolItem; |
| UInt32 NumDirClusters; |
| CByteBuffer ByteBuf; |
| UInt64 NumCurUsedBytes; |
| |
| CDatabase(): Fat(0) {} |
| ~CDatabase() { ClearAndClose(); } |
| |
| void Clear(); |
| void ClearAndClose(); |
| HRESULT OpenProgressFat(bool changeTotal = true); |
| HRESULT OpenProgress(); |
| |
| UString GetItemPath(Int32 index) const; |
| HRESULT Open(); |
| HRESULT ReadDir(Int32 parent, UInt32 cluster, int level); |
| |
| UInt64 GetHeadersSize() const |
| { |
| return (UInt64)(Header.DataSector + (NumDirClusters << Header.SectorsPerClusterLog)) << Header.SectorSizeLog; |
| } |
| HRESULT SeekToSector(UInt32 sector); |
| HRESULT SeekToCluster(UInt32 cluster) { return SeekToSector(Header.ClusterToSector(cluster)); } |
| }; |
| |
| HRESULT CDatabase::SeekToSector(UInt32 sector) |
| { |
| return InStream->Seek((UInt64)sector << Header.SectorSizeLog, STREAM_SEEK_SET, NULL); |
| } |
| |
| void CDatabase::Clear() |
| { |
| VolItemDefined = false; |
| NumDirClusters = 0; |
| NumCurUsedBytes = 0; |
| |
| Items.Clear(); |
| delete []Fat; |
| Fat = 0; |
| } |
| |
| void CDatabase::ClearAndClose() |
| { |
| Clear(); |
| InStream.Release(); |
| } |
| |
| HRESULT CDatabase::OpenProgressFat(bool changeTotal) |
| { |
| if (!OpenCallback) |
| return S_OK; |
| if (changeTotal) |
| { |
| UInt64 numTotalBytes = (Header.CalcFatSizeInSectors() << Header.SectorSizeLog) + |
| ((UInt64)(Header.FatSize - NumFreeClusters) << Header.ClusterSizeLog); |
| RINOK(OpenCallback->SetTotal(NULL, &numTotalBytes)); |
| } |
| return OpenCallback->SetCompleted(NULL, &NumCurUsedBytes); |
| } |
| |
| HRESULT CDatabase::OpenProgress() |
| { |
| if (!OpenCallback) |
| return S_OK; |
| UInt64 numItems = Items.Size(); |
| return OpenCallback->SetCompleted(&numItems, &NumCurUsedBytes); |
| } |
| |
| UString CDatabase::GetItemPath(Int32 index) const |
| { |
| const CItem *item = &Items[index]; |
| UString name = item->GetName(); |
| for (;;) |
| { |
| index = item->Parent; |
| if (index < 0) |
| return name; |
| item = &Items[index]; |
| name = item->GetName() + WCHAR_PATH_SEPARATOR + name; |
| } |
| } |
| |
| static wchar_t *AddSubStringToName(wchar_t *dest, const Byte *p, int numChars) |
| { |
| for (int i = 0; i < numChars; i++) |
| { |
| wchar_t c = Get16(p + i * 2); |
| if (c != 0 && c != 0xFFFF) |
| *dest++ = c; |
| } |
| *dest = 0; |
| return dest; |
| } |
| |
| HRESULT CDatabase::ReadDir(Int32 parent, UInt32 cluster, int level) |
| { |
| int startIndex = Items.Size(); |
| if (startIndex >= (1 << 30) || level > 256) |
| return S_FALSE; |
| |
| UInt32 sectorIndex = 0; |
| UInt32 blockSize = Header.ClusterSize(); |
| bool clusterMode = (Header.IsFat32() || parent >= 0); |
| if (!clusterMode) |
| { |
| blockSize = Header.SectorSize(); |
| RINOK(SeekToSector(Header.RootDirSector)); |
| } |
| |
| ByteBuf.SetCapacity(blockSize); |
| UString curName; |
| int checkSum = -1; |
| int numLongRecords = -1; |
| for (UInt32 pos = blockSize;; pos += 32) |
| { |
| if (pos == blockSize) |
| { |
| pos = 0; |
| |
| if ((NumDirClusters & 0xFF) == 0) |
| { |
| RINOK(OpenProgress()); |
| } |
| |
| if (clusterMode) |
| { |
| if (Header.IsEoc(cluster)) |
| break; |
| if (!Header.IsValidCluster(cluster)) |
| return S_FALSE; |
| PRF(printf("\nCluster = %4X", cluster)); |
| RINOK(SeekToCluster(cluster)); |
| UInt32 newCluster = Fat[cluster]; |
| if ((newCluster & kFatItemUsedByDirMask) != 0) |
| return S_FALSE; |
| Fat[cluster] |= kFatItemUsedByDirMask; |
| cluster = newCluster; |
| NumDirClusters++; |
| NumCurUsedBytes += Header.ClusterSize(); |
| } |
| else if (sectorIndex++ >= Header.NumRootDirSectors) |
| break; |
| |
| RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize)); |
| } |
| const Byte *p = ByteBuf + pos; |
| if (p[0] == 0) |
| { |
| /* |
| // FreeDOS formats FAT partition with cluster chain longer than required. |
| if (clusterMode && !Header.IsEoc(cluster)) |
| return S_FALSE; |
| */ |
| break; |
| } |
| if (p[0] == 0xE5) |
| { |
| if (numLongRecords > 0) |
| return S_FALSE; |
| continue; |
| } |
| |
| Byte attrib = p[11]; |
| if ((attrib & 0x3F) == 0xF) |
| { |
| if (p[0] > 0x7F || Get16(p + 26) != 0) |
| return S_FALSE; |
| int longIndex = p[0] & 0x3F; |
| if (longIndex == 0) |
| return S_FALSE; |
| bool isLast = (p[0] & 0x40) != 0; |
| if (numLongRecords < 0) |
| { |
| if (!isLast) |
| return S_FALSE; |
| numLongRecords = longIndex; |
| } |
| else if (isLast || numLongRecords != longIndex) |
| return S_FALSE; |
| |
| numLongRecords--; |
| |
| if (p[12] == 0) |
| { |
| wchar_t nameBuf[14]; |
| wchar_t *dest; |
| |
| dest = AddSubStringToName(nameBuf, p + 1, 5); |
| dest = AddSubStringToName(dest, p + 14, 6); |
| AddSubStringToName(dest, p + 28, 2); |
| curName = nameBuf + curName; |
| if (isLast) |
| checkSum = p[13]; |
| if (checkSum != p[13]) |
| return S_FALSE; |
| } |
| } |
| else |
| { |
| if (numLongRecords > 0) |
| return S_FALSE; |
| CItem item; |
| memcpy(item.DosName, p, 11); |
| |
| if (checkSum >= 0) |
| { |
| Byte sum = 0; |
| for (int i = 0; i < 11; i++) |
| sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + (Byte)item.DosName[i]; |
| if (sum == checkSum) |
| item.UName = curName; |
| } |
| |
| if (item.DosName[0] == 5) |
| item.DosName[0] = (char)(Byte)0xE5; |
| item.Attrib = attrib; |
| item.Flags = p[12]; |
| item.Size = Get32(p + 28); |
| item.Cluster = Get16(p + 26); |
| if (Header.NumFatBits > 16) |
| item.Cluster |= ((UInt32)Get16(p + 20) << 16); |
| else |
| { |
| // OS/2 and WinNT probably can store EA (extended atributes) in that field. |
| } |
| |
| item.CTime = Get32(p + 14); |
| item.CTime2 = p[13]; |
| item.ADate = Get16(p + 18); |
| item.MTime = Get32(p + 22); |
| item.Parent = parent; |
| |
| if (attrib == 8) |
| { |
| VolItem = item; |
| VolItemDefined = true; |
| } |
| else |
| if (memcmp(item.DosName, ". ", 11) != 0 && |
| memcmp(item.DosName, ".. ", 11) != 0) |
| { |
| if (!item.IsDir()) |
| NumCurUsedBytes += Header.GetFilePackSize(item.Size); |
| Items.Add(item); |
| PRF(printf("\n%7d: %S", Items.Size(), GetItemPath(Items.Size() - 1))); |
| } |
| numLongRecords = -1; |
| curName.Empty(); |
| checkSum = -1; |
| } |
| } |
| |
| int finishIndex = Items.Size(); |
| for (int i = startIndex; i < finishIndex; i++) |
| { |
| const CItem &item = Items[i]; |
| if (item.IsDir()) |
| { |
| PRF(printf("\n%S", GetItemPath(i))); |
| RINOK(CDatabase::ReadDir(i, item.Cluster, level + 1)); |
| } |
| } |
| return S_OK; |
| } |
| |
| HRESULT CDatabase::Open() |
| { |
| Clear(); |
| bool numFreeClustersDefined = false; |
| { |
| 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)); |
| |
| /* we comment that check to support truncated images */ |
| /* |
| if (fileSize < Header.GetPhySize()) |
| return S_FALSE; |
| */ |
| |
| if (Header.IsFat32()) |
| { |
| SeekToSector(Header.FsInfoSector); |
| RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)); |
| if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA) |
| return S_FALSE; |
| if (Get32(buf) == 0x41615252 && Get32(buf + 484) == 0x61417272) |
| { |
| NumFreeClusters = Get32(buf + 488); |
| numFreeClustersDefined = (NumFreeClusters <= Header.FatSize); |
| } |
| } |
| } |
| |
| // numFreeClustersDefined = false; // to recalculate NumFreeClusters |
| if (!numFreeClustersDefined) |
| NumFreeClusters = 0; |
| |
| CByteBuffer byteBuf; |
| Fat = new UInt32[Header.FatSize]; |
| |
| RINOK(OpenProgressFat()); |
| RINOK(SeekToSector(Header.GetFatSector())); |
| if (Header.NumFatBits == 32) |
| { |
| const UInt32 kBufSize = (1 << 15); |
| byteBuf.SetCapacity(kBufSize); |
| for (UInt32 i = 0; i < Header.FatSize;) |
| { |
| UInt32 size = Header.FatSize - i; |
| const UInt32 kBufSize32 = kBufSize / 4; |
| if (size > kBufSize32) |
| size = kBufSize32; |
| UInt32 readSize = Header.SizeToSectors(size * 4) << Header.SectorSizeLog; |
| RINOK(ReadStream_FALSE(InStream, byteBuf, readSize)); |
| NumCurUsedBytes += readSize; |
| |
| const UInt32 *src = (const UInt32 *)(const Byte *)byteBuf; |
| UInt32 *dest = Fat + i; |
| if (numFreeClustersDefined) |
| for (UInt32 j = 0; j < size; j++) |
| dest[j] = Get32(src + j) & 0x0FFFFFFF; |
| else |
| { |
| UInt32 numFreeClusters = 0; |
| for (UInt32 j = 0; j < size; j++) |
| { |
| UInt32 v = Get32(src + j) & 0x0FFFFFFF; |
| numFreeClusters += (UInt32)(v - 1) >> 31; |
| dest[j] = v; |
| } |
| NumFreeClusters += numFreeClusters; |
| } |
| i += size; |
| if ((i & 0xFFFFF) == 0) |
| { |
| RINOK(OpenProgressFat(!numFreeClustersDefined)); |
| } |
| } |
| } |
| else |
| { |
| const UInt32 kBufSize = (UInt32)Header.CalcFatSizeInSectors() << Header.SectorSizeLog; |
| NumCurUsedBytes += kBufSize; |
| byteBuf.SetCapacity(kBufSize); |
| Byte *p = byteBuf; |
| RINOK(ReadStream_FALSE(InStream, p, kBufSize)); |
| UInt32 fatSize = Header.FatSize; |
| UInt32 *fat = &Fat[0]; |
| if (Header.NumFatBits == 16) |
| for (UInt32 j = 0; j < fatSize; j++) |
| fat[j] = Get16(p + j * 2); |
| else |
| for (UInt32 j = 0; j < fatSize; j++) |
| fat[j] = (Get16(p + j * 3 / 2) >> ((j & 1) << 2)) & 0xFFF; |
| |
| if (!numFreeClustersDefined) |
| { |
| UInt32 numFreeClusters = 0; |
| for (UInt32 i = 0; i < fatSize; i++) |
| numFreeClusters += (UInt32)(fat[i] - 1) >> 31; |
| NumFreeClusters = numFreeClusters; |
| } |
| } |
| |
| RINOK(OpenProgressFat()); |
| |
| if ((Fat[0] & 0xFF) != Header.MediaType) |
| return S_FALSE; |
| |
| return ReadDir(-1, Header.RootCluster, 0); |
| } |
| |
| 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 |
| *stream = 0; |
| const CItem &item = Items[index]; |
| CClusterInStream *streamSpec = new CClusterInStream; |
| CMyComPtr<ISequentialInStream> streamTemp = streamSpec; |
| streamSpec->Stream = InStream; |
| streamSpec->StartOffset = Header.DataSector << Header.SectorSizeLog; |
| streamSpec->BlockSizeLog = Header.ClusterSizeLog; |
| streamSpec->Size = item.Size; |
| |
| UInt32 numClusters = Header.GetNumClusters(item.Size); |
| streamSpec->Vector.Reserve(numClusters); |
| UInt32 cluster = item.Cluster; |
| UInt32 size = item.Size; |
| |
| if (size == 0) |
| { |
| if (cluster != 0) |
| return S_FALSE; |
| } |
| else |
| { |
| UInt32 clusterSize = Header.ClusterSize(); |
| for (;; size -= clusterSize) |
| { |
| if (!Header.IsValidCluster(cluster)) |
| return S_FALSE; |
| streamSpec->Vector.Add(cluster - 2); |
| cluster = Fat[cluster]; |
| if (size <= clusterSize) |
| break; |
| } |
| if (!Header.IsEocAndUnused(cluster)) |
| return S_FALSE; |
| } |
| RINOK(streamSpec->InitAndSeek()); |
| *stream = streamTemp.Detach(); |
| return S_OK; |
| COM_TRY_END |
| } |
| |
| 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_UI8}, |
| { NULL, kpidShortName, VT_BSTR} |
| }; |
| |
| enum |
| { |
| kpidNumFats = kpidUserDefined |
| // kpidOemName, |
| // kpidVolName, |
| // kpidFileSysType |
| }; |
| |
| STATPROPSTG kArcProps[] = |
| { |
| { NULL, kpidFileSystem, VT_BSTR}, |
| { NULL, kpidClusterSize, VT_UI4}, |
| { NULL, kpidPhySize, VT_UI8}, |
| { NULL, kpidFreeSpace, VT_UI8}, |
| { NULL, kpidHeadersSize, VT_UI8}, |
| { NULL, kpidMTime, VT_FILETIME}, |
| { NULL, kpidVolumeName, VT_BSTR}, |
| |
| { L"FATs", kpidNumFats, VT_UI4}, |
| { NULL, kpidSectorSize, VT_UI4}, |
| { NULL, kpidId, VT_UI4}, |
| // { L"OEM Name", kpidOemName, VT_BSTR}, |
| // { L"Volume Name", kpidVolName, VT_BSTR}, |
| // { L"File System Type", kpidFileSysType, VT_BSTR} |
| // { NULL, kpidSectorsPerTrack, VT_UI4}, |
| // { NULL, kpidNumHeads, VT_UI4}, |
| // { NULL, kpidHiddenSectors, VT_UI4} |
| }; |
| |
| IMP_IInArchive_Props |
| IMP_IInArchive_ArcProps_WITH_NAME |
| |
| static void FatTimeToProp(UInt32 dosTime, UInt32 ms10, NWindows::NCOM::CPropVariant &prop) |
| { |
| FILETIME localFileTime, utc; |
| if (NWindows::NTime::DosTimeToFileTime(dosTime, localFileTime)) |
| if (LocalFileTimeToFileTime(&localFileTime, &utc)) |
| { |
| UInt64 t64 = (((UInt64)utc.dwHighDateTime) << 32) + utc.dwLowDateTime; |
| t64 += ms10 * 100000; |
| utc.dwLowDateTime = (DWORD)t64; |
| utc.dwHighDateTime = (DWORD)(t64 >> 32); |
| prop = utc; |
| } |
| } |
| |
| /* |
| static void StringToProp(const Byte *src, int size, NWindows::NCOM::CPropVariant &prop) |
| { |
| char dest[32]; |
| memcpy(dest, src, size); |
| dest[size] = 0; |
| prop = FatStringToUnicode(dest); |
| } |
| |
| #define STRING_TO_PROP(s, p) StringToProp(s, sizeof(s), prop) |
| */ |
| |
| STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) |
| { |
| COM_TRY_BEGIN |
| NWindows::NCOM::CPropVariant prop; |
| switch(propID) |
| { |
| case kpidFileSystem: |
| { |
| wchar_t s[32] = { L'F', L'A', L'T' }; |
| ConvertUInt32ToString(Header.NumFatBits, s + 3); |
| prop = s; |
| break; |
| } |
| case kpidClusterSize: prop = Header.ClusterSize(); break; |
| case kpidPhySize: prop = Header.GetPhySize(); break; |
| case kpidFreeSpace: prop = (UInt64)NumFreeClusters << Header.ClusterSizeLog; break; |
| case kpidHeadersSize: prop = GetHeadersSize(); break; |
| case kpidMTime: if (VolItemDefined) FatTimeToProp(VolItem.MTime, 0, prop); break; |
| case kpidVolumeName: if (VolItemDefined) prop = VolItem.GetVolName(); break; |
| case kpidNumFats: if (Header.NumFats != 2) prop = Header.NumFats; break; |
| case kpidSectorSize: prop = (UInt32)1 << Header.SectorSizeLog; break; |
| // case kpidSectorsPerTrack: prop = Header.SectorsPerTrack; break; |
| // case kpidNumHeads: prop = Header.NumHeads; break; |
| // case kpidOemName: STRING_TO_PROP(Header.OemName, prop); break; |
| case kpidId: if (Header.VolFieldsDefined) prop = Header.VolId; break; |
| // case kpidVolName: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.VolName, prop); break; |
| // case kpidFileSysType: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.FileSys, prop); 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]; |
| switch(propID) |
| { |
| case kpidPath: prop = GetItemPath(index); break; |
| case kpidShortName: prop = item.GetShortName(); break; |
| case kpidIsDir: prop = item.IsDir(); break; |
| case kpidMTime: FatTimeToProp(item.MTime, 0, prop); break; |
| case kpidCTime: FatTimeToProp(item.CTime, item.CTime2, prop); break; |
| case kpidATime: FatTimeToProp(((UInt32)item.ADate << 16), 0, prop); break; |
| case kpidAttrib: prop = (UInt32)item.Attrib; break; |
| case kpidSize: if (!item.IsDir()) prop = item.Size; break; |
| case kpidPackSize: if (!item.IsDir()) prop = Header.GetFilePackSize(item.Size); 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]]; |
| if (!item.IsDir()) |
| totalSize += item.Size; |
| } |
| RINOK(extractCallback->SetTotal(totalSize)); |
| |
| UInt64 totalPackSize; |
| totalSize = totalPackSize = 0; |
| |
| NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); |
| CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec; |
| |
| CLocalProgress *lps = new CLocalProgress; |
| CMyComPtr<ICompressProgressInfo> progress = lps; |
| lps->Init(extractCallback, false); |
| |
| CDummyOutStream *outStreamSpec = new CDummyOutStream; |
| CMyComPtr<ISequentialOutStream> outStream(outStreamSpec); |
| |
| for (i = 0; i < numItems; i++) |
| { |
| lps->InSize = totalPackSize; |
| lps->OutSize = totalSize; |
| RINOK(lps->SetCur()); |
| CMyComPtr<ISequentialOutStream> realOutStream; |
| Int32 askMode = testMode ? |
| NExtract::NAskMode::kTest : |
| NExtract::NAskMode::kExtract; |
| Int32 index = allFilesMode ? i : indices[i]; |
| const CItem &item = Items[index]; |
| RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); |
| |
| if (item.IsDir()) |
| { |
| RINOK(extractCallback->PrepareOperation(askMode)); |
| RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); |
| continue; |
| } |
| |
| totalPackSize += Header.GetFilePackSize(item.Size); |
| totalSize += item.Size; |
| |
| if (!testMode && !realOutStream) |
| continue; |
| RINOK(extractCallback->PrepareOperation(askMode)); |
| |
| outStreamSpec->SetStream(realOutStream); |
| realOutStream.Release(); |
| outStreamSpec->Init(); |
| |
| int res = NExtract::NOperationResult::kDataError; |
| CMyComPtr<ISequentialInStream> inStream; |
| HRESULT hres = GetStream(index, &inStream); |
| if (hres != S_FALSE) |
| { |
| RINOK(hres); |
| if (inStream) |
| { |
| RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); |
| if (copyCoderSpec->TotalSize == item.Size) |
| res = NExtract::NOperationResult::kOK; |
| } |
| } |
| 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"FAT", L"fat img", 0, 0xDA, { 0x55, 0xAA }, 2, false, CreateArc, 0 }; |
| |
| REGISTER_ARC(Fat) |
| |
| }} |