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