xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/FatHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // FatHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 // #include <stdio.h>
6 
7 #include "../../../C/CpuArch.h"
8 
9 #include "../../Common/ComTry.h"
10 #include "../../Common/IntToString.h"
11 #include "../../Common/MyBuffer.h"
12 #include "../../Common/MyCom.h"
13 #include "../../Common/StringConvert.h"
14 
15 #include "../../Windows/PropVariant.h"
16 #include "../../Windows/TimeUtils.h"
17 
18 #include "../Common/LimitedStreams.h"
19 #include "../Common/ProgressUtils.h"
20 #include "../Common/RegisterArc.h"
21 #include "../Common/StreamUtils.h"
22 
23 #include "../Compress/CopyCoder.h"
24 
25 #include "Common/DummyOutStream.h"
26 
27 #define Get16(p) GetUi16(p)
28 #define Get32(p) GetUi32(p)
29 #define Get16a(p) GetUi16a(p)
30 #define Get32a(p) GetUi32a(p)
31 
32 #define PRF(x) /* x */
33 
34 namespace NArchive {
35 namespace NFat {
36 
37 static const UInt32 kFatItemUsedByDirMask = (UInt32)1 << 31;
38 
39 struct CHeader
40 {
41   UInt32 NumSectors;
42   UInt16 NumReservedSectors;
43   Byte NumFats;
44   UInt32 NumFatSectors;
45   UInt32 RootDirSector;
46   UInt32 NumRootDirSectors;
47   UInt32 DataSector;
48 
49   UInt32 FatSize;
50   UInt32 BadCluster;
51 
52   Byte NumFatBits;
53   Byte SectorSizeLog;
54   Byte SectorsPerClusterLog;
55   Byte ClusterSizeLog;
56 
57   UInt16 SectorsPerTrack;
58   UInt16 NumHeads;
59   UInt32 NumHiddenSectors;
60 
61   bool VolFieldsDefined;
62   bool HeadersWarning;
63 
64   UInt32 VolId;
65   // Byte VolName[11];
66   // Byte FileSys[8];
67 
68   // Byte OemName[5];
69   Byte MediaType;
70 
71   // 32-bit FAT
72   UInt16 Flags;
73   UInt16 FsInfoSector;
74   UInt32 RootCluster;
75 
IsFat32NArchive::NFat::CHeader76   bool IsFat32() const { return NumFatBits == 32; }
GetPhySizeNArchive::NFat::CHeader77   UInt64 GetPhySize() const { return (UInt64)NumSectors << SectorSizeLog; }
SectorSizeNArchive::NFat::CHeader78   UInt32 SectorSize() const { return (UInt32)1 << SectorSizeLog; }
ClusterSizeNArchive::NFat::CHeader79   UInt32 ClusterSize() const { return (UInt32)1 << ClusterSizeLog; }
ClusterToSectorNArchive::NFat::CHeader80   UInt32 ClusterToSector(UInt32 c) const { return DataSector + ((c - 2) << SectorsPerClusterLog); }
IsEocNArchive::NFat::CHeader81   UInt32 IsEoc(UInt32 c) const { return c > BadCluster; }
IsEocAndUnusedNArchive::NFat::CHeader82   UInt32 IsEocAndUnused(UInt32 c) const { return c > BadCluster && (c & kFatItemUsedByDirMask) == 0; }
IsValidClusterNArchive::NFat::CHeader83   UInt32 IsValidCluster(UInt32 c) const { return c >= 2 && c < FatSize; }
SizeToSectorsNArchive::NFat::CHeader84   UInt32 SizeToSectors(UInt32 size) const { return (size + SectorSize() - 1) >> SectorSizeLog; }
CalcFatSizeInSectorsNArchive::NFat::CHeader85   UInt32 CalcFatSizeInSectors() const { return SizeToSectors((FatSize * (NumFatBits / 4) + 1) / 2); }
86 
GetFatSectorNArchive::NFat::CHeader87   UInt32 GetFatSector() const
88   {
89     UInt32 index = (IsFat32() && (Flags & 0x80) != 0) ? (Flags & 0xF) : 0;
90     if (index > NumFats)
91       index = 0;
92     return NumReservedSectors + index * NumFatSectors;
93   }
94 
GetFilePackSizeNArchive::NFat::CHeader95   UInt64 GetFilePackSize(UInt32 unpackSize) const
96   {
97     UInt64 mask = ClusterSize() - 1;
98     return (unpackSize + mask) & ~mask;
99   }
100 
GetNumClustersNArchive::NFat::CHeader101   UInt32 GetNumClusters(UInt32 size) const
102     { return (UInt32)(((UInt64)size + ClusterSize() - 1) >> ClusterSizeLog); }
103 
104   bool Parse(const Byte *p);
105 };
106 
GetLog(UInt32 num)107 static int GetLog(UInt32 num)
108 {
109   for (int i = 0; i < 31; i++)
110     if (((UInt32)1 << i) == num)
111       return i;
112   return -1;
113 }
114 
115 static const UInt32 kHeaderSize = 512;
116 
117 API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size);
IsArc_Fat(const Byte * p,size_t size)118 API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size)
119 {
120   if (size < kHeaderSize)
121     return k_IsArc_Res_NEED_MORE;
122   CHeader h;
123   return h.Parse(p) ? k_IsArc_Res_YES : k_IsArc_Res_NO;
124 }
125 
Parse(const Byte * p)126 bool CHeader::Parse(const Byte *p)
127 {
128   if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA)
129     return false;
130 
131   HeadersWarning = false;
132 
133   int codeOffset = 0;
134   switch (p[0])
135   {
136     case 0xE9: codeOffset = 3 + (Int16)Get16(p + 1); break;
137     case 0xEB: if (p[2] != 0x90) return false; codeOffset = 2 + (signed char)p[1]; break;
138     default: return false;
139   }
140   {
141     {
142       const UInt32 val32 = Get16(p + 11);
143       const int s = GetLog(val32);
144       if (s < 9 || s > 12)
145         return false;
146       SectorSizeLog = (Byte)s;
147     }
148     {
149       const UInt32 val32 = p[13];
150       const int s = GetLog(val32);
151       if (s < 0)
152         return false;
153       SectorsPerClusterLog = (Byte)s;
154     }
155     ClusterSizeLog = (Byte)(SectorSizeLog + SectorsPerClusterLog);
156     if (ClusterSizeLog > 24)
157       return false;
158   }
159 
160   NumReservedSectors = Get16(p + 14);
161   if (NumReservedSectors == 0)
162     return false;
163 
164   NumFats = p[16];
165   if (NumFats < 1 || NumFats > 4)
166     return false;
167 
168   // we also support images that contain 0 in offset field.
169   const bool isOkOffset = (codeOffset == 0)
170       || (codeOffset == (p[0] == 0xEB ? 2 : 3));
171 
172   const UInt16 numRootDirEntries = Get16(p + 17);
173   if (numRootDirEntries == 0)
174   {
175     if (codeOffset < 90 && !isOkOffset)
176       return false;
177     NumFatBits = 32;
178     NumRootDirSectors = 0;
179   }
180   else
181   {
182     // Some FAT12s don't contain VolFields
183     if (codeOffset < 62 - 24 && !isOkOffset)
184       return false;
185     NumFatBits = 0;
186     const UInt32 mask = (1 << (SectorSizeLog - 5)) - 1;
187     if ((numRootDirEntries & mask) != 0)
188       return false;
189     NumRootDirSectors = (numRootDirEntries + mask) >> (SectorSizeLog - 5);
190   }
191 
192   NumSectors = Get16(p + 19);
193   if (NumSectors == 0)
194     NumSectors = Get32(p + 32);
195   /*
196   // mkfs.fat could create fat32 image with 16-bit number of sectors.
197   // v23: we allow 16-bit value for number of sectors in fat32.
198   else if (IsFat32())
199     return false;
200   */
201 
202   MediaType = p[21];
203   NumFatSectors = Get16(p + 22);
204   SectorsPerTrack = Get16(p + 24);
205   NumHeads = Get16(p + 26);
206   NumHiddenSectors = Get32(p + 28);
207 
208   // memcpy(OemName, p + 3, 5);
209 
210   int curOffset = 36;
211   p += 36;
212   if (IsFat32())
213   {
214     if (NumFatSectors != 0)
215       return false;
216     NumFatSectors = Get32(p);
217     if (NumFatSectors >= (1 << 24))
218       return false;
219 
220     Flags = Get16(p + 4);
221     if (Get16(p + 6) != 0)
222       return false;
223     RootCluster = Get32(p + 8);
224     FsInfoSector = Get16(p + 12);
225     for (int i = 16; i < 28; i++)
226       if (p[i] != 0)
227         return false;
228     p += 28;
229     curOffset += 28;
230   }
231 
232   // DriveNumber = p[0];
233   VolFieldsDefined = false;
234   if (codeOffset >= curOffset + 3)
235   {
236     VolFieldsDefined = (p[2] == 0x29); // ExtendedBootSig
237     if (VolFieldsDefined)
238     {
239       if (codeOffset < curOffset + 26)
240         return false;
241       VolId = Get32(p + 3);
242       // memcpy(VolName, p + 7, 11);
243       // memcpy(FileSys, p + 18, 8);
244     }
245   }
246 
247   if (NumFatSectors == 0)
248     return false;
249   RootDirSector = NumReservedSectors + NumFatSectors * NumFats;
250   DataSector = RootDirSector + NumRootDirSectors;
251   if (NumSectors < DataSector)
252     return false;
253   const UInt32 numDataSectors = NumSectors - DataSector;
254   const UInt32 numClusters = numDataSectors >> SectorsPerClusterLog;
255 
256   BadCluster = 0x0FFFFFF7;
257   // v23: we support unusual (< 0xFFF5) numClusters values in fat32 systems
258   if (NumFatBits != 32)
259   {
260     if (numClusters >= 0xFFF5)
261       return false;
262     NumFatBits = (Byte)(numClusters < 0xFF5 ? 12 : 16);
263     BadCluster &= ((1 << NumFatBits) - 1);
264   }
265 
266   FatSize = numClusters + 2;
267   if (FatSize > BadCluster)
268     return false;
269   if (CalcFatSizeInSectors() > NumFatSectors)
270   {
271     /* some third-party program can create such FAT image, where
272        size of FAT table (NumFatSectors from headers) is smaller than
273        required value that is calculated from calculated (FatSize) value.
274        Another FAT unpackers probably ignore that error.
275        v23.02: we also ignore that error, and
276        we recalculate (FatSize) value from (NumFatSectors).
277        New (FatSize) will be smaller than original "full" (FatSize) value.
278        So we will have some unused clusters at the end of archive.
279     */
280     FatSize = (UInt32)(((UInt64)NumFatSectors << (3 + SectorSizeLog)) / NumFatBits);
281     HeadersWarning = true;
282   }
283   return true;
284 }
285 
286 struct CItem
287 {
288   UString UName;
289   char DosName[11];
290   Byte CTime2;
291   UInt32 CTime;
292   UInt32 MTime;
293   UInt16 ADate;
294   Byte Attrib;
295   Byte Flags;
296   UInt32 Size;
297   UInt32 Cluster;
298   Int32 Parent;
299 
300   // NT uses Flags to store Low Case status
NameIsLowNArchive::NFat::CItem301   bool NameIsLow() const { return (Flags & 0x8) != 0; }
ExtIsLowNArchive::NFat::CItem302   bool ExtIsLow() const { return (Flags & 0x10) != 0; }
IsDirNArchive::NFat::CItem303   bool IsDir() const { return (Attrib & 0x10) != 0; }
304   UString GetShortName() const;
305   UString GetName() const;
306   UString GetVolName() const;
307 };
308 
CopyAndTrim(char * dest,const char * src,unsigned size,bool toLower)309 static unsigned CopyAndTrim(char *dest, const char *src, unsigned size, bool toLower)
310 {
311   memcpy(dest, src, size);
312   if (toLower)
313   {
314     for (unsigned i = 0; i < size; i++)
315     {
316       char c = dest[i];
317       if (c >= 'A' && c <= 'Z')
318         dest[i] = (char)(c + 0x20);
319     }
320   }
321 
322   for (unsigned i = size;;)
323   {
324     if (i == 0)
325       return 0;
326     if (dest[i - 1] != ' ')
327       return i;
328     i--;
329   }
330 }
331 
FatStringToUnicode(const char * s)332 static UString FatStringToUnicode(const char *s)
333 {
334   return MultiByteToUnicodeString(s, CP_OEMCP);
335 }
336 
GetShortName() const337 UString CItem::GetShortName() const
338 {
339   char s[16];
340   unsigned i = CopyAndTrim(s, DosName, 8, NameIsLow());
341   s[i++] = '.';
342   unsigned j = CopyAndTrim(s + i, DosName + 8, 3, ExtIsLow());
343   if (j == 0)
344     i--;
345   s[i + j] = 0;
346   return FatStringToUnicode(s);
347 }
348 
GetName() const349 UString CItem::GetName() const
350 {
351   if (!UName.IsEmpty())
352     return UName;
353   return GetShortName();
354 }
355 
GetVolName() const356 UString CItem::GetVolName() const
357 {
358   if (!UName.IsEmpty())
359     return UName;
360   char s[12];
361   unsigned i = CopyAndTrim(s, DosName, 11, false);
362   s[i] = 0;
363   return FatStringToUnicode(s);
364 }
365 
366 struct CDatabase
367 {
368   CHeader Header;
369   CObjectVector<CItem> Items;
370   UInt32 *Fat;
371   CMyComPtr<IInStream> InStream;
372   IArchiveOpenCallback *OpenCallback;
373 
374   UInt32 NumFreeClusters;
375   bool VolItemDefined;
376   CItem VolItem;
377   UInt32 NumDirClusters;
378   CByteBuffer ByteBuf;
379   UInt64 NumCurUsedBytes;
380 
381   UInt64 PhySize;
382 
CDatabaseNArchive::NFat::CDatabase383   CDatabase(): Fat(NULL) {}
~CDatabaseNArchive::NFat::CDatabase384   ~CDatabase() { ClearAndClose(); }
385 
386   void Clear();
387   void ClearAndClose();
388   HRESULT OpenProgressFat(bool changeTotal = true);
389   HRESULT OpenProgress();
390 
391   UString GetItemPath(UInt32 index) const;
392   HRESULT Open();
393   HRESULT ReadDir(Int32 parent, UInt32 cluster, unsigned level);
394 
GetHeadersSizeNArchive::NFat::CDatabase395   UInt64 GetHeadersSize() const
396   {
397     return (UInt64)(Header.DataSector + (NumDirClusters << Header.SectorsPerClusterLog)) << Header.SectorSizeLog;
398   }
399   HRESULT SeekToSector(UInt32 sector);
SeekToClusterNArchive::NFat::CDatabase400   HRESULT SeekToCluster(UInt32 cluster) { return SeekToSector(Header.ClusterToSector(cluster)); }
401 };
402 
SeekToSector(UInt32 sector)403 HRESULT CDatabase::SeekToSector(UInt32 sector)
404 {
405   return InStream_SeekSet(InStream, (UInt64)sector << Header.SectorSizeLog);
406 }
407 
Clear()408 void CDatabase::Clear()
409 {
410   PhySize = 0;
411   VolItemDefined = false;
412   NumDirClusters = 0;
413   NumCurUsedBytes = 0;
414 
415   Items.Clear();
416   delete []Fat;
417   Fat = NULL;
418 }
419 
ClearAndClose()420 void CDatabase::ClearAndClose()
421 {
422   Clear();
423   InStream.Release();
424 }
425 
OpenProgressFat(bool changeTotal)426 HRESULT CDatabase::OpenProgressFat(bool changeTotal)
427 {
428   if (!OpenCallback)
429     return S_OK;
430   if (changeTotal)
431   {
432     const UInt64 numTotalBytes = (Header.CalcFatSizeInSectors() << Header.SectorSizeLog) +
433         ((UInt64)(Header.FatSize - NumFreeClusters) << Header.ClusterSizeLog);
434     RINOK(OpenCallback->SetTotal(NULL, &numTotalBytes))
435   }
436   return OpenCallback->SetCompleted(NULL, &NumCurUsedBytes);
437 }
438 
OpenProgress()439 HRESULT CDatabase::OpenProgress()
440 {
441   if (!OpenCallback)
442     return S_OK;
443   UInt64 numItems = Items.Size();
444   return OpenCallback->SetCompleted(&numItems, &NumCurUsedBytes);
445 }
446 
GetItemPath(UInt32 index) const447 UString CDatabase::GetItemPath(UInt32 index) const
448 {
449   const CItem *item = &Items[index];
450   UString name = item->GetName();
451   for (;;)
452   {
453     index = (UInt32)item->Parent;
454     if (item->Parent < 0)
455       return name;
456     item = &Items[index];
457     name.InsertAtFront(WCHAR_PATH_SEPARATOR);
458     if (item->UName.IsEmpty())
459       name.Insert(0, item->GetShortName());
460     else
461       name.Insert(0, item->UName);
462   }
463 }
464 
AddSubStringToName(wchar_t * dest,const Byte * p,unsigned numChars)465 static wchar_t *AddSubStringToName(wchar_t *dest, const Byte *p, unsigned numChars)
466 {
467   for (unsigned i = 0; i < numChars; i++)
468   {
469     wchar_t c = Get16(p + i * 2);
470     if (c != 0 && c != 0xFFFF)
471       *dest++ = c;
472   }
473   *dest = 0;
474   return dest;
475 }
476 
ReadDir(Int32 parent,UInt32 cluster,unsigned level)477 HRESULT CDatabase::ReadDir(Int32 parent, UInt32 cluster, unsigned level)
478 {
479   unsigned startIndex = Items.Size();
480   if (startIndex >= (1 << 30) || level > 256)
481     return S_FALSE;
482 
483   UInt32 sectorIndex = 0;
484   UInt32 blockSize = Header.ClusterSize();
485   bool clusterMode = (Header.IsFat32() || parent >= 0);
486   if (!clusterMode)
487   {
488     blockSize = Header.SectorSize();
489     RINOK(SeekToSector(Header.RootDirSector))
490   }
491 
492   ByteBuf.Alloc(blockSize);
493   UString curName;
494   int checkSum = -1;
495   int numLongRecords = -1;
496 
497   for (UInt32 pos = blockSize;; pos += 32)
498   {
499     if (pos == blockSize)
500     {
501       pos = 0;
502 
503       if ((NumDirClusters & 0xFF) == 0)
504       {
505         RINOK(OpenProgress())
506       }
507 
508       if (clusterMode)
509       {
510         if (Header.IsEoc(cluster))
511           break;
512         if (!Header.IsValidCluster(cluster))
513           return S_FALSE;
514         PRF(printf("\nCluster = %4X", cluster));
515         RINOK(SeekToCluster(cluster))
516         const UInt32 newCluster = Fat[cluster];
517         if ((newCluster & kFatItemUsedByDirMask) != 0)
518           return S_FALSE;
519         Fat[cluster] |= kFatItemUsedByDirMask;
520         cluster = newCluster;
521         NumDirClusters++;
522         NumCurUsedBytes += Header.ClusterSize();
523       }
524       else if (sectorIndex++ >= Header.NumRootDirSectors)
525         break;
526 
527       RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize))
528     }
529 
530     const Byte *p = ByteBuf + pos;
531 
532     if (p[0] == 0)
533     {
534       /*
535       // FreeDOS formats FAT partition with cluster chain longer than required.
536       if (clusterMode && !Header.IsEoc(cluster))
537         return S_FALSE;
538       */
539       break;
540     }
541 
542     if (p[0] == 0xE5)
543     {
544       if (numLongRecords > 0)
545         return S_FALSE;
546       continue;
547     }
548 
549     Byte attrib = p[11];
550     if ((attrib & 0x3F) == 0xF)
551     {
552       if (p[0] > 0x7F || Get16(p + 26) != 0)
553         return S_FALSE;
554       int longIndex = p[0] & 0x3F;
555       if (longIndex == 0)
556         return S_FALSE;
557       bool isLast = (p[0] & 0x40) != 0;
558       if (numLongRecords < 0)
559       {
560         if (!isLast)
561           return S_FALSE;
562         numLongRecords = longIndex;
563       }
564       else if (isLast || numLongRecords != longIndex)
565         return S_FALSE;
566 
567       numLongRecords--;
568 
569       if (p[12] == 0)
570       {
571         wchar_t nameBuf[14];
572         wchar_t *dest;
573 
574         dest = AddSubStringToName(nameBuf, p + 1, 5);
575         dest = AddSubStringToName(dest, p + 14, 6);
576         AddSubStringToName(dest, p + 28, 2);
577         curName = nameBuf + curName;
578         if (isLast)
579           checkSum = p[13];
580         if (checkSum != p[13])
581           return S_FALSE;
582       }
583     }
584     else
585     {
586       if (numLongRecords > 0)
587         return S_FALSE;
588       CItem item;
589       memcpy(item.DosName, p, 11);
590 
591       if (checkSum >= 0)
592       {
593         Byte sum = 0;
594         for (unsigned i = 0; i < 11; i++)
595           sum = (Byte)(((sum & 1) ? 0x80 : 0) + (sum >> 1) + (Byte)item.DosName[i]);
596         if (sum == checkSum)
597           item.UName = curName;
598       }
599 
600       if (item.DosName[0] == 5)
601         item.DosName[0] = (char)(Byte)0xE5;
602       item.Attrib = attrib;
603       item.Flags = p[12];
604       item.Size = Get32(p + 28);
605       item.Cluster = Get16(p + 26);
606       if (Header.NumFatBits > 16)
607         item.Cluster |= ((UInt32)Get16(p + 20) << 16);
608       else
609       {
610         // OS/2 and WinNT probably can store EA (extended atributes) in that field.
611       }
612 
613       item.CTime = Get32(p + 14);
614       item.CTime2 = p[13];
615       item.ADate = Get16(p + 18);
616       item.MTime = Get32(p + 22);
617       item.Parent = parent;
618 
619       if (attrib == 8)
620       {
621         VolItem = item;
622         VolItemDefined = true;
623       }
624       else
625         if (memcmp(item.DosName, ".          ", 11) != 0 &&
626             memcmp(item.DosName, "..         ", 11) != 0)
627       {
628         if (!item.IsDir())
629           NumCurUsedBytes += Header.GetFilePackSize(item.Size);
630         Items.Add(item);
631         PRF(printf("\n%7d: %S", Items.Size(), GetItemPath(Items.Size() - 1)));
632       }
633       numLongRecords = -1;
634       curName.Empty();
635       checkSum = -1;
636     }
637   }
638 
639   unsigned finishIndex = Items.Size();
640   for (unsigned i = startIndex; i < finishIndex; i++)
641   {
642     const CItem &item = Items[i];
643     if (item.IsDir())
644     {
645       PRF(printf("\n%S", GetItemPath(i)));
646       RINOK(CDatabase::ReadDir((int)i, item.Cluster, level + 1))
647     }
648   }
649   return S_OK;
650 }
651 
Open()652 HRESULT CDatabase::Open()
653 {
654   Clear();
655   bool numFreeClustersDefined = false;
656   {
657     Byte buf[kHeaderSize];
658     RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize))
659     if (!Header.Parse(buf))
660       return S_FALSE;
661     UInt64 fileSize;
662     RINOK(InStream_GetSize_SeekToEnd(InStream, fileSize))
663 
664     /* we comment that check to support truncated images */
665     /*
666     if (fileSize < Header.GetPhySize())
667       return S_FALSE;
668     */
669 
670     if (Header.IsFat32())
671     {
672       if (((UInt32)Header.FsInfoSector << Header.SectorSizeLog) + kHeaderSize <= fileSize
673           && SeekToSector(Header.FsInfoSector) == S_OK
674           && ReadStream_FALSE(InStream, buf, kHeaderSize) == S_OK
675           && 0xaa550000 == Get32(buf + 508)
676           && 0x41615252 == Get32(buf)
677           && 0x61417272 == Get32(buf + 484))
678       {
679         NumFreeClusters = Get32(buf + 488);
680         numFreeClustersDefined = (NumFreeClusters <= Header.FatSize);
681       }
682       else
683         Header.HeadersWarning = true;
684     }
685   }
686 
687   // numFreeClustersDefined = false; // to recalculate NumFreeClusters
688   if (!numFreeClustersDefined)
689     NumFreeClusters = 0;
690 
691   CByteBuffer byteBuf;
692   Fat = new UInt32[Header.FatSize];
693 
694   RINOK(OpenProgressFat())
695   RINOK(SeekToSector(Header.GetFatSector()))
696   if (Header.NumFatBits == 32)
697   {
698     const UInt32 kBufSize = (1 << 15);
699     byteBuf.Alloc(kBufSize);
700     for (UInt32 i = 0;;)
701     {
702       UInt32 size = Header.FatSize - i;
703       if (size == 0)
704         break;
705       const UInt32 kBufSize32 = kBufSize / 4;
706       if (size > kBufSize32)
707         size = kBufSize32;
708       const UInt32 readSize = Header.SizeToSectors(size * 4) << Header.SectorSizeLog;
709       RINOK(ReadStream_FALSE(InStream, byteBuf, readSize))
710       NumCurUsedBytes += readSize;
711 
712       const UInt32 *src = (const UInt32 *)(const void *)(const Byte *)byteBuf;
713       UInt32 *dest = Fat + i;
714       const UInt32 *srcLim = src + size;
715       if (numFreeClustersDefined)
716         do
717           *dest++ = Get32a(src) & 0x0FFFFFFF;
718         while (++src != srcLim);
719       else
720       {
721         UInt32 numFreeClusters = 0;
722         do
723         {
724           const UInt32 v = Get32a(src) & 0x0FFFFFFF;
725           *dest++ = v;
726           numFreeClusters += (UInt32)(v - 1) >> 31;
727         }
728         while (++src != srcLim);
729         NumFreeClusters += numFreeClusters;
730       }
731       i += size;
732       if ((i & 0xFFFFF) == 0)
733       {
734         RINOK(OpenProgressFat(!numFreeClustersDefined))
735       }
736     }
737   }
738   else
739   {
740     const UInt32 kBufSize = (UInt32)Header.CalcFatSizeInSectors() << Header.SectorSizeLog;
741     NumCurUsedBytes += kBufSize;
742     byteBuf.Alloc(kBufSize);
743     Byte *p = byteBuf;
744     RINOK(ReadStream_FALSE(InStream, p, kBufSize))
745     const UInt32 fatSize = Header.FatSize;
746     UInt32 *fat = &Fat[0];
747     if (Header.NumFatBits == 16)
748       for (UInt32 j = 0; j < fatSize; j++)
749         fat[j] = Get16a(p + j * 2);
750     else
751       for (UInt32 j = 0; j < fatSize; j++)
752         fat[j] = (Get16(p + j * 3 / 2) >> ((j & 1) << 2)) & 0xFFF;
753 
754     if (!numFreeClustersDefined)
755     {
756       UInt32 numFreeClusters = 0;
757       for (UInt32 i = 0; i < fatSize; i++)
758         numFreeClusters += (UInt32)(fat[i] - 1) >> 31;
759       NumFreeClusters = numFreeClusters;
760     }
761   }
762 
763   RINOK(OpenProgressFat())
764 
765   if ((Fat[0] & 0xFF) != Header.MediaType)
766   {
767     // that case can mean error in FAT,
768     // but xdf file: (MediaType == 0xF0 && Fat[0] == 0xFF9)
769     // 19.00: so we use non-strict check
770     if ((Fat[0] & 0xFF) < 0xF0)
771       return S_FALSE;
772   }
773 
774   RINOK(ReadDir(-1, Header.RootCluster, 0))
775 
776   PhySize = Header.GetPhySize();
777   return S_OK;
778 }
779 
780 
781 
782 Z7_class_CHandler_final:
783   public IInArchive,
784   public IInArchiveGetStream,
785   public CMyUnknownImp,
786   CDatabase
787 {
788   Z7_IFACES_IMP_UNK_2(IInArchive, IInArchiveGetStream)
789 };
790 
791 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
792 {
793   COM_TRY_BEGIN
794   *stream = NULL;
795   const CItem &item = Items[index];
796   CClusterInStream *streamSpec = new CClusterInStream;
797   CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
798   streamSpec->Stream = InStream;
799   streamSpec->StartOffset = Header.DataSector << Header.SectorSizeLog;
800   streamSpec->BlockSizeLog = Header.ClusterSizeLog;
801   streamSpec->Size = item.Size;
802 
803   const UInt32 numClusters = Header.GetNumClusters(item.Size);
804   streamSpec->Vector.ClearAndReserve(numClusters);
805   UInt32 cluster = item.Cluster;
806   UInt32 size = item.Size;
807 
808   if (size == 0)
809   {
810     if (cluster != 0)
811       return S_FALSE;
812   }
813   else
814   {
815     const UInt32 clusterSize = Header.ClusterSize();
816     for (;; size -= clusterSize)
817     {
818       if (!Header.IsValidCluster(cluster))
819         return S_FALSE;
820       streamSpec->Vector.AddInReserved(cluster - 2);
821       cluster = Fat[cluster];
822       if (size <= clusterSize)
823         break;
824     }
825     if (!Header.IsEocAndUnused(cluster))
826       return S_FALSE;
827   }
828   RINOK(streamSpec->InitAndSeek())
829   *stream = streamTemp.Detach();
830   return S_OK;
831   COM_TRY_END
832 }
833 
834 static const Byte kProps[] =
835 {
836   kpidPath,
837   kpidIsDir,
838   kpidSize,
839   kpidPackSize,
840   kpidMTime,
841   kpidCTime,
842   kpidATime,
843   kpidAttrib,
844   kpidShortName
845 };
846 
847 enum
848 {
849   kpidNumFats = kpidUserDefined
850   // kpidOemName,
851   // kpidVolName,
852   // kpidFileSysType
853 };
854 
855 static const CStatProp kArcProps[] =
856 {
857   { NULL, kpidFileSystem, VT_BSTR},
858   { NULL, kpidClusterSize, VT_UI4},
859   { NULL, kpidFreeSpace, VT_UI8},
860   { NULL, kpidHeadersSize, VT_UI8},
861   { NULL, kpidMTime, VT_FILETIME},
862   { NULL, kpidVolumeName, VT_BSTR},
863 
864   { "FATs", kpidNumFats, VT_UI4},
865   { NULL, kpidSectorSize, VT_UI4},
866   { NULL, kpidId, VT_UI4},
867   // { "OEM Name", kpidOemName, VT_BSTR},
868   // { "Volume Name", kpidVolName, VT_BSTR},
869   // { "File System Type", kpidFileSysType, VT_BSTR}
870   // { NULL, kpidSectorsPerTrack, VT_UI4},
871   // { NULL, kpidNumHeads, VT_UI4},
872   // { NULL, kpidHiddenSectors, VT_UI4}
873 };
874 
875 IMP_IInArchive_Props
876 IMP_IInArchive_ArcProps_WITH_NAME
877 
878 
879 static void FatTimeToProp(UInt32 dosTime, UInt32 ms10, NWindows::NCOM::CPropVariant &prop)
880 {
881   FILETIME localFileTime, utc;
882   if (NWindows::NTime::DosTime_To_FileTime(dosTime, localFileTime))
883     if (LocalFileTimeToFileTime(&localFileTime, &utc))
884     {
885       UInt64 t64 = (((UInt64)utc.dwHighDateTime) << 32) + utc.dwLowDateTime;
886       t64 += ms10 * 100000;
887       utc.dwLowDateTime = (DWORD)t64;
888       utc.dwHighDateTime = (DWORD)(t64 >> 32);
889       prop.SetAsTimeFrom_FT_Prec(utc, k_PropVar_TimePrec_Base + 2);
890     }
891 }
892 
893 /*
894 static void StringToProp(const Byte *src, unsigned size, NWindows::NCOM::CPropVariant &prop)
895 {
896   char dest[32];
897   memcpy(dest, src, size);
898   dest[size] = 0;
899   prop = FatStringToUnicode(dest);
900 }
901 
902 #define STRING_TO_PROP(s, p) StringToProp(s, sizeof(s), prop)
903 */
904 
905 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
906 {
907   COM_TRY_BEGIN
908   NWindows::NCOM::CPropVariant prop;
909   switch (propID)
910   {
911     case kpidFileSystem:
912     {
913       char s[16];
914       s[0] = 'F';
915       s[1] = 'A';
916       s[2] = 'T';
917       ConvertUInt32ToString(Header.NumFatBits, s + 3);
918       prop = s;
919       break;
920     }
921     case kpidClusterSize: prop = Header.ClusterSize(); break;
922     case kpidPhySize: prop = PhySize; break;
923     case kpidFreeSpace: prop = (UInt64)NumFreeClusters << Header.ClusterSizeLog; break;
924     case kpidHeadersSize: prop = GetHeadersSize(); break;
925     case kpidMTime: if (VolItemDefined) PropVariant_SetFrom_DosTime(prop, VolItem.MTime); break;
926     case kpidShortComment:
927     case kpidVolumeName: if (VolItemDefined) prop = VolItem.GetVolName(); break;
928     case kpidNumFats: if (Header.NumFats != 2) prop = Header.NumFats; break;
929     case kpidSectorSize: prop = (UInt32)1 << Header.SectorSizeLog; break;
930     // case kpidSectorsPerTrack: prop = Header.SectorsPerTrack; break;
931     // case kpidNumHeads: prop = Header.NumHeads; break;
932     // case kpidOemName: STRING_TO_PROP(Header.OemName, prop); break;
933     case kpidId: if (Header.VolFieldsDefined) prop = Header.VolId; break;
934     // case kpidVolName: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.VolName, prop); break;
935     // case kpidFileSysType: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.FileSys, prop); break;
936     // case kpidHiddenSectors: prop = Header.NumHiddenSectors; break;
937     case kpidWarningFlags:
938     {
939       UInt32 v = 0;
940       if (Header.HeadersWarning) v |= kpv_ErrorFlags_HeadersError;
941       if (v != 0)
942         prop = v;
943       break;
944     }
945   }
946   prop.Detach(value);
947   return S_OK;
948   COM_TRY_END
949 }
950 
951 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
952 {
953   COM_TRY_BEGIN
954   NWindows::NCOM::CPropVariant prop;
955   const CItem &item = Items[index];
956   switch (propID)
957   {
958     case kpidPath: prop = GetItemPath(index); break;
959     case kpidShortName: prop = item.GetShortName(); break;
960     case kpidIsDir: prop = item.IsDir(); break;
961     case kpidMTime: PropVariant_SetFrom_DosTime(prop, item.MTime); break;
962     case kpidCTime: FatTimeToProp(item.CTime, item.CTime2, prop); break;
963     case kpidATime: PropVariant_SetFrom_DosTime(prop, ((UInt32)item.ADate << 16)); break;
964     case kpidAttrib: prop = (UInt32)item.Attrib; break;
965     case kpidSize: if (!item.IsDir()) prop = item.Size; break;
966     case kpidPackSize: if (!item.IsDir()) prop = Header.GetFilePackSize(item.Size); break;
967   }
968   prop.Detach(value);
969   return S_OK;
970   COM_TRY_END
971 }
972 
973 Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback))
974 {
975   COM_TRY_BEGIN
976   {
977     OpenCallback = callback;
978     InStream = stream;
979     HRESULT res;
980     try
981     {
982       res = CDatabase::Open();
983       if (res == S_OK)
984         return S_OK;
985     }
986     catch(...)
987     {
988       Close();
989       throw;
990     }
991     Close();
992     return res;
993   }
994   COM_TRY_END
995 }
996 
997 Z7_COM7F_IMF(CHandler::Close())
998 {
999   ClearAndClose();
1000   return S_OK;
1001 }
1002 
1003 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1004     Int32 testMode, IArchiveExtractCallback *extractCallback))
1005 {
1006   COM_TRY_BEGIN
1007   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1008   if (allFilesMode)
1009     numItems = Items.Size();
1010   if (numItems == 0)
1011     return S_OK;
1012   UInt32 i;
1013   UInt64 totalSize = 0;
1014   for (i = 0; i < numItems; i++)
1015   {
1016     const CItem &item = Items[allFilesMode ? i : indices[i]];
1017     if (!item.IsDir())
1018       totalSize += item.Size;
1019   }
1020   RINOK(extractCallback->SetTotal(totalSize))
1021 
1022   UInt64 totalPackSize;
1023   totalSize = totalPackSize = 0;
1024 
1025   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
1026   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
1027 
1028   CLocalProgress *lps = new CLocalProgress;
1029   CMyComPtr<ICompressProgressInfo> progress = lps;
1030   lps->Init(extractCallback, false);
1031 
1032   CDummyOutStream *outStreamSpec = new CDummyOutStream;
1033   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
1034 
1035   for (i = 0;; i++)
1036   {
1037     lps->InSize = totalPackSize;
1038     lps->OutSize = totalSize;
1039     RINOK(lps->SetCur())
1040     if (i == numItems)
1041       break;
1042     CMyComPtr<ISequentialOutStream> realOutStream;
1043     const Int32 askMode = testMode ?
1044         NExtract::NAskMode::kTest :
1045         NExtract::NAskMode::kExtract;
1046     const UInt32 index = allFilesMode ? i : indices[i];
1047     const CItem &item = Items[index];
1048     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
1049 
1050     if (item.IsDir())
1051     {
1052       RINOK(extractCallback->PrepareOperation(askMode))
1053       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
1054       continue;
1055     }
1056 
1057     totalPackSize += Header.GetFilePackSize(item.Size);
1058     totalSize += item.Size;
1059 
1060     if (!testMode && !realOutStream)
1061       continue;
1062     RINOK(extractCallback->PrepareOperation(askMode))
1063 
1064     outStreamSpec->SetStream(realOutStream);
1065     realOutStream.Release();
1066     outStreamSpec->Init();
1067 
1068     int res = NExtract::NOperationResult::kDataError;
1069     CMyComPtr<ISequentialInStream> inStream;
1070     HRESULT hres = GetStream(index, &inStream);
1071     if (hres != S_FALSE)
1072     {
1073       RINOK(hres)
1074       if (inStream)
1075       {
1076         RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress))
1077         if (copyCoderSpec->TotalSize == item.Size)
1078           res = NExtract::NOperationResult::kOK;
1079       }
1080     }
1081     outStreamSpec->ReleaseStream();
1082     RINOK(extractCallback->SetOperationResult(res))
1083   }
1084   return S_OK;
1085   COM_TRY_END
1086 }
1087 
1088 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
1089 {
1090   *numItems = Items.Size();
1091   return S_OK;
1092 }
1093 
1094 static const Byte k_Signature[] = { 0x55, 0xAA };
1095 
1096 REGISTER_ARC_I(
1097   "FAT", "fat img", NULL, 0xDA,
1098   k_Signature,
1099   0x1FE,
1100   0,
1101   IsArc_Fat)
1102 
1103 }}
1104