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