Files
Aaru/DiscImageChef.Filesystems/FAT.cs
Natalia Portillo 0de296b512 * DiscImageChef.DiscImages/CDRDAO.cs:
Corrected typo on audio track matching.

	* DiscImageChef.DiscImages/CDRWin.cs:
	  Corrected detection of images with CD-Text.
	Do not output partitions for index 0.

	* DiscImageChef.DiscImages/CopyQM.cs:
	  Do not create debug image output.

	* DiscImageChef.DiscImages/Nero.cs:
	  Added type for any dvd seen on old Nero version.
	Corrected handling of images where pregap is not indicated
	  (nonetheless, Nero stores them).
	Corrected handling of track 1 (Lead-In is stored there).
	Corrected session count in discs with 1 session.
	Do not add partitions of index 0.
	Corrected partition start offset of disc start.
	Guess disc type for old Nero discs.
	Corrected output of Mode2 sectors stored in RAW mode.
	Do not throw exceptions on values that should be returned
	  empty or null if not supported by image format.

	* DiscImageChef.Filesystems/FFS.cs:
	* DiscImageChef.Filesystems/BFS.cs:
	* DiscImageChef.Filesystems/ODS.cs:
	* DiscImageChef.Filesystems/FAT.cs:
	* DiscImageChef.Filesystems/APFS.cs:
	* DiscImageChef.Filesystems/NTFS.cs:
	* DiscImageChef.Filesystems/SysV.cs:
	* DiscImageChef.Filesystems/HPFS.cs:
	* DiscImageChef.Filesystems/Opera.cs:
	* DiscImageChef.Filesystems/Acorn.cs:
	* DiscImageChef.Filesystems/extFS.cs:
	* DiscImageChef.Filesystems/BTRFS.cs:
	* DiscImageChef.Filesystems/ext2FS.cs:
	* DiscImageChef.Filesystems/ProDOS.cs:
	* DiscImageChef.Filesystems/SolarFS.cs:
	* DiscImageChef.Filesystems/UNIXBFS.cs:
	* DiscImageChef.Filesystems/ISO9660.cs:
	* DiscImageChef.Filesystems/MinixFS.cs:
	* DiscImageChef.Filesystems/AmigaDOS.cs:
	* DiscImageChef.Filesystems/PCEngine.cs:
	* DiscImageChef.Filesystems/AppleHFS.cs:
	* DiscImageChef.Filesystems/AppleHFSPlus.cs:
	* DiscImageChef.Filesystems/AppleMFS/Info.cs:
	  Do not try to read past partition end.

	* DiscImageChef/Commands/CreateSidecar.cs:
	  Added points for skipping whole image checksum on debugging.
	Track starts at index 0.
2016-08-08 18:44:08 +01:00

545 lines
25 KiB
C#

// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename : FAT.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Microsoft FAT filesystem plugin.
//
// --[ Description ] ----------------------------------------------------------
//
// Identifies the Microsoft FAT filesystem and shows information.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2016 Natalia Portillo
// ****************************************************************************/
using System;
using System.Text;
using System.Collections.Generic;
using DiscImageChef.Console;
namespace DiscImageChef.Filesystems
{
// TODO: Implement detecting DOS bootable disks
// TODO: Implement detecting Atari TOS bootable disks and printing corresponding fields
class FAT : Filesystem
{
public FAT()
{
Name = "Microsoft File Allocation Table";
PluginUUID = new Guid("33513B2C-0D26-0D2D-32C3-79D8611158E0");
}
public FAT(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
{
Name = "Microsoft File Allocation Table";
PluginUUID = new Guid("33513B2C-0D26-0D2D-32C3-79D8611158E0");
}
public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
{
if((2 + partitionStart) >= partitionEnd)
return false;
byte media_descriptor; // Not present on DOS <= 3, present on TOS but != of first FAT entry
byte fats_no; // Must be 1 or 2. Dunno if it can be 0 in the wild, but it CANNOT BE bigger than 2
byte[] fat32_signature = new byte[8]; // "FAT32 "
uint first_fat_entry; // No matter FAT size we read 4 bytes for checking
ushort bps, rsectors;
byte[] bpb_sector = imagePlugin.ReadSector(0 + partitionStart);
byte[] fat_sector = imagePlugin.ReadSector(1 + partitionStart);
bool bpb_found = true;
fats_no = bpb_sector[0x010]; // FATs, 1 or 2, maybe 0, never bigger
media_descriptor = bpb_sector[0x015]; // Media Descriptor if present is in 0x15
Array.Copy(bpb_sector, 0x52, fat32_signature, 0, 8); // FAT32 signature, if present, is in 0x52
bps = BitConverter.ToUInt16(bpb_sector, 0x00B); // Bytes per sector
if(bps == 0)
bps = 0x200;
rsectors = BitConverter.ToUInt16(bpb_sector, 0x00E); // Sectors between BPB and FAT, including the BPB sector => [BPB,FAT)
if(rsectors == 0)
rsectors = 1;
if(partitionEnd > (rsectors + partitionStart))
fat_sector = imagePlugin.ReadSector(rsectors + partitionStart); // First FAT entry
else
bpb_found = false;
if(bpb_found)
{
first_fat_entry = BitConverter.ToUInt32(fat_sector, 0); // Easier to manage
DicConsole.DebugWriteLine("FAT plugin", "fats_no = {0}", fats_no);
DicConsole.DebugWriteLine("FAT plugin", "media_descriptor = 0x{0:X2}", media_descriptor);
DicConsole.DebugWriteLine("FAT plugin", "fat32_signature = {0}", StringHandlers.CToString(fat32_signature));
DicConsole.DebugWriteLine("FAT plugin", "bps = {0}", bps);
DicConsole.DebugWriteLine("FAT plugin", "first_fat_entry = 0x{0:X8}", first_fat_entry);
if(fats_no > 2) // Must be 1 or 2, but as TOS makes strange things and I have not checked if it puts this to 0, ignore if 0. MUST NOT BE BIGGER THAN 2!
return false;
// Let's start the fun
if(Encoding.ASCII.GetString(fat32_signature) == "FAT32 ")
return true; // Seems easy, check reading
if((first_fat_entry & 0xFFFFFFF0) == 0xFFFFFFF0) // Seems to be FAT16
{
if((first_fat_entry & 0xFF) == media_descriptor)
return true; // It MUST be FAT16, or... maybe not :S
}
else if((first_fat_entry & 0x00FFFFF0) == 0x00FFFFF0)
{
//if((first_fat_entry & 0xFF) == media_descriptor) // Pre DOS<4 does not implement this, TOS does and is !=
return true; // It MUST be FAT12, or... maybe not :S
}
}
else
{
// This may create a lot of false positives, need to do extensive checkins...
fat_sector = imagePlugin.ReadSector(1 + partitionStart);
first_fat_entry = BitConverter.ToUInt32(fat_sector, 0);
byte fat_id = fat_sector[0];
if((first_fat_entry & 0x00FFFFF0) == 0x00FFFFF0)
{
if(fat_id == 0xFF)
{
if(imagePlugin.GetSectorSize() == 512 && imagePlugin.GetSectors() == 640)
return true;
if(imagePlugin.GetSectorSize() == 128)
{
if(imagePlugin.GetSectors() == 2002)
return true;
if(imagePlugin.GetSectors() == 4004)
return true;
}
if(imagePlugin.GetSectorSize() == 1024)
{
if(imagePlugin.GetSectors() == 616)
return true;
if(imagePlugin.GetSectors() == 1232)
return true;
}
return false;
}
if(fat_id == 0xFE)
{
if(imagePlugin.GetSectorSize() == 512 && imagePlugin.GetSectors() == 320)
return true;
if(imagePlugin.GetSectorSize() == 128)
{
if(imagePlugin.GetSectors() == 2002)
return true;
if(imagePlugin.GetSectors() == 4004)
return true;
}
if(imagePlugin.GetSectorSize() == 1024)
{
if(imagePlugin.GetSectors() == 616)
return true;
if(imagePlugin.GetSectors() == 1232)
return true;
}
return false;
}
if(fat_id == 0xFD && imagePlugin.GetSectors() == 2002)
return true;
}
}
return false;
}
public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information)
{
information = "";
StringBuilder sb = new StringBuilder();
xmlFSType = new Schemas.FileSystemType();
byte[] dosString; // Space-padded
bool isFAT32 = false;
uint first_fat_entry;
byte media_descriptor, fats_no;
string fat32_signature;
ushort bps, rsectors;
byte[] bpb_sector = imagePlugin.ReadSector(0 + partitionStart);
byte[] fat_sector = imagePlugin.ReadSector(1 + partitionStart);
bool bpb_found = true;
fats_no = bpb_sector[0x010]; // FATs, 1 or 2, maybe 0, never bigger
media_descriptor = bpb_sector[0x015]; // Media Descriptor if present is in 0x15
dosString = new byte[8];
Array.Copy(bpb_sector, 0x52, dosString, 0, 8); // FAT32 signature, if present, is in 0x52
fat32_signature = Encoding.ASCII.GetString(dosString);
bps = BitConverter.ToUInt16(bpb_sector, 0x00B); // Bytes per sector
if(bps == 0)
bps = 0x200;
rsectors = BitConverter.ToUInt16(bpb_sector, 0x00E); // Sectors between BPB and FAT, including the BPB sector => [BPB,FAT)
if(rsectors == 0)
rsectors = 1;
if(partitionEnd > (rsectors + partitionStart))
fat_sector = imagePlugin.ReadSector(rsectors + partitionStart); // First FAT entry
else
bpb_found = false;
if(bpb_found)
{
first_fat_entry = BitConverter.ToUInt32(fat_sector, 0); // Easier to manage
if(fats_no > 2) // Must be 1 or 2, but as TOS makes strange things and I have not checked if it puts this to 0, ignore if 0. MUST NOT BE BIGGER THAN 2!
return;
// Let's start the fun
if(fat32_signature == "FAT32 ")
{
sb.AppendLine("Microsoft FAT32"); // Seems easy, check reading
xmlFSType.Type = "FAT32";
isFAT32 = true;
}
else if((first_fat_entry & 0xFFFFFFF0) == 0xFFFFFFF0) // Seems to be FAT16
{
if((first_fat_entry & 0xFF) == media_descriptor)
{
sb.AppendLine("Microsoft FAT16"); // It MUST be FAT16, or... maybe not :S
xmlFSType.Type = "FAT16";
}
}
else if((first_fat_entry & 0x00FFFFF0) == 0x00FFFFF0)
{
//if((first_fat_entry & 0xFF) == media_descriptor) // Pre DOS<4 does not implement this, TOS does and is !=
sb.AppendLine("Microsoft FAT12"); // It MUST be FAT12, or... maybe not :S
xmlFSType.Type = "FAT12";
}
else
return;
BIOSParameterBlock BPB = new BIOSParameterBlock();
ExtendedParameterBlock EPB = new ExtendedParameterBlock();
FAT32ParameterBlock FAT32PB = new FAT32ParameterBlock();
dosString = new byte[8];
Array.Copy(bpb_sector, 0x03, dosString, 0, 8);
BPB.OEMName = StringHandlers.CToString(dosString);
BPB.bps = BitConverter.ToUInt16(bpb_sector, 0x0B);
BPB.spc = bpb_sector[0x0D];
BPB.rsectors = BitConverter.ToUInt16(bpb_sector, 0x0E);
BPB.fats_no = bpb_sector[0x10];
BPB.root_ent = BitConverter.ToUInt16(bpb_sector, 0x11);
BPB.sectors = BitConverter.ToUInt16(bpb_sector, 0x13);
BPB.media = bpb_sector[0x15];
BPB.spfat = BitConverter.ToUInt16(bpb_sector, 0x16);
BPB.sptrk = BitConverter.ToUInt16(bpb_sector, 0x18);
BPB.heads = BitConverter.ToUInt16(bpb_sector, 0x1A);
BPB.hsectors = BitConverter.ToUInt32(bpb_sector, 0x1C);
BPB.big_sectors = BitConverter.ToUInt32(bpb_sector, 0x20);
if(isFAT32)
{
FAT32PB.spfat = BitConverter.ToUInt32(bpb_sector, 0x24);
FAT32PB.fat_flags = BitConverter.ToUInt16(bpb_sector, 0x28);
FAT32PB.version = BitConverter.ToUInt16(bpb_sector, 0x2A);
FAT32PB.root_cluster = BitConverter.ToUInt32(bpb_sector, 0x2C);
FAT32PB.fsinfo_sector = BitConverter.ToUInt16(bpb_sector, 0x30);
FAT32PB.backup_sector = BitConverter.ToUInt16(bpb_sector, 0x32);
FAT32PB.drive_no = bpb_sector[0x40];
FAT32PB.nt_flags = bpb_sector[0x41];
FAT32PB.signature = bpb_sector[0x42];
FAT32PB.serial_no = BitConverter.ToUInt32(bpb_sector, 0x43);
dosString = new byte[11];
Array.Copy(bpb_sector, 0x47, dosString, 0, 11);
FAT32PB.volume_label = StringHandlers.CToString(dosString);
dosString = new byte[8];
Array.Copy(bpb_sector, 0x52, dosString, 0, 8);
FAT32PB.fs_type = StringHandlers.CToString(dosString);
}
else
{
EPB.drive_no = bpb_sector[0x24];
EPB.nt_flags = bpb_sector[0x25];
EPB.signature = bpb_sector[0x26];
EPB.serial_no = BitConverter.ToUInt32(bpb_sector, 0x27);
dosString = new byte[11];
Array.Copy(bpb_sector, 0x2B, dosString, 0, 11);
EPB.volume_label = StringHandlers.CToString(dosString);
dosString = new byte[8];
Array.Copy(bpb_sector, 0x36, dosString, 0, 8);
EPB.fs_type = StringHandlers.CToString(dosString);
}
sb.AppendFormat("OEM Name: {0}", BPB.OEMName).AppendLine();
sb.AppendFormat("{0} bytes per sector.", BPB.bps).AppendLine();
sb.AppendFormat("{0} sectors per cluster.", BPB.spc).AppendLine();
xmlFSType.ClusterSize = BPB.bps * BPB.spc;
sb.AppendFormat("{0} sectors reserved between BPB and FAT.", BPB.rsectors).AppendLine();
sb.AppendFormat("{0} FATs.", BPB.fats_no).AppendLine();
sb.AppendFormat("{0} entries on root directory.", BPB.root_ent).AppendLine();
if(BPB.sectors == 0)
{
sb.AppendFormat("{0} sectors on volume ({1} bytes).", BPB.big_sectors, BPB.big_sectors * BPB.bps).AppendLine();
xmlFSType.Clusters = BPB.big_sectors / BPB.spc;
}
else
{
sb.AppendFormat("{0} sectors on volume ({1} bytes).", BPB.sectors, BPB.sectors * BPB.bps).AppendLine();
xmlFSType.Clusters = BPB.sectors / BPB.spc;
}
if((BPB.media & 0xF0) == 0xF0)
sb.AppendFormat("Media format: 0x{0:X2}", BPB.media).AppendLine();
if(fat32_signature == "FAT32 ")
sb.AppendFormat("{0} sectors per FAT.", FAT32PB.spfat).AppendLine();
else
sb.AppendFormat("{0} sectors per FAT.", BPB.spfat).AppendLine();
sb.AppendFormat("{0} sectors per track.", BPB.sptrk).AppendLine();
sb.AppendFormat("{0} heads.", BPB.heads).AppendLine();
sb.AppendFormat("{0} hidden sectors before BPB.", BPB.hsectors).AppendLine();
if(isFAT32)
{
sb.AppendFormat("Cluster of root directory: {0}", FAT32PB.root_cluster).AppendLine();
sb.AppendFormat("Sector of FSINFO structure: {0}", FAT32PB.fsinfo_sector).AppendLine();
sb.AppendFormat("Sector of backup FAT32 parameter block: {0}", FAT32PB.backup_sector).AppendLine();
sb.AppendFormat("Drive number: 0x{0:X2}", FAT32PB.drive_no).AppendLine();
sb.AppendFormat("Volume Serial Number: 0x{0:X8}", FAT32PB.serial_no).AppendLine();
xmlFSType.VolumeSerial = string.Format("{0:X8}", FAT32PB.serial_no);
if((FAT32PB.nt_flags & 0x01) == 0x01)
{
sb.AppendLine("Volume should be checked on next mount.");
if((EPB.nt_flags & 0x02) == 0x02)
sb.AppendLine("Disk surface should be checked also.");
xmlFSType.Dirty = true;
}
sb.AppendFormat("Volume label: {0}", EPB.volume_label).AppendLine();
if(!string.IsNullOrEmpty(EPB.volume_label))
xmlFSType.VolumeName = EPB.volume_label;
sb.AppendFormat("Filesystem type: {0}", EPB.fs_type).AppendLine();
}
else if(EPB.signature == 0x28 || EPB.signature == 0x29)
{
sb.AppendFormat("Drive number: 0x{0:X2}", EPB.drive_no).AppendLine();
sb.AppendFormat("Volume Serial Number: 0x{0:X8}", EPB.serial_no).AppendLine();
xmlFSType.VolumeSerial = string.Format("{0:X8}", EPB.serial_no);
if(EPB.signature == 0x29)
{
if((EPB.nt_flags & 0x01) == 0x01)
{
sb.AppendLine("Volume should be checked on next mount.");
if((EPB.nt_flags & 0x02) == 0x02)
sb.AppendLine("Disk surface should be checked also.");
xmlFSType.Dirty = true;
}
sb.AppendFormat("Volume label: {0}", EPB.volume_label).AppendLine();
if(!string.IsNullOrEmpty(EPB.volume_label))
xmlFSType.VolumeName = EPB.volume_label;
sb.AppendFormat("Filesystem type: {0}", EPB.fs_type).AppendLine();
}
}
}
else
{
sb.AppendLine("Pre-DOS 2.0 Microsoft FAT12.");
sb.AppendLine("***WARNING***");
sb.AppendLine("This may be a false positive.");
sb.AppendFormat("Disk image identifies disk type as {0}.", imagePlugin.GetMediaType()).AppendLine();
}
information = sb.ToString();
}
/// <summary>FAT's BIOS Parameter Block.</summary>
public struct BIOSParameterBlock
{
/// <summary>0x03, OEM Name, 8 bytes, space-padded</summary>
public string OEMName;
/// <summary>0x0B, Bytes per sector</summary>
public ushort bps;
/// <summary>0x0D, Sectors per cluster</summary>
public byte spc;
/// <summary>0x0E, Reserved sectors between BPB and FAT</summary>
public ushort rsectors;
/// <summary>0x10, Number of FATs</summary>
public byte fats_no;
/// <summary>0x11, Number of entries on root directory</summary>
public ushort root_ent;
/// <summary>0x13, Sectors in volume</summary>
public ushort sectors;
/// <summary>0x15, Media descriptor</summary>
public byte media;
/// <summary>0x16, Sectors per FAT</summary>
public ushort spfat;
/// <summary>0x18, Sectors per track</summary>
public ushort sptrk;
/// <summary>0x1A, Heads</summary>
public ushort heads;
/// <summary>0x1C, Hidden sectors before BPB</summary>
public uint hsectors;
/// <summary>0x20, Sectors in volume if > 65535</summary>
public uint big_sectors;
}
/// <summary>
/// Atari Boot Block.
/// This only applies for bootable disks
/// From http://info-coach.fr/atari/software/FD-Soft.php
/// </summary>
public struct AtariBootBlock
{
/// <summary>0x01C, Atari ST use 16 bit for hidden sectors, probably so did old DOS</summary>
public ushort hsectors;
/// <summary>0x01E, indicates if COMMAND.PRG must be executed after OS load</summary>
public ushort xflag;
/// <summary>0x020, load mode for, or 0 if fname indicates boot file</summary>
public ushort ldmode;
/// <summary>0x022, sector from which to boot</summary>
public ushort bsect;
/// <summary>0x024, how many sectors to boot</summary>
public ushort bsects_no;
/// <summary>0x026, RAM address where boot should be located</summary>
public uint ldaddr;
/// <summary>0x02A, RAM address to copy the FAT and root directory</summary>
public uint fatbuf;
/// <summary>0x02E, 11 bytes, name of boot file</summary>
public string fname;
/// <summary>0x039, unused</summary>
public ushort reserved;
/// <summary>0x03B, 451 bytes boot code</summary>
public byte[] boot_code;
/// <summary>0x1FE, the sum of all the BPB+ABB must be 0x1234, so this bigendian value works as adjustment</summary>
public ushort checksum;
}
/// <summary>DOS Extended Parameter Block</summary>
public struct ExtendedParameterBlock
{
/// <summary>0x24, Drive number<summary>
public byte drive_no;
/// <summary>0x25, Volume flags if NT (must be 0x29 signature)<summary>
public byte nt_flags;
/// <summary>0x26, EPB signature, 0x28 or 0x29<summary>
public byte signature;
/// <summary>0x27, Volume serial number<summary>
public uint serial_no;
/// <summary>0x2B, Volume label, 11 bytes, space-padded
/// Present only if signature == 0x29<summary>
public string volume_label;
/// <summary>0x36, Filesystem type, 8 bytes, space-padded
/// Present only if signature == 0x29<summary>
public string fs_type;
}
/// <summary>FAT32 Parameter Block</summary>
public struct FAT32ParameterBlock
{
/// <summary>0x24, Sectors per FAT</summary>
public uint spfat;
/// <summary>0x28, FAT flags</summary>
public ushort fat_flags;
/// <summary>0x2A, FAT32 version</summary>
public ushort version;
/// <summary>0x2C, Cluster of root directory</summary>
public uint root_cluster;
/// <summary>0x30, Sector of FSINFO structure</summary>
public ushort fsinfo_sector;
/// <summary>0x32, Sector of FAT32PB backup</summary>
public ushort backup_sector;
/// <summary>0x34, 12 reserved bytes</summary>
public byte[] reserved;
/// <summary>0x40, Drive number</summary>
public byte drive_no;
/// <summary>0x41, Volume flags</summary>
public byte nt_flags;
/// <summary>0x42, FAT32PB signature, should be 0x29</summary>
public byte signature;
/// <summary>0x43, Volume serial number</summary>
public uint serial_no;
/// <summary>0x47, Volume label, 11 bytes, space-padded</summary>
public string volume_label;
/// <summary>0x52, Filesystem type, 8 bytes, space-padded, must be "FAT32 "</summary>
public string fs_type;
}
public override Errno Mount()
{
return Errno.NotImplemented;
}
public override Errno Mount(bool debug)
{
return Errno.NotImplemented;
}
public override Errno Unmount()
{
return Errno.NotImplemented;
}
public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock)
{
return Errno.NotImplemented;
}
public override Errno GetAttributes(string path, ref FileAttributes attributes)
{
return Errno.NotImplemented;
}
public override Errno ListXAttr(string path, ref List<string> xattrs)
{
return Errno.NotImplemented;
}
public override Errno GetXattr(string path, string xattr, ref byte[] buf)
{
return Errno.NotImplemented;
}
public override Errno Read(string path, long offset, long size, ref byte[] buf)
{
return Errno.NotImplemented;
}
public override Errno ReadDir(string path, ref List<string> contents)
{
return Errno.NotImplemented;
}
public override Errno StatFs(ref FileSystemInfo stat)
{
return Errno.NotImplemented;
}
public override Errno Stat(string path, ref FileEntryInfo stat)
{
return Errno.NotImplemented;
}
public override Errno ReadLink(string path, ref string dest)
{
return Errno.NotImplemented;
}
}
}