// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : FAT.cs // Author(s) : Natalia Portillo // // 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 . // // ---------------------------------------------------------------------------- // Copyright © 2011-2018 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using DiscImageChef.Checksums; using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.DiscImages; using DiscImageChef.Helpers; using Schemas; namespace DiscImageChef.Filesystems { // TODO: Differentiate between Atari and X68k FAT, as this one uses a standard BPB. // X68K uses cdate/adate from direntry for extending filename public class FAT : Filesystem { public FAT() { Name = "Microsoft File Allocation Table"; PluginUUID = new Guid("33513B2C-0D26-0D2D-32C3-79D8611158E0"); CurrentEncoding = Encoding.GetEncoding("IBM437"); } public FAT(Encoding encoding) { Name = "Microsoft File Allocation Table"; PluginUUID = new Guid("33513B2C-0D26-0D2D-32C3-79D8611158E0"); if(encoding == null) CurrentEncoding = Encoding.GetEncoding("IBM437"); else CurrentEncoding = encoding; } public FAT(ImagePlugin imagePlugin, Partition partition, Encoding encoding) { Name = "Microsoft File Allocation Table"; PluginUUID = new Guid("33513B2C-0D26-0D2D-32C3-79D8611158E0"); if(encoding == null) CurrentEncoding = Encoding.GetEncoding("IBM437"); else CurrentEncoding = encoding; } public override bool Identify(ImagePlugin imagePlugin, Partition partition) { if(2 + partition.Start >= partition.End) return false; ushort bps; byte spc; byte fats_no; ushort reserved_secs; ushort root_entries; ushort sectors; byte media_descriptor; ushort fat_sectors; uint big_sectors; byte bpb_signature; byte fat32_signature; ulong huge_sectors; byte[] fat32_id = new byte[8]; byte[] msx_id = new byte[6]; byte fat_id; byte[] dos_oem = new byte[8]; byte[] atari_oem = new byte[6]; ushort bootable = 0; byte[] bpb_sector = imagePlugin.ReadSector(0 + partition.Start); byte[] fat_sector = imagePlugin.ReadSector(1 + partition.Start); Array.Copy(bpb_sector, 0x02, atari_oem, 0, 6); Array.Copy(bpb_sector, 0x03, dos_oem, 0, 8); bps = BitConverter.ToUInt16(bpb_sector, 0x00B); spc = bpb_sector[0x00D]; reserved_secs = BitConverter.ToUInt16(bpb_sector, 0x00E); fats_no = bpb_sector[0x010]; root_entries = BitConverter.ToUInt16(bpb_sector, 0x011); sectors = BitConverter.ToUInt16(bpb_sector, 0x013); media_descriptor = bpb_sector[0x015]; fat_sectors = BitConverter.ToUInt16(bpb_sector, 0x016); Array.Copy(bpb_sector, 0x052, msx_id, 0, 6); big_sectors = BitConverter.ToUInt32(bpb_sector, 0x020); bpb_signature = bpb_sector[0x026]; fat32_signature = bpb_sector[0x042]; Array.Copy(bpb_sector, 0x052, fat32_id, 0, 8); huge_sectors = BitConverter.ToUInt64(bpb_sector, 0x052); fat_id = fat_sector[0]; int bits_in_bps = CountBits.Count(bps); if(imagePlugin.ImageInfo.SectorSize >= 512) bootable = BitConverter.ToUInt16(bpb_sector, 0x1FE); bool correct_spc = spc == 1 || spc == 2 || spc == 4 || spc == 8 || spc == 16 || spc == 32 || spc == 64; string msx_string = Encoding.ASCII.GetString(msx_id); string fat32_string = Encoding.ASCII.GetString(fat32_id); bool atari_oem_correct = atari_oem[0] >= 0x20 && atari_oem[1] >= 0x20 && atari_oem[2] >= 0x20 && atari_oem[3] >= 0x20 && atari_oem[4] >= 0x20 && atari_oem[5] >= 0x20; bool dos_oem_correct = dos_oem[0] >= 0x20 && dos_oem[1] >= 0x20 && dos_oem[2] >= 0x20 && dos_oem[3] >= 0x20 && dos_oem[4] >= 0x20 && dos_oem[5] >= 0x20 && dos_oem[6] >= 0x20 && dos_oem[7] >= 0x20; string atari_string = Encoding.ASCII.GetString(atari_oem); string oem_string = Encoding.ASCII.GetString(dos_oem); DicConsole.DebugWriteLine("FAT plugin", "atari_oem_correct = {0}", atari_oem_correct); DicConsole.DebugWriteLine("FAT plugin", "dos_oem_correct = {0}", dos_oem_correct); DicConsole.DebugWriteLine("FAT plugin", "bps = {0}", bps); DicConsole.DebugWriteLine("FAT plugin", "bits in bps = {0}", bits_in_bps); DicConsole.DebugWriteLine("FAT plugin", "spc = {0}", spc); DicConsole.DebugWriteLine("FAT plugin", "correct_spc = {0}", correct_spc); DicConsole.DebugWriteLine("FAT plugin", "reserved_secs = {0}", reserved_secs); DicConsole.DebugWriteLine("FAT plugin", "fats_no = {0}", fats_no); DicConsole.DebugWriteLine("FAT plugin", "root_entries = {0}", root_entries); DicConsole.DebugWriteLine("FAT plugin", "sectors = {0}", sectors); DicConsole.DebugWriteLine("FAT plugin", "media_descriptor = 0x{0:X2}", media_descriptor); DicConsole.DebugWriteLine("FAT plugin", "fat_sectors = {0}", fat_sectors); DicConsole.DebugWriteLine("FAT plugin", "msx_id = \"{0}\"", msx_string); DicConsole.DebugWriteLine("FAT plugin", "big_sectors = {0}", big_sectors); DicConsole.DebugWriteLine("FAT plugin", "bpb_signature = 0x{0:X2}", bpb_signature); DicConsole.DebugWriteLine("FAT plugin", "fat32_signature = 0x{0:X2}", fat32_signature); DicConsole.DebugWriteLine("FAT plugin", "fat32_id = \"{0}\"", fat32_string); DicConsole.DebugWriteLine("FAT plugin", "huge_sectors = {0}", huge_sectors); DicConsole.DebugWriteLine("FAT plugin", "fat_id = 0x{0:X2}", fat_id); ushort apricot_bps = BitConverter.ToUInt16(bpb_sector, 0x50); byte apricot_spc = bpb_sector[0x52]; ushort apricot_reserved_secs = BitConverter.ToUInt16(bpb_sector, 0x53); byte apricot_fats_no = bpb_sector[0x55]; ushort apricot_root_entries = BitConverter.ToUInt16(bpb_sector, 0x56); ushort apricot_sectors = BitConverter.ToUInt16(bpb_sector, 0x58); byte apricot_media_descriptor = bpb_sector[0x5A]; ushort apricot_fat_sectors = BitConverter.ToUInt16(bpb_sector, 0x5B); bool apricot_correct_spc = apricot_spc == 1 || apricot_spc == 2 || apricot_spc == 4 || apricot_spc == 8 || apricot_spc == 16 || apricot_spc == 32 || apricot_spc == 64; int bits_in_apricot_bps = CountBits.Count(apricot_bps); byte apricot_partitions = bpb_sector[0x0C]; DicConsole.DebugWriteLine("FAT plugin", "apricot_bps = {0}", apricot_bps); DicConsole.DebugWriteLine("FAT plugin", "apricot_spc = {0}", apricot_spc); DicConsole.DebugWriteLine("FAT plugin", "apricot_correct_spc = {0}", apricot_correct_spc); DicConsole.DebugWriteLine("FAT plugin", "apricot_reserved_secs = {0}", apricot_reserved_secs); DicConsole.DebugWriteLine("FAT plugin", "apricot_fats_no = {0}", apricot_fats_no); DicConsole.DebugWriteLine("FAT plugin", "apricot_root_entries = {0}", apricot_root_entries); DicConsole.DebugWriteLine("FAT plugin", "apricot_sectors = {0}", apricot_sectors); DicConsole.DebugWriteLine("FAT plugin", "apricot_media_descriptor = 0x{0:X2}", apricot_media_descriptor); DicConsole.DebugWriteLine("FAT plugin", "apricot_fat_sectors = {0}", apricot_fat_sectors); // This is to support FAT partitions on hybrid ISO/USB images if(imagePlugin.ImageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { sectors /= 4; big_sectors /= 4; huge_sectors /= 4; } switch(oem_string) { // exFAT case "EXFAT ": return false; // NTFS case "NTFS " when bootable == 0xAA55 && fats_no == 0 && fat_sectors == 0: return false; // QNX4 case "FQNX4FS ": return false; } // HPFS if(16 + partition.Start <= partition.End) { uint hpfs_magic1, hpfs_magic2; byte[] hpfs_sb_sector = imagePlugin.ReadSector(16 + partition.Start); // Seek to superblock, on logical sector 16 hpfs_magic1 = BitConverter.ToUInt32(hpfs_sb_sector, 0x000); hpfs_magic2 = BitConverter.ToUInt32(hpfs_sb_sector, 0x004); if(hpfs_magic1 == 0xF995E849 && hpfs_magic2 == 0xFA53E9C5) return false; } switch(bits_in_bps) { // FAT32 for sure case 1 when correct_spc && fats_no <= 2 && sectors == 0 && fat_sectors == 0 && fat32_signature == 0x29 && fat32_string == "FAT32 ": return true; // short FAT32 case 1 when correct_spc && fats_no <= 2 && sectors == 0 && fat_sectors == 0 && fat32_signature == 0x28: return big_sectors == 0 ? huge_sectors <= partition.End - partition.Start + 1 : big_sectors <= partition.End - partition.Start + 1; // MSX-DOS FAT12 case 1 when correct_spc && fats_no <= 2 && root_entries > 0 && sectors <= partition.End - partition.Start + 1 && fat_sectors > 0 && msx_string == "VOL_ID": return true; // EBPB case 1 when correct_spc && fats_no <= 2 && root_entries > 0 && fat_sectors > 0 && (bpb_signature == 0x28 || bpb_signature == 0x29): return sectors == 0 ? big_sectors <= partition.End - partition.Start + 1 : sectors <= partition.End - partition.Start + 1; // BPB case 1 when correct_spc && reserved_secs < partition.End - partition.Start && fats_no <= 2 && root_entries > 0 && fat_sectors > 0: return sectors == 0 ? big_sectors <= partition.End - partition.Start + 1 : sectors <= partition.End - partition.Start + 1; } // Apricot BPB if(bits_in_apricot_bps == 1 && apricot_correct_spc && apricot_reserved_secs < partition.End - partition.Start && apricot_fats_no <= 2 && apricot_root_entries > 0 && apricot_fat_sectors > 0 && apricot_sectors <= partition.End - partition.Start + 1 && apricot_partitions == 0) return true; // All FAT12 without BPB can only be used on floppies, without partitions. if(partition.Start != 0) return false; // DEC Rainbow, lacks a BPB but has a very concrete structure... if(imagePlugin.GetSectors() == 800 && imagePlugin.GetSectorSize() == 512) { // DEC Rainbow boots up with a Z80, first byte should be DI (disable interrupts) byte z80_di = bpb_sector[0]; // First FAT1 sector resides at LBA 0x14 byte[] fat1_sector0 = imagePlugin.ReadSector(0x14); // First FAT2 sector resides at LBA 0x1A byte[] fat2_sector0 = imagePlugin.ReadSector(0x1A); bool equal_fat_ids = fat1_sector0[0] == fat2_sector0[0] && fat1_sector0[1] == fat2_sector0[1]; // Volume is software interleaved 2:1 MemoryStream rootMs = new MemoryStream(); foreach(byte[] tmp in from ulong rootSector in new[] {0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20} select imagePlugin.ReadSector(rootSector)) { rootMs.Write(tmp, 0, tmp.Length); } byte[] root_dir = rootMs.ToArray(); bool valid_root_dir = true; // Iterate all root directory for(int e = 0; e < 96 * 32; e += 32) { for(int c = 0; c < 11; c++) if(root_dir[c + e] < 0x20 && root_dir[c + e] != 0x00 && root_dir[c + e] != 0x05 || root_dir[c + e] == 0xFF || root_dir[c + e] == 0x2E) { valid_root_dir = false; break; } if(!valid_root_dir) break; } if(z80_di == 0xF3 && equal_fat_ids && (fat1_sector0[0] & 0xF0) == 0xF0 && fat1_sector0[1] == 0xFF && valid_root_dir) return true; } byte fat2 = fat_sector[1]; byte fat3 = fat_sector[2]; ushort fat_2nd_cluster = (ushort)(((fat2 << 8) + fat3) & 0xFFF); DicConsole.DebugWriteLine("FAT plugin", "1st fat cluster 1 = {0:X3}", fat_2nd_cluster); if(fat_2nd_cluster < 0xFF0) return false; ulong fat2_sector_no = 0; switch(fat_id) { case 0xE5: if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) fat2_sector_no = 2; break; case 0xFD: if(imagePlugin.ImageInfo.Sectors == 4004 && imagePlugin.ImageInfo.SectorSize == 128) fat2_sector_no = 7; else if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) fat2_sector_no = 7; break; case 0xFE: if(imagePlugin.ImageInfo.Sectors == 320 && imagePlugin.ImageInfo.SectorSize == 512) fat2_sector_no = 2; else if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) fat2_sector_no = 7; else if(imagePlugin.ImageInfo.Sectors == 1232 && imagePlugin.ImageInfo.SectorSize == 1024) fat2_sector_no = 3; else if(imagePlugin.ImageInfo.Sectors == 616 && imagePlugin.ImageInfo.SectorSize == 1024) fat2_sector_no = 2; else if(imagePlugin.ImageInfo.Sectors == 720 && imagePlugin.ImageInfo.SectorSize == 128) fat2_sector_no = 5; else if(imagePlugin.ImageInfo.Sectors == 640 && imagePlugin.ImageInfo.SectorSize == 512) fat2_sector_no = 2; break; case 0xFF: if(imagePlugin.ImageInfo.Sectors == 640 && imagePlugin.ImageInfo.SectorSize == 512) fat2_sector_no = 2; break; default: if(fat_id < 0xE8) return false; fat2_sector_no = 2; break; } if(fat2_sector_no > partition.End) return false; DicConsole.DebugWriteLine("FAT plugin", "2nd fat starts at = {0}", fat2_sector_no); byte[] fat2_sector = imagePlugin.ReadSector(fat2_sector_no); fat2 = fat2_sector[1]; fat3 = fat2_sector[2]; fat_2nd_cluster = (ushort)(((fat2 << 8) + fat3) & 0xFFF); if(fat_2nd_cluster < 0xFF0) return false; return fat_id == fat2_sector[0]; } public override void GetInformation(ImagePlugin imagePlugin, Partition partition, out string information) { information = ""; StringBuilder sb = new StringBuilder(); xmlFSType = new FileSystemType(); bool useAtariBPB = false; bool useMSXBPB = false; bool useDOS2BPB = false; bool useDOS3BPB = false; bool useDOS32BPB = false; bool useDOS33BPB = false; bool useShortEBPB = false; bool useEBPB = false; bool useShortFAT32 = false; bool useLongFAT32 = false; bool andos_oem_correct = false; bool useApricotBPB = false; bool useDecRainbowBPB = false; AtariParameterBlock atariBPB = new AtariParameterBlock(); MSXParameterBlock msxBPB = new MSXParameterBlock(); BIOSParameterBlock2 dos2BPB = new BIOSParameterBlock2(); BIOSParameterBlock30 dos30BPB = new BIOSParameterBlock30(); BIOSParameterBlock32 dos32BPB = new BIOSParameterBlock32(); BIOSParameterBlock33 dos33BPB = new BIOSParameterBlock33(); BIOSParameterBlockShortEBPB shortEBPB = new BIOSParameterBlockShortEBPB(); BIOSParameterBlockEBPB EBPB = new BIOSParameterBlockEBPB(); FAT32ParameterBlockShort shortFat32BPB = new FAT32ParameterBlockShort(); FAT32ParameterBlock Fat32BPB = new FAT32ParameterBlock(); ApricotLabel ApricotBPB = new ApricotLabel(); byte[] bpb_sector = imagePlugin.ReadSectors(partition.Start, 2); if(imagePlugin.ImageInfo.SectorSize >= 256) { IntPtr bpbPtr = Marshal.AllocHGlobal(512); Marshal.Copy(bpb_sector, 0, bpbPtr, 512); atariBPB = (AtariParameterBlock)Marshal.PtrToStructure(bpbPtr, typeof(AtariParameterBlock)); msxBPB = (MSXParameterBlock)Marshal.PtrToStructure(bpbPtr, typeof(MSXParameterBlock)); dos2BPB = (BIOSParameterBlock2)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlock2)); dos30BPB = (BIOSParameterBlock30)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlock30)); dos32BPB = (BIOSParameterBlock32)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlock32)); dos33BPB = (BIOSParameterBlock33)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlock33)); shortEBPB = (BIOSParameterBlockShortEBPB)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlockShortEBPB)); EBPB = (BIOSParameterBlockEBPB)Marshal.PtrToStructure(bpbPtr, typeof(BIOSParameterBlockEBPB)); shortFat32BPB = (FAT32ParameterBlockShort)Marshal.PtrToStructure(bpbPtr, typeof(FAT32ParameterBlockShort)); Fat32BPB = (FAT32ParameterBlock)Marshal.PtrToStructure(bpbPtr, typeof(FAT32ParameterBlock)); ApricotBPB = (ApricotLabel)Marshal.PtrToStructure(bpbPtr, typeof(ApricotLabel)); Marshal.FreeHGlobal(bpbPtr); int bits_in_bps_atari = CountBits.Count(atariBPB.bps); int bits_in_bps_msx = CountBits.Count(msxBPB.bps); int bits_in_bps_dos20 = CountBits.Count(dos2BPB.bps); int bits_in_bps_dos30 = CountBits.Count(dos30BPB.bps); int bits_in_bps_dos32 = CountBits.Count(dos32BPB.bps); int bits_in_bps_dos33 = CountBits.Count(dos33BPB.bps); int bits_in_bps_dos34 = CountBits.Count(shortEBPB.bps); int bits_in_bps_dos40 = CountBits.Count(EBPB.bps); int bits_in_bps_fat32_short = CountBits.Count(shortFat32BPB.bps); int bits_in_bps_fat32 = CountBits.Count(Fat32BPB.bps); int bits_in_bps_apricot = CountBits.Count(ApricotBPB.mainBPB.bps); bool correct_spc_atari = atariBPB.spc == 1 || atariBPB.spc == 2 || atariBPB.spc == 4 || atariBPB.spc == 8 || atariBPB.spc == 16 || atariBPB.spc == 32 || atariBPB.spc == 64; bool correct_spc_msx = msxBPB.spc == 1 || msxBPB.spc == 2 || msxBPB.spc == 4 || msxBPB.spc == 8 || msxBPB.spc == 16 || msxBPB.spc == 32 || msxBPB.spc == 64; bool correct_spc_dos20 = dos2BPB.spc == 1 || dos2BPB.spc == 2 || dos2BPB.spc == 4 || dos2BPB.spc == 8 || dos2BPB.spc == 16 || dos2BPB.spc == 32 || dos2BPB.spc == 64; bool correct_spc_dos30 = dos30BPB.spc == 1 || dos30BPB.spc == 2 || dos30BPB.spc == 4 || dos30BPB.spc == 8 || dos30BPB.spc == 16 || dos30BPB.spc == 32 || dos30BPB.spc == 64; bool correct_spc_dos32 = dos32BPB.spc == 1 || dos32BPB.spc == 2 || dos32BPB.spc == 4 || dos32BPB.spc == 8 || dos32BPB.spc == 16 || dos32BPB.spc == 32 || dos32BPB.spc == 64; bool correct_spc_dos33 = dos33BPB.spc == 1 || dos33BPB.spc == 2 || dos33BPB.spc == 4 || dos33BPB.spc == 8 || dos33BPB.spc == 16 || dos33BPB.spc == 32 || dos33BPB.spc == 64; bool correct_spc_dos34 = shortEBPB.spc == 1 || shortEBPB.spc == 2 || shortEBPB.spc == 4 || shortEBPB.spc == 8 || shortEBPB.spc == 16 || shortEBPB.spc == 32 || shortEBPB.spc == 64; bool correct_spc_dos40 = EBPB.spc == 1 || EBPB.spc == 2 || EBPB.spc == 4 || EBPB.spc == 8 || EBPB.spc == 16 || EBPB.spc == 32 || EBPB.spc == 64; bool correct_spc_fat32_short = shortFat32BPB.spc == 1 || shortFat32BPB.spc == 2 || shortFat32BPB.spc == 4 || shortFat32BPB.spc == 8 || shortFat32BPB.spc == 16 || shortFat32BPB.spc == 32 || shortFat32BPB.spc == 64; bool correct_spc_fat32 = Fat32BPB.spc == 1 || Fat32BPB.spc == 2 || Fat32BPB.spc == 4 || Fat32BPB.spc == 8 || Fat32BPB.spc == 16 || Fat32BPB.spc == 32 || Fat32BPB.spc == 64; bool correct_spc_apricot = ApricotBPB.mainBPB.spc == 1 || ApricotBPB.mainBPB.spc == 2 || ApricotBPB.mainBPB.spc == 4 || ApricotBPB.mainBPB.spc == 8 || ApricotBPB.mainBPB.spc == 16 || ApricotBPB.mainBPB.spc == 32 || ApricotBPB.mainBPB.spc == 64; // This is to support FAT partitions on hybrid ISO/USB images if(imagePlugin.ImageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { atariBPB.sectors /= 4; msxBPB.sectors /= 4; dos2BPB.sectors /= 4; dos30BPB.sectors /= 4; dos32BPB.sectors /= 4; dos33BPB.sectors /= 4; dos33BPB.big_sectors /= 4; shortEBPB.sectors /= 4; shortEBPB.big_sectors /= 4; EBPB.sectors /= 4; EBPB.big_sectors /= 4; shortFat32BPB.sectors /= 4; shortFat32BPB.big_sectors /= 4; shortFat32BPB.huge_sectors /= 4; Fat32BPB.sectors /= 4; Fat32BPB.big_sectors /= 4; ApricotBPB.mainBPB.sectors /= 4; } andos_oem_correct = dos33BPB.oem_name[0] < 0x20 && dos33BPB.oem_name[1] >= 0x20 && dos33BPB.oem_name[2] >= 0x20 && dos33BPB.oem_name[3] >= 0x20 && dos33BPB.oem_name[4] >= 0x20 && dos33BPB.oem_name[5] >= 0x20 && dos33BPB.oem_name[6] >= 0x20 && dos33BPB.oem_name[7] >= 0x20; if(bits_in_bps_fat32 == 1 && correct_spc_fat32 && Fat32BPB.fats_no <= 2 && Fat32BPB.sectors == 0 && Fat32BPB.spfat == 0 && Fat32BPB.signature == 0x29 && Encoding.ASCII.GetString(Fat32BPB.fs_type) == "FAT32 ") { DicConsole.DebugWriteLine("FAT plugin", "Using FAT32 BPB"); useLongFAT32 = true; } else if(bits_in_bps_fat32_short == 1 && correct_spc_fat32_short && shortFat32BPB.fats_no <= 2 && shortFat32BPB.sectors == 0 && shortFat32BPB.spfat == 0 && shortFat32BPB.signature == 0x28) { DicConsole.DebugWriteLine("FAT plugin", "Using short FAT32 BPB"); useShortFAT32 = shortFat32BPB.big_sectors == 0 ? shortFat32BPB.huge_sectors <= partition.End - partition.Start + 1 : shortFat32BPB.big_sectors <= partition.End - partition.Start + 1; } else if(bits_in_bps_msx == 1 && correct_spc_msx && msxBPB.fats_no <= 2 && msxBPB.root_ent > 0 && msxBPB.sectors <= partition.End - partition.Start + 1 && msxBPB.spfat > 0 && Encoding.ASCII.GetString(msxBPB.vol_id) == "VOL_ID") { DicConsole.DebugWriteLine("FAT plugin", "Using MSX BPB"); useMSXBPB = true; } else if(bits_in_bps_apricot == 1 && correct_spc_apricot && ApricotBPB.mainBPB.fats_no <= 2 && ApricotBPB.mainBPB.root_ent > 0 && ApricotBPB.mainBPB.sectors <= partition.End - partition.Start + 1 && ApricotBPB.mainBPB.spfat > 0 && ApricotBPB.partitionCount == 0) { DicConsole.DebugWriteLine("FAT plugin", "Using Apricot BPB"); useApricotBPB = true; } else if(bits_in_bps_dos40 == 1 && correct_spc_dos40 && EBPB.fats_no <= 2 && EBPB.root_ent > 0 && EBPB.spfat > 0 && (EBPB.signature == 0x28 || EBPB.signature == 0x29 || andos_oem_correct)) { if(EBPB.sectors == 0) { if(EBPB.big_sectors <= partition.End - partition.Start + 1) if(EBPB.signature == 0x29 || andos_oem_correct) { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 4.0 BPB"); useEBPB = true; } else { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.4 BPB"); useShortEBPB = true; } } else if(EBPB.sectors <= partition.End - partition.Start + 1) if(EBPB.signature == 0x29 || andos_oem_correct) { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 4.0 BPB"); useEBPB = true; } else { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.4 BPB"); useShortEBPB = true; } } else if(bits_in_bps_dos33 == 1 && correct_spc_dos33 && dos33BPB.rsectors < partition.End - partition.Start && dos33BPB.fats_no <= 2 && dos33BPB.root_ent > 0 && dos33BPB.spfat > 0) if(dos33BPB.sectors == 0 && dos33BPB.hsectors <= partition.Start && dos33BPB.big_sectors > 0 && dos33BPB.big_sectors <= partition.End - partition.Start + 1) { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.3 BPB"); useDOS33BPB = true; } else if(dos33BPB.big_sectors == 0 && dos33BPB.hsectors <= partition.Start && dos33BPB.sectors > 0 && dos33BPB.sectors <= partition.End - partition.Start + 1) if(atariBPB.jump[0] == 0x60 || atariBPB.jump[0] == 0xE9 && atariBPB.jump[1] == 0x00 && Encoding.ASCII.GetString(dos33BPB.oem_name) != "NEXT ") { DicConsole.DebugWriteLine("FAT plugin", "Using Atari BPB"); useAtariBPB = true; } else { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.3 BPB"); useDOS33BPB = true; } else { if(dos32BPB.hsectors <= partition.Start && dos32BPB.hsectors + dos32BPB.sectors == dos32BPB.total_sectors) { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.2 BPB"); useDOS32BPB = true; } else if(dos30BPB.sptrk > 0 && dos30BPB.sptrk < 64 && dos30BPB.heads > 0 && dos30BPB.heads < 256) if(atariBPB.jump[0] == 0x60 || atariBPB.jump[0] == 0xE9 && atariBPB.jump[1] == 0x00 && Encoding.ASCII.GetString(dos33BPB.oem_name) != "NEXT ") { DicConsole.DebugWriteLine("FAT plugin", "Using Atari BPB"); useAtariBPB = true; } else { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 3.0 BPB"); useDOS3BPB = true; } else { if(atariBPB.jump[0] == 0x60 || atariBPB.jump[0] == 0xE9 && atariBPB.jump[1] == 0x00 && Encoding.ASCII.GetString(dos33BPB.oem_name) != "NEXT ") { DicConsole.DebugWriteLine("FAT plugin", "Using Atari BPB"); useAtariBPB = true; } else { DicConsole.DebugWriteLine("FAT plugin", "Using DOS 2.0 BPB"); useDOS2BPB = true; } } } } BIOSParameterBlockEBPB fakeBPB = new BIOSParameterBlockEBPB(); byte[] fat_sector; bool isFAT12 = false; bool isFAT16 = false; bool isFAT32 = false; ulong root_directory_sector = 0; string extraInfo = null; string bootChk = null; Sha1Context sha1Ctx = new Sha1Context(); sha1Ctx.Init(); byte[] chkTmp; // This is needed because for FAT16, GEMDOS increases bytes per sector count instead of using big_sectors field. uint sectors_per_real_sector = 0; // This is needed because some OSes don't put volume label as first entry in the root directory uint sectors_for_root_directory = 0; // DEC Rainbow, lacks a BPB but has a very concrete structure... if(imagePlugin.GetSectors() == 800 && imagePlugin.GetSectorSize() == 512 && !useAtariBPB && !useMSXBPB && !useDOS2BPB && !useDOS3BPB && !useDOS32BPB && !useDOS33BPB && !useShortEBPB && !useEBPB && !useShortFAT32 && !useLongFAT32 && !useApricotBPB) { // DEC Rainbow boots up with a Z80, first byte should be DI (disable interrupts) byte z80_di = bpb_sector[0]; // First FAT1 sector resides at LBA 0x14 byte[] fat1_sector0 = imagePlugin.ReadSector(0x14); // First FAT2 sector resides at LBA 0x1A byte[] fat2_sector0 = imagePlugin.ReadSector(0x1A); bool equal_fat_ids = fat1_sector0[0] == fat2_sector0[0] && fat1_sector0[1] == fat2_sector0[1]; // Volume is software interleaved 2:1 MemoryStream rootMs = new MemoryStream(); foreach(byte[] tmp in from ulong rootSector in new[] {0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20} select imagePlugin.ReadSector(rootSector)) { rootMs.Write(tmp, 0, tmp.Length); } byte[] root_dir = rootMs.ToArray(); bool valid_root_dir = true; // Iterate all root directory for(int e = 0; e < 96 * 32; e += 32) { for(int c = 0; c < 11; c++) if(root_dir[c + e] < 0x20 && root_dir[c + e] != 0x00 && root_dir[c + e] != 0x05 || root_dir[c + e] == 0xFF || root_dir[c + e] == 0x2E) { valid_root_dir = false; break; } if(!valid_root_dir) break; } if(z80_di == 0xF3 && equal_fat_ids && (fat1_sector0[0] & 0xF0) == 0xF0 && fat1_sector0[1] == 0xFF && valid_root_dir) { useDecRainbowBPB = true; DicConsole.DebugWriteLine("FAT plugin", "Using DEC Rainbow hardcoded BPB."); fakeBPB.bps = 512; fakeBPB.spc = 1; fakeBPB.rsectors = 20; fakeBPB.fats_no = 2; fakeBPB.root_ent = 96; fakeBPB.sectors = 800; fakeBPB.media = 0xFA; fakeBPB.sptrk = 10; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 3; xmlFSType.Bootable = true; fakeBPB.boot_code = bpb_sector; isFAT12 = true; } } if(!useAtariBPB && !useMSXBPB && !useDOS2BPB && !useDOS3BPB && !useDOS32BPB && !useDOS33BPB && !useShortEBPB && !useEBPB && !useShortFAT32 && !useLongFAT32 && !useApricotBPB && !useDecRainbowBPB) { isFAT12 = true; fat_sector = imagePlugin.ReadSector(1 + partition.Start); switch(fat_sector[0]) { case 0xE5: if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 128; fakeBPB.spc = 4; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 64; fakeBPB.sectors = 2002; fakeBPB.media = 0xE5; fakeBPB.sptrk = 26; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 1; } break; case 0xFD: if(imagePlugin.ImageInfo.Sectors == 4004 && imagePlugin.ImageInfo.SectorSize == 128) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 128; fakeBPB.spc = 4; fakeBPB.rsectors = 4; fakeBPB.fats_no = 2; fakeBPB.root_ent = 68; fakeBPB.sectors = 4004; fakeBPB.media = 0xFD; fakeBPB.sptrk = 26; fakeBPB.heads = 2; fakeBPB.hsectors = 0; fakeBPB.spfat = 6; } else if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 128; fakeBPB.spc = 4; fakeBPB.rsectors = 4; fakeBPB.fats_no = 2; fakeBPB.root_ent = 68; fakeBPB.sectors = 2002; fakeBPB.media = 0xFD; fakeBPB.sptrk = 26; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 6; } break; case 0xFE: if(imagePlugin.ImageInfo.Sectors == 320 && imagePlugin.ImageInfo.SectorSize == 512) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB for 5.25\" SSDD."); fakeBPB.bps = 512; fakeBPB.spc = 1; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 64; fakeBPB.sectors = 320; fakeBPB.media = 0xFE; fakeBPB.sptrk = 8; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 1; } else if(imagePlugin.ImageInfo.Sectors == 2002 && imagePlugin.ImageInfo.SectorSize == 128) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 128; fakeBPB.spc = 4; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 68; fakeBPB.sectors = 2002; fakeBPB.media = 0xFE; fakeBPB.sptrk = 26; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 6; } else if(imagePlugin.ImageInfo.Sectors == 1232 && imagePlugin.ImageInfo.SectorSize == 1024) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 1024; fakeBPB.spc = 1; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 192; fakeBPB.sectors = 1232; fakeBPB.media = 0xFE; fakeBPB.sptrk = 8; fakeBPB.heads = 2; fakeBPB.hsectors = 0; fakeBPB.spfat = 2; } else if(imagePlugin.ImageInfo.Sectors == 616 && imagePlugin.ImageInfo.SectorSize == 1024) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 1024; fakeBPB.spc = 1; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 6192; fakeBPB.sectors = 616; fakeBPB.media = 0xFE; fakeBPB.sptrk = 8; fakeBPB.heads = 2; fakeBPB.hsectors = 0; } else if(imagePlugin.ImageInfo.Sectors == 720 && imagePlugin.ImageInfo.SectorSize == 128) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB."); fakeBPB.bps = 128; fakeBPB.spc = 2; fakeBPB.rsectors = 54; fakeBPB.fats_no = 2; fakeBPB.root_ent = 64; fakeBPB.sectors = 720; fakeBPB.media = 0xFE; fakeBPB.sptrk = 18; fakeBPB.heads = 1; fakeBPB.hsectors = 0; fakeBPB.spfat = 4; } else if(imagePlugin.ImageInfo.Sectors == 640 && imagePlugin.ImageInfo.SectorSize == 512) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB for 5.25\" DSDD."); fakeBPB.bps = 512; fakeBPB.spc = 2; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 112; fakeBPB.sectors = 640; fakeBPB.media = 0xFF; fakeBPB.sptrk = 8; fakeBPB.heads = 2; fakeBPB.hsectors = 0; fakeBPB.spfat = 1; } break; case 0xFF: if(imagePlugin.ImageInfo.Sectors == 640 && imagePlugin.ImageInfo.SectorSize == 512) { DicConsole.DebugWriteLine("FAT plugin", "Using hardcoded BPB for 5.25\" DSDD."); fakeBPB.bps = 512; fakeBPB.spc = 2; fakeBPB.rsectors = 1; fakeBPB.fats_no = 2; fakeBPB.root_ent = 112; fakeBPB.sectors = 640; fakeBPB.media = 0xFF; fakeBPB.sptrk = 8; fakeBPB.heads = 2; fakeBPB.hsectors = 0; fakeBPB.spfat = 1; } break; } // This assumes a bootable sector will jump somewhere or disable interrupts in x86 code xmlFSType.Bootable |= bpb_sector[0] == 0xFA || bpb_sector[0] == 0xEB && bpb_sector[1] <= 0x7F; fakeBPB.boot_code = bpb_sector; } else if(useShortFAT32 || useLongFAT32) { isFAT32 = true; // This is to support FAT partitions on hybrid ISO/USB images if(imagePlugin.ImageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { Fat32BPB.bps *= 4; Fat32BPB.spc /= 4; Fat32BPB.big_spfat /= 4; Fat32BPB.hsectors /= 4; Fat32BPB.sptrk /= 4; } if(Fat32BPB.version != 0) { sb.AppendLine("FAT+"); xmlFSType.Type = "FAT+"; } else { sb.AppendLine("Microsoft FAT32"); xmlFSType.Type = "FAT32"; } if(Fat32BPB.oem_name != null) if(Fat32BPB.oem_name[5] == 0x49 && Fat32BPB.oem_name[6] == 0x48 && Fat32BPB.oem_name[7] == 0x43) sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker."); else xmlFSType.SystemIdentifier = StringHandlers.CToString(Fat32BPB.oem_name); if(!string.IsNullOrEmpty(xmlFSType.SystemIdentifier)) sb.AppendFormat("OEM Name: {0}", xmlFSType.SystemIdentifier.Trim()).AppendLine(); sb.AppendFormat("{0} bytes per sector.", Fat32BPB.bps).AppendLine(); sb.AppendFormat("{0} sectors per cluster.", Fat32BPB.spc).AppendLine(); xmlFSType.ClusterSize = Fat32BPB.bps * Fat32BPB.spc; sb.AppendFormat("{0} sectors reserved between BPB and FAT.", Fat32BPB.rsectors).AppendLine(); if(Fat32BPB.big_sectors == 0 && Fat32BPB.signature == 0x28) { sb.AppendFormat("{0} sectors on volume ({1} bytes).", shortFat32BPB.huge_sectors, shortFat32BPB.huge_sectors * shortFat32BPB.bps).AppendLine(); xmlFSType.Clusters = (long)(shortFat32BPB.huge_sectors / shortFat32BPB.spc); } else { sb.AppendFormat("{0} sectors on volume ({1} bytes).", Fat32BPB.big_sectors, Fat32BPB.big_sectors * Fat32BPB.bps).AppendLine(); xmlFSType.Clusters = Fat32BPB.big_sectors / Fat32BPB.spc; } sb.AppendFormat("{0} clusters on volume.", xmlFSType.Clusters).AppendLine(); sb.AppendFormat("Media descriptor: 0x{0:X2}", Fat32BPB.media).AppendLine(); sb.AppendFormat("{0} sectors per FAT.", Fat32BPB.big_spfat).AppendLine(); sb.AppendFormat("{0} sectors per track.", Fat32BPB.sptrk).AppendLine(); sb.AppendFormat("{0} heads.", Fat32BPB.heads).AppendLine(); sb.AppendFormat("{0} hidden sectors before BPB.", Fat32BPB.hsectors).AppendLine(); sb.AppendFormat("Cluster of root directory: {0}", Fat32BPB.root_cluster).AppendLine(); sb.AppendFormat("Sector of FSINFO structure: {0}", Fat32BPB.fsinfo_sector).AppendLine(); sb.AppendFormat("Sector of backup FAT32 parameter block: {0}", Fat32BPB.backup_sector).AppendLine(); sb.AppendFormat("Drive number: 0x{0:X2}", Fat32BPB.drive_no).AppendLine(); sb.AppendFormat("Volume Serial Number: 0x{0:X8}", Fat32BPB.serial_no).AppendLine(); xmlFSType.VolumeSerial = $"{Fat32BPB.serial_no:X8}"; if((Fat32BPB.flags & 0xF8) == 0x00) { if((Fat32BPB.flags & 0x01) == 0x01) { sb.AppendLine("Volume should be checked on next mount."); xmlFSType.Dirty = true; } if((Fat32BPB.flags & 0x02) == 0x02) sb.AppendLine("Disk surface should be on next mount."); } if((Fat32BPB.mirror_flags & 0x80) == 0x80) sb.AppendFormat("FATs are out of sync. FAT #{0} is in use.", Fat32BPB.mirror_flags & 0xF) .AppendLine(); else sb.AppendLine("All copies of FAT are the same."); if((Fat32BPB.mirror_flags & 0x6F20) == 0x6F20) sb.AppendLine("DR-DOS will boot this FAT32 using CHS."); else if((Fat32BPB.mirror_flags & 0x4F20) == 0x4F20) sb.AppendLine("DR-DOS will boot this FAT32 using LBA."); if(Fat32BPB.signature == 0x29) { xmlFSType.VolumeName = Encoding.ASCII.GetString(Fat32BPB.volume_label); sb.AppendFormat("Filesystem type: {0}", Encoding.ASCII.GetString(Fat32BPB.fs_type)).AppendLine(); bootChk = sha1Ctx.Data(Fat32BPB.boot_code, out chkTmp); } else bootChk = sha1Ctx.Data(shortFat32BPB.boot_code, out chkTmp); // Check that jumps to a correct boot code position and has boot signature set. // This will mean that the volume will boot, even if just to say "this is not bootable change disk"...... xmlFSType.Bootable |= Fat32BPB.jump[0] == 0xEB && Fat32BPB.jump[1] > 0x58 && Fat32BPB.jump[1] < 0x80 && Fat32BPB.boot_signature == 0xAA55; sectors_per_real_sector = Fat32BPB.bps / imagePlugin.ImageInfo.SectorSize; // First root directory sector root_directory_sector = (ulong)((Fat32BPB.root_cluster - 2) * Fat32BPB.spc + Fat32BPB.big_spfat * Fat32BPB.fats_no + Fat32BPB.rsectors) * sectors_per_real_sector; sectors_for_root_directory = 1; if(Fat32BPB.fsinfo_sector + partition.Start <= partition.End) { byte[] fsinfo_sector = imagePlugin.ReadSector(Fat32BPB.fsinfo_sector + partition.Start); IntPtr fsinfo_ptr = Marshal.AllocHGlobal(512); Marshal.Copy(fsinfo_sector, 0, fsinfo_ptr, 512); FSInfoSector fs_info = (FSInfoSector)Marshal.PtrToStructure(fsinfo_ptr, typeof(FSInfoSector)); Marshal.FreeHGlobal(fsinfo_ptr); if(fs_info.signature1 == fsinfo_signature1 && fs_info.signature2 == fsinfo_signature2 && fs_info.signature3 == fsinfo_signature3) { if(fs_info.free_clusters < 0xFFFFFFFF) { sb.AppendFormat("{0} free clusters", fs_info.free_clusters).AppendLine(); xmlFSType.FreeClusters = fs_info.free_clusters; xmlFSType.FreeClustersSpecified = true; } if(fs_info.last_cluster > 2 && fs_info.last_cluster < 0xFFFFFFFF) sb.AppendFormat("Last allocated cluster {0}", fs_info.last_cluster).AppendLine(); } } } else if(useEBPB) fakeBPB = EBPB; else if(useShortEBPB) { fakeBPB.jump = shortEBPB.jump; fakeBPB.oem_name = shortEBPB.oem_name; fakeBPB.bps = shortEBPB.bps; fakeBPB.spc = shortEBPB.spc; fakeBPB.rsectors = shortEBPB.rsectors; fakeBPB.fats_no = shortEBPB.fats_no; fakeBPB.root_ent = shortEBPB.root_ent; fakeBPB.sectors = shortEBPB.sectors; fakeBPB.media = shortEBPB.media; fakeBPB.spfat = shortEBPB.spfat; fakeBPB.sptrk = shortEBPB.sptrk; fakeBPB.heads = shortEBPB.heads; fakeBPB.hsectors = shortEBPB.hsectors; fakeBPB.big_sectors = shortEBPB.big_sectors; fakeBPB.drive_no = shortEBPB.drive_no; fakeBPB.flags = shortEBPB.flags; fakeBPB.signature = shortEBPB.signature; fakeBPB.serial_no = shortEBPB.serial_no; fakeBPB.boot_code = shortEBPB.boot_code; fakeBPB.boot_signature = shortEBPB.boot_signature; } else if(useDOS33BPB) { fakeBPB.jump = dos33BPB.jump; fakeBPB.oem_name = dos33BPB.oem_name; fakeBPB.bps = dos33BPB.bps; fakeBPB.spc = dos33BPB.spc; fakeBPB.rsectors = dos33BPB.rsectors; fakeBPB.fats_no = dos33BPB.fats_no; fakeBPB.root_ent = dos33BPB.root_ent; fakeBPB.sectors = dos33BPB.sectors; fakeBPB.media = dos33BPB.media; fakeBPB.spfat = dos33BPB.spfat; fakeBPB.sptrk = dos33BPB.sptrk; fakeBPB.heads = dos33BPB.heads; fakeBPB.hsectors = dos33BPB.hsectors; fakeBPB.big_sectors = dos33BPB.big_sectors; fakeBPB.boot_code = dos33BPB.boot_code; fakeBPB.boot_signature = dos33BPB.boot_signature; } else if(useDOS32BPB) { fakeBPB.jump = dos32BPB.jump; fakeBPB.oem_name = dos32BPB.oem_name; fakeBPB.bps = dos32BPB.bps; fakeBPB.spc = dos32BPB.spc; fakeBPB.rsectors = dos32BPB.rsectors; fakeBPB.fats_no = dos32BPB.fats_no; fakeBPB.root_ent = dos32BPB.root_ent; fakeBPB.sectors = dos32BPB.sectors; fakeBPB.media = dos32BPB.media; fakeBPB.spfat = dos32BPB.spfat; fakeBPB.sptrk = dos32BPB.sptrk; fakeBPB.heads = dos32BPB.heads; fakeBPB.hsectors = dos32BPB.hsectors; fakeBPB.boot_code = dos32BPB.boot_code; fakeBPB.boot_signature = dos32BPB.boot_signature; } else if(useDOS3BPB) { fakeBPB.jump = dos30BPB.jump; fakeBPB.oem_name = dos30BPB.oem_name; fakeBPB.bps = dos30BPB.bps; fakeBPB.spc = dos30BPB.spc; fakeBPB.rsectors = dos30BPB.rsectors; fakeBPB.fats_no = dos30BPB.fats_no; fakeBPB.root_ent = dos30BPB.root_ent; fakeBPB.sectors = dos30BPB.sectors; fakeBPB.media = dos30BPB.media; fakeBPB.spfat = dos30BPB.spfat; fakeBPB.sptrk = dos30BPB.sptrk; fakeBPB.heads = dos30BPB.heads; fakeBPB.hsectors = dos30BPB.hsectors; fakeBPB.boot_code = dos30BPB.boot_code; fakeBPB.boot_signature = dos30BPB.boot_signature; } else if(useDOS2BPB) { fakeBPB.jump = dos2BPB.jump; fakeBPB.oem_name = dos2BPB.oem_name; fakeBPB.bps = dos2BPB.bps; fakeBPB.spc = dos2BPB.spc; fakeBPB.rsectors = dos2BPB.rsectors; fakeBPB.fats_no = dos2BPB.fats_no; fakeBPB.root_ent = dos2BPB.root_ent; fakeBPB.sectors = dos2BPB.sectors; fakeBPB.media = dos2BPB.media; fakeBPB.spfat = dos2BPB.spfat; fakeBPB.boot_code = dos2BPB.boot_code; fakeBPB.boot_signature = dos2BPB.boot_signature; } else if(useMSXBPB) { isFAT12 = true; fakeBPB.jump = msxBPB.jump; fakeBPB.oem_name = msxBPB.oem_name; fakeBPB.bps = msxBPB.bps; fakeBPB.spc = msxBPB.spc; fakeBPB.rsectors = msxBPB.rsectors; fakeBPB.fats_no = msxBPB.fats_no; fakeBPB.root_ent = msxBPB.root_ent; fakeBPB.sectors = msxBPB.sectors; fakeBPB.media = msxBPB.media; fakeBPB.spfat = msxBPB.spfat; fakeBPB.sptrk = msxBPB.sptrk; fakeBPB.heads = msxBPB.heads; fakeBPB.hsectors = msxBPB.hsectors; fakeBPB.boot_code = msxBPB.boot_code; fakeBPB.boot_signature = msxBPB.boot_signature; fakeBPB.serial_no = msxBPB.serial_no; // TODO: Is there any way to check this? xmlFSType.Bootable = true; } else if(useAtariBPB) { fakeBPB.jump = atariBPB.jump; fakeBPB.oem_name = atariBPB.oem_name; fakeBPB.bps = atariBPB.bps; fakeBPB.spc = atariBPB.spc; fakeBPB.rsectors = atariBPB.rsectors; fakeBPB.fats_no = atariBPB.fats_no; fakeBPB.root_ent = atariBPB.root_ent; fakeBPB.sectors = atariBPB.sectors; fakeBPB.media = atariBPB.media; fakeBPB.spfat = atariBPB.spfat; fakeBPB.sptrk = atariBPB.sptrk; fakeBPB.heads = atariBPB.heads; fakeBPB.boot_code = atariBPB.boot_code; ushort sum = 0; BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; for(int i = 0; i < bpb_sector.Length; i += 2) sum += BigEndianBitConverter.ToUInt16(bpb_sector, i); // TODO: Check this if(sum == 0x1234) { xmlFSType.Bootable = true; StringBuilder atariSb = new StringBuilder(); atariSb.AppendFormat("cmdload will be loaded with value {0:X4}h", BigEndianBitConverter.ToUInt16(bpb_sector, 0x01E)).AppendLine(); atariSb.AppendFormat("Boot program will be loaded at address {0:X4}h", atariBPB.ldaaddr) .AppendLine(); atariSb.AppendFormat("FAT and directory will be cached at address {0:X4}h", atariBPB.fatbuf) .AppendLine(); if(atariBPB.ldmode == 0) { byte[] tmp = new byte[8]; Array.Copy(atariBPB.fname, 0, tmp, 0, 8); string fname = Encoding.ASCII.GetString(tmp).Trim(); tmp = new byte[3]; Array.Copy(atariBPB.fname, 8, tmp, 0, 3); string extension = Encoding.ASCII.GetString(tmp).Trim(); string filename; if(string.IsNullOrEmpty(extension)) filename = fname; else filename = fname + "." + extension; atariSb.AppendFormat("Boot program resides in file \"{0}\"", filename).AppendLine(); } else atariSb.AppendFormat("Boot program starts in sector {0} and is {1} sectors long ({2} bytes)", atariBPB.ssect, atariBPB.sectcnt, atariBPB.sectcnt * atariBPB.bps) .AppendLine(); extraInfo = atariSb.ToString(); } } else if(useApricotBPB) { isFAT12 = true; fakeBPB.bps = ApricotBPB.mainBPB.bps; fakeBPB.spc = ApricotBPB.mainBPB.spc; fakeBPB.rsectors = ApricotBPB.mainBPB.rsectors; fakeBPB.fats_no = ApricotBPB.mainBPB.fats_no; fakeBPB.root_ent = ApricotBPB.mainBPB.root_ent; fakeBPB.sectors = ApricotBPB.mainBPB.sectors; fakeBPB.media = ApricotBPB.mainBPB.media; fakeBPB.spfat = ApricotBPB.mainBPB.spfat; fakeBPB.sptrk = ApricotBPB.spt; xmlFSType.Bootable = ApricotBPB.bootType > 0; if(ApricotBPB.bootLocation > 0 && ApricotBPB.bootLocation + ApricotBPB.bootSize < imagePlugin.GetSectors()) fakeBPB.boot_code = imagePlugin.ReadSectors(ApricotBPB.bootLocation, (uint)(ApricotBPB.sectorSize * ApricotBPB.bootSize) / imagePlugin.GetSectorSize()); } if(!isFAT32) { // This is to support FAT partitions on hybrid ISO/USB images if(imagePlugin.ImageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { fakeBPB.bps *= 4; fakeBPB.spc /= 4; fakeBPB.spfat /= 4; fakeBPB.hsectors /= 4; fakeBPB.sptrk /= 4; fakeBPB.rsectors /= 4; if(fakeBPB.spc == 0) fakeBPB.spc = 1; } // This assumes no sane implementation will violate cluster size rules // However nothing prevents this to happen // If first file on disk uses only one cluster there is absolutely no way to differentiate between FAT12 and FAT16, // so let's hope implementations use common sense? if(!isFAT12 && !isFAT16) { ulong clusters; if(fakeBPB.sectors == 0) clusters = fakeBPB.spc == 0 ? fakeBPB.big_sectors : fakeBPB.big_sectors / fakeBPB.spc; else clusters = fakeBPB.spc == 0 ? fakeBPB.sectors : (ulong)fakeBPB.sectors / fakeBPB.spc; if(clusters < 4089) isFAT12 = true; else isFAT16 = true; } if(isFAT12) { if(useAtariBPB) sb.AppendLine("Atari FAT12"); else if(useApricotBPB) sb.AppendLine("Apricot FAT12"); else sb.AppendLine("Microsoft FAT12"); xmlFSType.Type = "FAT12"; } else if(isFAT16) { if(useAtariBPB) sb.AppendLine("Atari FAT16"); else sb.AppendLine("Microsoft FAT16"); xmlFSType.Type = "FAT16"; } if(useAtariBPB) { if(atariBPB.serial_no[0] == 0x49 && atariBPB.serial_no[1] == 0x48 && atariBPB.serial_no[2] == 0x43) sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker."); else xmlFSType.VolumeSerial = $"{atariBPB.serial_no[0]:X2}{atariBPB.serial_no[1]:X2}{atariBPB.serial_no[2]:X2}"; xmlFSType.SystemIdentifier = StringHandlers.CToString(atariBPB.oem_name); if(string.IsNullOrEmpty(xmlFSType.SystemIdentifier)) xmlFSType.SystemIdentifier = null; } else if(fakeBPB.oem_name != null) { if(fakeBPB.oem_name[5] == 0x49 && fakeBPB.oem_name[6] == 0x48 && fakeBPB.oem_name[7] == 0x43) sb.AppendLine("Volume has been modified by Windows 9x/Me Volume Tracker."); else { // Later versions of Windows create a DOS 3 BPB without OEM name on 8 sectors/track floppies // OEM ID should be ASCII, otherwise ignore it if(fakeBPB.oem_name[0] >= 0x20 && fakeBPB.oem_name[0] <= 0x7F && fakeBPB.oem_name[1] >= 0x20 && fakeBPB.oem_name[1] <= 0x7F && fakeBPB.oem_name[2] >= 0x20 && fakeBPB.oem_name[2] <= 0x7F && fakeBPB.oem_name[3] >= 0x20 && fakeBPB.oem_name[3] <= 0x7F && fakeBPB.oem_name[4] >= 0x20 && fakeBPB.oem_name[4] <= 0x7F && fakeBPB.oem_name[5] >= 0x20 && fakeBPB.oem_name[5] <= 0x7F && fakeBPB.oem_name[6] >= 0x20 && fakeBPB.oem_name[6] <= 0x7F && fakeBPB.oem_name[7] >= 0x20 && fakeBPB.oem_name[7] <= 0x7F) xmlFSType.SystemIdentifier = StringHandlers.CToString(fakeBPB.oem_name); else if(fakeBPB.oem_name[0] < 0x20 && fakeBPB.oem_name[1] >= 0x20 && fakeBPB.oem_name[1] <= 0x7F && fakeBPB.oem_name[2] >= 0x20 && fakeBPB.oem_name[2] <= 0x7F && fakeBPB.oem_name[3] >= 0x20 && fakeBPB.oem_name[3] <= 0x7F && fakeBPB.oem_name[4] >= 0x20 && fakeBPB.oem_name[4] <= 0x7F && fakeBPB.oem_name[5] >= 0x20 && fakeBPB.oem_name[5] <= 0x7F && fakeBPB.oem_name[6] >= 0x20 && fakeBPB.oem_name[6] <= 0x7F && fakeBPB.oem_name[7] >= 0x20 && fakeBPB.oem_name[7] <= 0x7F) xmlFSType.SystemIdentifier = StringHandlers.CToString(fakeBPB.oem_name, CurrentEncoding, start: 1); } if(fakeBPB.signature == 0x28 || fakeBPB.signature == 0x29) xmlFSType.VolumeSerial = $"{fakeBPB.serial_no:X8}"; } if(xmlFSType.SystemIdentifier != null) sb.AppendFormat("OEM Name: {0}", xmlFSType.SystemIdentifier.Trim()).AppendLine(); sb.AppendFormat("{0} bytes per sector.", fakeBPB.bps).AppendLine(); if(fakeBPB.sectors == 0) { sb.AppendFormat("{0} sectors on volume ({1} bytes).", fakeBPB.big_sectors, fakeBPB.big_sectors * fakeBPB.bps).AppendLine(); xmlFSType.Clusters = fakeBPB.spc == 0 ? fakeBPB.big_sectors : fakeBPB.big_sectors / fakeBPB.spc; } else { sb.AppendFormat("{0} sectors on volume ({1} bytes).", fakeBPB.sectors, fakeBPB.sectors * fakeBPB.bps).AppendLine(); xmlFSType.Clusters = fakeBPB.spc == 0 ? fakeBPB.sectors : fakeBPB.sectors / fakeBPB.spc; } sb.AppendFormat("{0} sectors per cluster.", fakeBPB.spc).AppendLine(); sb.AppendFormat("{0} clusters on volume.", xmlFSType.Clusters).AppendLine(); xmlFSType.ClusterSize = fakeBPB.bps * fakeBPB.spc; sb.AppendFormat("{0} sectors reserved between BPB and FAT.", fakeBPB.rsectors).AppendLine(); sb.AppendFormat("{0} FATs.", fakeBPB.fats_no).AppendLine(); sb.AppendFormat("{0} entries on root directory.", fakeBPB.root_ent).AppendLine(); if(fakeBPB.media > 0) sb.AppendFormat("Media descriptor: 0x{0:X2}", fakeBPB.media).AppendLine(); sb.AppendFormat("{0} sectors per FAT.", fakeBPB.spfat).AppendLine(); if(fakeBPB.sptrk > 0 && fakeBPB.sptrk < 64 && fakeBPB.heads > 0 && fakeBPB.heads < 256) { sb.AppendFormat("{0} sectors per track.", fakeBPB.sptrk).AppendLine(); sb.AppendFormat("{0} heads.", fakeBPB.heads).AppendLine(); } if(fakeBPB.hsectors <= partition.Start) sb.AppendFormat("{0} hidden sectors before BPB.", fakeBPB.hsectors).AppendLine(); if(fakeBPB.signature == 0x28 || fakeBPB.signature == 0x29 || andos_oem_correct) { sb.AppendFormat("Drive number: 0x{0:X2}", fakeBPB.drive_no).AppendLine(); if(xmlFSType.VolumeSerial != null) sb.AppendFormat("Volume Serial Number: {0}", xmlFSType.VolumeSerial).AppendLine(); if((fakeBPB.flags & 0xF8) == 0x00) { if((fakeBPB.flags & 0x01) == 0x01) { sb.AppendLine("Volume should be checked on next mount."); xmlFSType.Dirty = true; } if((fakeBPB.flags & 0x02) == 0x02) sb.AppendLine("Disk surface should be on next mount."); } if(fakeBPB.signature == 0x29 || andos_oem_correct) { xmlFSType.VolumeName = Encoding.ASCII.GetString(fakeBPB.volume_label); sb.AppendFormat("Filesystem type: {0}", Encoding.ASCII.GetString(fakeBPB.fs_type)).AppendLine(); } } else if(useAtariBPB && xmlFSType.VolumeSerial != null) sb.AppendFormat("Volume Serial Number: {0}", xmlFSType.VolumeSerial).AppendLine(); bootChk = sha1Ctx.Data(fakeBPB.boot_code, out chkTmp); // Check that jumps to a correct boot code position and has boot signature set. // This will mean that the volume will boot, even if just to say "this is not bootable change disk"...... if(xmlFSType.Bootable == false && fakeBPB.jump != null) xmlFSType.Bootable |= fakeBPB.jump[0] == 0xEB && fakeBPB.jump[1] > 0x58 && fakeBPB.jump[1] < 0x80 && fakeBPB.boot_signature == 0xAA55; sectors_per_real_sector = fakeBPB.bps / imagePlugin.ImageInfo.SectorSize; // First root directory sector root_directory_sector = (ulong)(fakeBPB.spfat * fakeBPB.fats_no + fakeBPB.rsectors) * sectors_per_real_sector; sectors_for_root_directory = (uint)(fakeBPB.root_ent * 32 / imagePlugin.ImageInfo.SectorSize); } if(extraInfo != null) sb.Append(extraInfo); if(root_directory_sector + partition.Start < partition.End && imagePlugin.ImageInfo.XmlMediaType != XmlMediaType.OpticalDisc) { byte[] root_directory = imagePlugin.ReadSectors(root_directory_sector + partition.Start, sectors_for_root_directory); if(useDecRainbowBPB) { MemoryStream rootMs = new MemoryStream(); foreach(byte[] tmp in from ulong rootSector in new[] {0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20} select imagePlugin.ReadSector(rootSector)) { rootMs.Write(tmp, 0, tmp.Length); } root_directory = rootMs.ToArray(); } for(int i = 0; i < root_directory.Length; i += 32) { // Not a correct entry if(root_directory[i] < 0x20 && root_directory[i] != 0x05) continue; // Deleted or subdirectory entry if(root_directory[i] == 0x2E || root_directory[i] == 0xE5) continue; // Not a volume label if(root_directory[i + 0x0B] != 0x08 && root_directory[i + 0x0B] != 0x28) continue; IntPtr entry_ptr = Marshal.AllocHGlobal(32); Marshal.Copy(root_directory, i, entry_ptr, 32); DirectoryEntry entry = (DirectoryEntry)Marshal.PtrToStructure(entry_ptr, typeof(DirectoryEntry)); Marshal.FreeHGlobal(entry_ptr); byte[] fullname = new byte[11]; Array.Copy(entry.filename, 0, fullname, 0, 8); Array.Copy(entry.extension, 0, fullname, 8, 3); string volname = CurrentEncoding.GetString(fullname).Trim(); if(!string.IsNullOrEmpty(volname)) if((entry.caseinfo & 0x0C) > 0) xmlFSType.VolumeName = volname.ToLower(); else xmlFSType.VolumeName = volname; if(entry.ctime > 0 && entry.cdate > 0) { xmlFSType.CreationDate = DateHandlers.DOSToDateTime(entry.cdate, entry.ctime); if(entry.ctime_ms > 0) xmlFSType.CreationDate = xmlFSType.CreationDate.AddMilliseconds(entry.ctime_ms * 10); xmlFSType.CreationDateSpecified = true; sb.AppendFormat("Volume created on {0}", xmlFSType.CreationDate).AppendLine(); } if(entry.mtime > 0 && entry.mdate > 0) { xmlFSType.ModificationDate = DateHandlers.DOSToDateTime(entry.mdate, entry.mtime); xmlFSType.ModificationDateSpecified = true; sb.AppendFormat("Volume last modified on {0}", xmlFSType.ModificationDate).AppendLine(); } if(entry.adate > 0) sb.AppendFormat("Volume last accessed on {0:d}", DateHandlers.DOSToDateTime(entry.adate, 0)) .AppendLine(); break; } } if(!string.IsNullOrEmpty(xmlFSType.VolumeName)) sb.AppendFormat("Volume label: {0}", xmlFSType.VolumeName).AppendLine(); if(xmlFSType.Bootable) { sb.AppendLine("Volume is bootable"); sb.AppendFormat("Boot code's SHA1: {0}", bootChk).AppendLine(); } information = sb.ToString(); } /// /// BIOS Parameter Block as used by Atari ST GEMDOS on FAT12 volumes. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AtariParameterBlock { /// 68000 BRA.S jump or x86 loop [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] jump; /// OEM Name, 6 bytes, space-padded, "Loader" for Atari ST boot loader [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] oem_name; /// Volume serial number [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] serial_no; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT (inclusive) public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor, unused by GEMDOS public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB, unused by GEMDOS public ushort hsectors; /// Word to be loaded in the cmdload system variable. Big-endian. public ushort execflag; /// Word indicating load mode. If zero, file named is located and loaded. It not, sectors specified in and are loaded. Big endian. public ushort ldmode; /// Starting sector of boot code. public ushort ssect; /// Count of sectors of boot code. public ushort sectcnt; /// Address where boot code should be loaded. public ushort ldaaddr; /// Padding. public ushort padding; /// Address where FAT and root directory sectors must be loaded. public ushort fatbuf; /// Unknown. public ushort unknown; /// Filename to be loaded for booting. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] fname; /// Reserved public ushort reserved; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 455)] public byte[] boot_code; /// Big endian word to make big endian sum of all sector words be equal to 0x1234 if disk is bootable. public ushort checksum; } /// /// BIOS Parameter Block as used by MSX-DOS 2. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] struct MSXParameterBlock { /// x86 loop [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT (inclusive) public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public ushort hsectors; /// Jump for MSX-DOS 1 boot code public ushort msxdos_jmp; /// Set to "VOL_ID" by MSX-DOS 2 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] vol_id; /// Bigger than 0 if there are deleted files (MSX-DOS 2) public byte undelete_flag; /// Volume serial number (MSX-DOS 2) public uint serial_no; /// Reserved (MSX-DOS 2) [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public byte[] reserved; /// Jump for MSX-DOS 2 boot code (MSX-DOS 2) public ushort msxdos2_jmp; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 460)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// DOS 2.0 BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlock2 { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 486)] public byte[] boot_code; /// 0x55 0xAA if bootable. public ushort boot_signature; } /// DOS 3.0 BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlock30 { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public ushort hsectors; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 480)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// DOS 3.2 BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlock32 { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public ushort hsectors; /// Total sectors including hidden ones public ushort total_sectors; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 478)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// DOS 3.31 BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlock33 { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public uint hsectors; /// Sectors in volume if > 65535 public uint big_sectors; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 474)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// DOS 3.4 BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlockShortEBPB { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public uint hsectors; /// Sectors in volume if > 65535 public uint big_sectors; /// Drive number public byte drive_no; /// Volume flags public byte flags; /// EPB signature, 0x28 public byte signature; /// Volume serial number public uint serial_no; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 467)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// DOS 4.0 or higher BIOS Parameter Block. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BIOSParameterBlockEBPB { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public uint hsectors; /// Sectors in volume if > 65535 public uint big_sectors; /// Drive number public byte drive_no; /// Volume flags public byte flags; /// EPB signature, 0x29 public byte signature; /// Volume serial number public uint serial_no; /// Volume label, 11 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] volume_label; /// Filesystem type, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] fs_type; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 448)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// FAT32 Parameter Block [StructLayout(LayoutKind.Sequential, Pack = 1)] struct FAT32ParameterBlockShort { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory, set to 0 public ushort root_ent; /// Sectors in volume, set to 0 public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT, set to 0 public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public uint hsectors; /// Sectors in volume public uint big_sectors; /// Sectors per FAT public uint big_spfat; /// FAT flags public ushort mirror_flags; /// FAT32 version public ushort version; /// Cluster of root directory public uint root_cluster; /// Sector of FSINFO structure public ushort fsinfo_sector; /// Sector of FAT32PB backup public ushort backup_sector; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] public byte[] reserved; /// Drive number public byte drive_no; /// Volume flags public byte flags; /// Signature, should be 0x28 public byte signature; /// Volume serial number public uint serial_no; /// Volume label, 11 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] reserved2; /// Sectors in volume if equals 0 public ulong huge_sectors; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 420)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// FAT32 Parameter Block [StructLayout(LayoutKind.Sequential, Pack = 1)] struct FAT32ParameterBlock { /// x86 jump [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] jump; /// OEM Name, 8 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] oem_name; /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory, set to 0 public ushort root_ent; /// Sectors in volume, set to 0 public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT, set to 0 public ushort spfat; /// Sectors per track public ushort sptrk; /// Heads public ushort heads; /// Hidden sectors before BPB public uint hsectors; /// Sectors in volume public uint big_sectors; /// Sectors per FAT public uint big_spfat; /// FAT flags public ushort mirror_flags; /// FAT32 version public ushort version; /// Cluster of root directory public uint root_cluster; /// Sector of FSINFO structure public ushort fsinfo_sector; /// Sector of FAT32PB backup public ushort backup_sector; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] public byte[] reserved; /// Drive number public byte drive_no; /// Volume flags public byte flags; /// Signature, should be 0x29 public byte signature; /// Volume serial number public uint serial_no; /// Volume label, 11 bytes, space-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] volume_label; /// Filesystem type, 8 bytes, space-padded, must be "FAT32 " [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] fs_type; /// Boot code. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 419)] public byte[] boot_code; /// Always 0x55 0xAA. public ushort boot_signature; } /// Apricot Label. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ApricotLabel { /// Version of format which created disk [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] version; /// Operating system. public byte operatingSystem; /// Software write protection. [MarshalAs(UnmanagedType.U1)] public bool writeProtected; /// Copy protected. [MarshalAs(UnmanagedType.U1)] public bool copyProtected; /// Boot type. public byte bootType; /// Partitions. public byte partitionCount; /// Is hard disk?. [MarshalAs(UnmanagedType.U1)] public bool winchester; /// Sector size. public ushort sectorSize; /// Sectors per track. public ushort spt; /// Tracks per side. public uint cylinders; /// Sides. public byte heads; /// Interleave factor. public byte interleave; /// Skew factor. public ushort skew; /// Sector where boot code starts. public uint bootLocation; /// Size in sectors of boot code. public ushort bootSize; /// Address at which to load boot code. public uint bootAddress; /// Offset where to jump to boot. public ushort bootOffset; /// Segment where to jump to boot. public ushort bootSegment; /// First data sector. public uint firstDataBlock; /// Generation. public ushort generation; /// Copy count. public ushort copyCount; /// Maximum number of copies. public ushort maxCopies; /// Serial number. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] serialNumber; /// Part number. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] partNumber; /// Copyright. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] public byte[] copyright; /// BPB for whole disk. public ApricotParameterBlock mainBPB; /// Name of FONT file. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] fontName; /// Name of KEYBOARD file. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] keyboardName; /// Minor BIOS version. public byte biosMinorVersion; /// Major BIOS version. public byte biosMajorVersion; /// Diagnostics enabled?. [MarshalAs(UnmanagedType.U1)] public bool diagnosticsFlag; /// Printer device. public byte prnDevice; /// Bell volume. public byte bellVolume; /// Cache enabled?. [MarshalAs(UnmanagedType.U1)] public bool enableCache; /// Graphics enabled?. [MarshalAs(UnmanagedType.U1)] public bool enableGraphics; /// Length in sectors of DOS. public byte dosLength; /// Length in sectors of FONT file. public byte fontLength; /// Length in sectors of KEYBOARD file. public byte keyboardLength; /// Starting sector of DOS. public ushort dosStart; /// Starting sector of FONT file. public ushort fontStart; /// Starting sector of KEYBOARD file. public ushort keyboardStart; /// Keyboard click volume. public byte keyboardVolume; /// Auto-repeat enabled?. [MarshalAs(UnmanagedType.U1)] public bool autorepeat; /// Auto-repeat lead-in. public byte autorepeatLeadIn; /// Auto-repeat interval. public byte autorepeatInterval; /// Microscreen mode. public byte microscreenMode; /// Spare area for keyboard values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] spareKeyboard; /// Screen line mode. public byte lineMode; /// Screen line width. public byte lineWidth; /// Screen disabled?. [MarshalAs(UnmanagedType.U1)] public bool imageOff; /// Spare area for screen values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public byte[] spareScreen; /// TX baud rate. public byte txBaudRate; /// RX baud rate. public byte rxBaudRate; /// TX bits. public byte txBits; /// RX bits. public byte rxBits; /// Stop bits. public byte stopBits; /// Parity enabled?. [MarshalAs(UnmanagedType.U1)] public bool parityCheck; /// Parity type. public byte parityType; /// Xon/Xoff enabled on TX. [MarshalAs(UnmanagedType.U1)] public bool txXonXoff; /// Xon/Xoff enabled on RX. [MarshalAs(UnmanagedType.U1)] public bool rxXonXoff; /// Xon character. public byte xonCharacter; /// Xoff character. public byte xoffCharacter; /// Xon/Xoff buffer on RX. public ushort rxXonXoffBuffer; /// DTR/DSR enabled?. [MarshalAs(UnmanagedType.U1)] public bool dtrDsr; /// CTS/RTS enabled?. [MarshalAs(UnmanagedType.U1)] public bool ctsRts; /// NULLs after CR. public byte nullsAfterCr; /// NULLs after 0xFF. public byte nullsAfterFF; /// Send LF after CR in serial port. [MarshalAs(UnmanagedType.U1)] public bool lfAfterCRSerial; /// BIOS error report in serial port. [MarshalAs(UnmanagedType.U1)] public bool biosErrorReportSerial; /// Spare area for serial port values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public byte[] spareSerial; /// Send LF after CR in parallel port. [MarshalAs(UnmanagedType.U1)] public bool lfAfterCrParallel; /// Select line supported?. [MarshalAs(UnmanagedType.U1)] public bool selectLine; /// Paper empty supported?. [MarshalAs(UnmanagedType.U1)] public bool paperEmpty; /// Fault line supported?. [MarshalAs(UnmanagedType.U1)] public bool faultLine; /// BIOS error report in parallel port. [MarshalAs(UnmanagedType.U1)] public bool biosErrorReportParallel; /// Spare area for parallel port values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public byte[] spareParallel; /// Spare area for Winchester values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] public byte[] spareWinchester; /// Parking enabled?. [MarshalAs(UnmanagedType.U1)] public bool parkingEnabled; /// Format protection?. [MarshalAs(UnmanagedType.U1)] public bool formatProtection; /// Spare area for RAM disk values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] spareRamDisk; /// List of bad blocks. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public ushort[] badBlocks; /// Array of partition BPBs. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public ApricotParameterBlock[] partitions; /// Spare area. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 63)] public byte[] spare; /// CP/M double side indicator?. public bool cpmDoubleSided; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ApricotParameterBlock { /// Bytes per sector public ushort bps; /// Sectors per cluster public byte spc; /// Reserved sectors between BPB and FAT public ushort rsectors; /// Number of FATs public byte fats_no; /// Number of entries on root directory public ushort root_ent; /// Sectors in volume public ushort sectors; /// Media descriptor public byte media; /// Sectors per FAT public ushort spfat; /// Disk type public byte diskType; /// Volume starting sector public ushort startSector; } const uint fsinfo_signature1 = 0x41615252; const uint fsinfo_signature2 = 0x61417272; const uint fsinfo_signature3 = 0xAA550000; /// FAT32 FS Information Sector [StructLayout(LayoutKind.Sequential, Pack = 1)] struct FSInfoSector { /// Signature must be public uint signature1; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 480)] public byte[] reserved1; /// Signature must be public uint signature2; /// Free clusters public uint free_clusters; /// cated cluster public uint last_cluster; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] public byte[] reserved2; /// Signature must be public uint signature3; } [Flags] enum FatAttributes : byte { ReadOnly = 0x01, Hidden = 0x02, System = 0x04, VolumeLabel = 0x08, Subdirectory = 0x10, Archive = 0x20, Device = 0x40, Reserved = 0x80, LFN = 0x0F } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct DirectoryEntry { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] filename; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] extension; public FatAttributes attributes; public byte caseinfo; public byte ctime_ms; public ushort ctime; public ushort cdate; public ushort adate; public ushort ea_handle; public ushort mtime; public ushort mdate; public ushort start_cluster; public uint size; } 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 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 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; } } }