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