// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Apricot.cs // Author(s) : Natalia Portillo // // Component : Partitioning scheme plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages ACT Apricot partitions. // // --[ 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-2020 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Aaru.CommonTypes; using Aaru.CommonTypes.Interfaces; using Aaru.Console; using Marshal = Aaru.Helpers.Marshal; namespace Aaru.Partitions { public class Apricot : IPartition { readonly int[] baudRates = { 50, 75, 110, 134, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 19200 }; readonly string[] bootTypeCodes = { "Non-bootable", "Apricot & XI RAM BIOS", "Generic ROM BIOS", "Apricot & XI ROM BIOS", "Apricot Portable ROM BIOS", "Apricot F1 ROM BIOS" }; readonly string[] diskTypeCodes = { "MF1DD 70-track", "MF1DD", "MF2DD", "Winchester 5M", "Winchester 10M", "Winchester 20M" }; readonly int[] lineModes = { 256, 200 }; readonly int[] lineWidths = { 80, 40 }; readonly string[] operatingSystemCodes = { "Invalid", "MS-DOS", "UCSD Pascal", "CP/M", "Concurrent CP/M" }; readonly string[] parityTypes = { "None", "Odd", "Even", "Mark", "Space" }; readonly string[] printDevices = { "Parallel", "Serial" }; readonly double[] stopBits = { 1, 1.5, 2 }; public string Name => "ACT Apricot partitions"; public Guid Id => new Guid("8CBF5864-7B5A-47A0-8CEB-199C74FA22DE"); public string Author => "Natalia Portillo"; public bool GetInformation(IMediaImage imagePlugin, out List partitions, ulong sectorOffset) { partitions = new List(); // I think Apricot can't chain partitions so. if(sectorOffset != 0) return false; byte[] sector = imagePlugin.ReadSector(0); if(sector.Length < 512) return false; ApricotLabel label = Marshal.ByteArrayToStructureLittleEndian(sector); // Not much to check but... ulong deviceSectors = imagePlugin.Info.Sectors; ulong deviceSizeAccordingToLabel = label.cylinders * label.heads * label.spt; if(label.operatingSystem > 4 || label.bootType > 5 || label.partitionCount > 8 || deviceSizeAccordingToLabel > deviceSectors || label.firstDataBlock > deviceSectors) return false; AaruConsole.DebugWriteLine("Apricot partitions", "label.version = \"{0}\"", StringHandlers.CToString(label.version)); AaruConsole.DebugWriteLine("Apricot partitions", "label.operatingSystem = {0} ({1})", label.operatingSystem, label.operatingSystem < operatingSystemCodes.Length ? operatingSystemCodes[label.operatingSystem] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.writeProtected = {0}", label.writeProtected); AaruConsole.DebugWriteLine("Apricot partitions", "label.copyProtected = {0}", label.copyProtected); AaruConsole.DebugWriteLine("Apricot partitions", "label.bootType = {0} ({1})", label.bootType, label.bootType < bootTypeCodes.Length ? bootTypeCodes[label.bootType] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitionCount = {0}", label.partitionCount); AaruConsole.DebugWriteLine("Apricot partitions", "label.winchester = {0}", label.winchester); AaruConsole.DebugWriteLine("Apricot partitions", "label.sectorSize = {0}", label.sectorSize); AaruConsole.DebugWriteLine("Apricot partitions", "label.spt = {0}", label.spt); AaruConsole.DebugWriteLine("Apricot partitions", "label.cylinders = {0}", label.cylinders); AaruConsole.DebugWriteLine("Apricot partitions", "label.heads = {0}", label.heads); AaruConsole.DebugWriteLine("Apricot partitions", "label.interleave = {0}", label.interleave); AaruConsole.DebugWriteLine("Apricot partitions", "label.skew = {0}", label.skew); AaruConsole.DebugWriteLine("Apricot partitions", "label.bootLocation = {0}", label.bootLocation); AaruConsole.DebugWriteLine("Apricot partitions", "label.bootSize = {0}", label.bootSize); AaruConsole.DebugWriteLine("Apricot partitions", "label.bootAddress = 0x{0:X8}", label.bootAddress); AaruConsole.DebugWriteLine("Apricot partitions", "label.bootOffset:label.bootSegment = {0:X4}:{1:X4}", label.bootOffset, label.bootSegment); AaruConsole.DebugWriteLine("Apricot partitions", "label.firstDataBlock = {0}", label.firstDataBlock); AaruConsole.DebugWriteLine("Apricot partitions", "label.generation = {0}", label.generation); AaruConsole.DebugWriteLine("Apricot partitions", "label.copyCount = {0}", label.copyCount); AaruConsole.DebugWriteLine("Apricot partitions", "label.maxCopies = {0}", label.maxCopies); AaruConsole.DebugWriteLine("Apricot partitions", "label.serialNumber = \"{0}\"", StringHandlers.CToString(label.serialNumber)); AaruConsole.DebugWriteLine("Apricot partitions", "label.partNumber = \"{0}\"", StringHandlers.CToString(label.partNumber)); AaruConsole.DebugWriteLine("Apricot partitions", "label.copyright = \"{0}\"", StringHandlers.CToString(label.copyright)); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.bps = {0}", label.mainBPB.bps); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.spc = {0}", label.mainBPB.spc); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.rsectors = {0}", label.mainBPB.rsectors); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.fats_no = {0}", label.mainBPB.fats_no); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.root_ent = {0}", label.mainBPB.root_ent); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.sectors = {0}", label.mainBPB.sectors); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.media = {0}", label.mainBPB.media); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.spfat = {0}", label.mainBPB.spfat); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.diskType = {0} ({1})", label.mainBPB.diskType, label.mainBPB.diskType < diskTypeCodes.Length ? diskTypeCodes[label.mainBPB.diskType] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.mainBPB.startSector = {0}", label.mainBPB.startSector); AaruConsole.DebugWriteLine("Apricot partitions", "label.fontName = \"{0}\"", StringHandlers.CToString(label.fontName)); AaruConsole.DebugWriteLine("Apricot partitions", "label.keyboardName = \"{0}\"", StringHandlers.CToString(label.keyboardName)); AaruConsole.DebugWriteLine("Apricot partitions", "label.biosMajorVersion = {0}", label.biosMajorVersion); AaruConsole.DebugWriteLine("Apricot partitions", "label.biosMinorVersion = {0}", label.biosMinorVersion); AaruConsole.DebugWriteLine("Apricot partitions", "label.diagnosticsFlag = {0}", label.diagnosticsFlag); AaruConsole.DebugWriteLine("Apricot partitions", "label.prnDevice = {0} ({1})", label.prnDevice, label.prnDevice < printDevices.Length ? printDevices[label.prnDevice] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.bellVolume = {0}", label.bellVolume); AaruConsole.DebugWriteLine("Apricot partitions", "label.enableCache = {0}", label.enableCache); AaruConsole.DebugWriteLine("Apricot partitions", "label.enableGraphics = {0}", label.enableGraphics); AaruConsole.DebugWriteLine("Apricot partitions", "label.dosLength = {0}", label.dosLength); AaruConsole.DebugWriteLine("Apricot partitions", "label.fontLength = {0}", label.fontLength); AaruConsole.DebugWriteLine("Apricot partitions", "label.keyboardLength = {0}", label.keyboardLength); AaruConsole.DebugWriteLine("Apricot partitions", "label.dosStart = {0}", label.dosStart); AaruConsole.DebugWriteLine("Apricot partitions", "label.fontStart = {0}", label.fontStart); AaruConsole.DebugWriteLine("Apricot partitions", "label.keyboardStart = {0}", label.keyboardStart); AaruConsole.DebugWriteLine("Apricot partitions", "label.keyboardVolume = {0}", label.keyboardVolume); AaruConsole.DebugWriteLine("Apricot partitions", "label.autorepeat = {0}", label.autorepeat); AaruConsole.DebugWriteLine("Apricot partitions", "label.autorepeatLeadIn = {0}", label.autorepeatLeadIn); AaruConsole.DebugWriteLine("Apricot partitions", "label.autorepeatInterval = {0}", label.autorepeatInterval); AaruConsole.DebugWriteLine("Apricot partitions", "label.microscreenMode = {0}", label.microscreenMode); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareKeyboard is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareKeyboard)); AaruConsole.DebugWriteLine("Apricot partitions", "label.lineMode = {0} ({1} lines)", label.lineMode, label.lineMode < lineModes.Length ? lineModes[label.lineMode] : 0); AaruConsole.DebugWriteLine("Apricot partitions", "label.lineWidth = {0} ({1} columns)", label.lineWidth, label.lineWidth < lineWidths.Length ? lineWidths[label.lineWidth] : 0); AaruConsole.DebugWriteLine("Apricot partitions", "label.imageOff = {0}", label.imageOff); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareScreen is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareScreen)); AaruConsole.DebugWriteLine("Apricot partitions", "label.txBaudRate = {0} ({1} bps)", label.txBaudRate, label.txBaudRate < baudRates.Length ? baudRates[label.txBaudRate] : 0); AaruConsole.DebugWriteLine("Apricot partitions", "label.rxBaudRate = {0} ({1} bps)", label.rxBaudRate, label.rxBaudRate < baudRates.Length ? baudRates[label.rxBaudRate] : 0); AaruConsole.DebugWriteLine("Apricot partitions", "label.txBits = {0}", label.txBits); AaruConsole.DebugWriteLine("Apricot partitions", "label.rxBits = {0}", label.rxBits); AaruConsole.DebugWriteLine("Apricot partitions", "label.stopBits = {0} ({1} bits)", label.stopBits, label.stopBits < stopBits.Length ? stopBits[label.stopBits] : 0); AaruConsole.DebugWriteLine("Apricot partitions", "label.parityCheck = {0}", label.parityCheck); AaruConsole.DebugWriteLine("Apricot partitions", "label.parityType = {0} ({1})", label.parityType, label.parityType < parityTypes.Length ? parityTypes[label.parityType] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.txXonXoff = {0}", label.txXonXoff); AaruConsole.DebugWriteLine("Apricot partitions", "label.rxXonXoff = {0}", label.rxXonXoff); AaruConsole.DebugWriteLine("Apricot partitions", "label.xonCharacter = {0}", label.xonCharacter); AaruConsole.DebugWriteLine("Apricot partitions", "label.xoffCharacter = {0}", label.xoffCharacter); AaruConsole.DebugWriteLine("Apricot partitions", "label.rxXonXoffBuffer = {0}", label.rxXonXoffBuffer); AaruConsole.DebugWriteLine("Apricot partitions", "label.dtrDsr = {0}", label.dtrDsr); AaruConsole.DebugWriteLine("Apricot partitions", "label.ctsRts = {0}", label.ctsRts); AaruConsole.DebugWriteLine("Apricot partitions", "label.nullsAfterCr = {0}", label.nullsAfterCr); AaruConsole.DebugWriteLine("Apricot partitions", "label.nullsAfterFF = {0}", label.nullsAfterFF); AaruConsole.DebugWriteLine("Apricot partitions", "label.lfAfterCRSerial = {0}", label.lfAfterCRSerial); AaruConsole.DebugWriteLine("Apricot partitions", "label.biosErrorReportSerial = {0}", label.biosErrorReportSerial); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareSerial is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareSerial)); AaruConsole.DebugWriteLine("Apricot partitions", "label.lfAfterCrParallel = {0}", label.lfAfterCrParallel); AaruConsole.DebugWriteLine("Apricot partitions", "label.selectLine = {0}", label.selectLine); AaruConsole.DebugWriteLine("Apricot partitions", "label.paperEmpty = {0}", label.paperEmpty); AaruConsole.DebugWriteLine("Apricot partitions", "label.faultLine = {0}", label.faultLine); AaruConsole.DebugWriteLine("Apricot partitions", "label.biosErrorReportParallel = {0}", label.biosErrorReportParallel); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareParallel is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareParallel)); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareWinchester is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareWinchester)); AaruConsole.DebugWriteLine("Apricot partitions", "label.parkingEnabled = {0}", label.parkingEnabled); AaruConsole.DebugWriteLine("Apricot partitions", "label.formatProtection = {0}", label.formatProtection); AaruConsole.DebugWriteLine("Apricot partitions", "label.spareRamDisk is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spareRamDisk)); for(int i = 0; i < 32; i++) AaruConsole.DebugWriteLine("Apricot partitions", "label.badBlocks[{1}] = {0}", label.badBlocks[i], i); for(int i = 0; i < 8; i++) { AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].bps = {0}", label.partitions[i].bps, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].spc = {0}", label.partitions[i].spc, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].rsectors = {0}", label.partitions[i].rsectors, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].fats_no = {0}", label.partitions[i].fats_no, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].root_ent = {0}", label.partitions[i].root_ent, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].sectors = {0}", label.partitions[i].sectors, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].media = {0}", label.partitions[i].media, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].spfat = {0}", label.partitions[i].spfat, i); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].diskType = {0} ({2})", label.partitions[i].diskType, i, label.partitions[i].diskType < diskTypeCodes.Length ? diskTypeCodes[label.partitions[i].diskType] : "Unknown"); AaruConsole.DebugWriteLine("Apricot partitions", "label.partitions[{1}].startSector = {0}", label.partitions[i].startSector, i); } AaruConsole.DebugWriteLine("Apricot partitions", "label.spare is null? = {0}", ArrayHelpers.ArrayIsNullOrEmpty(label.spare)); AaruConsole.DebugWriteLine("Apricot partitions", "label.cpmDoubleSided = {0}", label.cpmDoubleSided); // Only hard disks can contain partitions if(!label.winchester) return false; for(byte i = 0; i < label.partitionCount; i++) { var part = new Partition { Start = label.partitions[i].startSector, Size = (ulong)(label.partitions[i].sectors * label.sectorSize), Length = label.partitions[i].sectors, Type = "ACT Apricot partition", Sequence = i, Scheme = Name, Offset = (ulong)(label.partitions[i].startSector * label.sectorSize) }; if(part.Start < deviceSectors && part.End < deviceSectors) partitions.Add(part); } return partitions.Count > 0; } /// Apricot Label. [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ApricotLabel { /// Version of format which created disk [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly byte[] version; /// Operating system. public readonly byte operatingSystem; /// Software write protection. [MarshalAs(UnmanagedType.U1)] public readonly bool writeProtected; /// Copy protected. [MarshalAs(UnmanagedType.U1)] public readonly bool copyProtected; /// Boot type. public readonly byte bootType; /// Partitions. public readonly byte partitionCount; /// Is hard disk?. [MarshalAs(UnmanagedType.U1)] public readonly bool winchester; /// Sector size. public readonly ushort sectorSize; /// Sectors per track. public readonly ushort spt; /// Tracks per side. public readonly uint cylinders; /// Sides. public readonly byte heads; /// Interleave factor. public readonly byte interleave; /// Skew factor. public readonly ushort skew; /// Sector where boot code starts. public readonly uint bootLocation; /// Size in sectors of boot code. public readonly ushort bootSize; /// Address at which to load boot code. public readonly uint bootAddress; /// Offset where to jump to boot. public readonly ushort bootOffset; /// Segment where to jump to boot. public readonly ushort bootSegment; /// First data sector. public readonly uint firstDataBlock; /// Generation. public readonly ushort generation; /// Copy count. public readonly ushort copyCount; /// Maximum number of copies. public readonly ushort maxCopies; /// Serial number. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly byte[] serialNumber; /// Part number. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly byte[] partNumber; /// Copyright. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] public readonly byte[] copyright; /// BPB for whole disk. public readonly ApricotParameterBlock mainBPB; /// Name of FONT file. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public readonly byte[] fontName; /// Name of KEYBOARD file. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public readonly byte[] keyboardName; /// Minor BIOS version. public readonly byte biosMinorVersion; /// Major BIOS version. public readonly byte biosMajorVersion; /// Diagnostics enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool diagnosticsFlag; /// Printer device. public readonly byte prnDevice; /// Bell volume. public readonly byte bellVolume; /// Cache enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool enableCache; /// Graphics enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool enableGraphics; /// Length in sectors of DOS. public readonly byte dosLength; /// Length in sectors of FONT file. public readonly byte fontLength; /// Length in sectors of KEYBOARD file. public readonly byte keyboardLength; /// Starting sector of DOS. public readonly ushort dosStart; /// Starting sector of FONT file. public readonly ushort fontStart; /// Starting sector of KEYBOARD file. public readonly ushort keyboardStart; /// Keyboard click volume. public readonly byte keyboardVolume; /// Auto-repeat enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool autorepeat; /// Auto-repeat lead-in. public readonly byte autorepeatLeadIn; /// Auto-repeat interval. public readonly byte autorepeatInterval; /// Microscreen mode. public readonly byte microscreenMode; /// Spare area for keyboard values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public readonly byte[] spareKeyboard; /// Screen line mode. public readonly byte lineMode; /// Screen line width. public readonly byte lineWidth; /// Screen disabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool imageOff; /// Spare area for screen values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public readonly byte[] spareScreen; /// TX baud rate. public readonly byte txBaudRate; /// RX baud rate. public readonly byte rxBaudRate; /// TX bits. public readonly byte txBits; /// RX bits. public readonly byte rxBits; /// Stop bits. public readonly byte stopBits; /// Parity enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool parityCheck; /// Parity type. public readonly byte parityType; /// Xon/Xoff enabled on TX. [MarshalAs(UnmanagedType.U1)] public readonly bool txXonXoff; /// Xon/Xoff enabled on RX. [MarshalAs(UnmanagedType.U1)] public readonly bool rxXonXoff; /// Xon character. public readonly byte xonCharacter; /// Xoff character. public readonly byte xoffCharacter; /// Xon/Xoff buffer on RX. public readonly ushort rxXonXoffBuffer; /// DTR/DSR enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool dtrDsr; /// CTS/RTS enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool ctsRts; /// NULLs after CR. public readonly byte nullsAfterCr; /// NULLs after 0xFF. public readonly byte nullsAfterFF; /// Send LF after CR in serial port. [MarshalAs(UnmanagedType.U1)] public readonly bool lfAfterCRSerial; /// BIOS error report in serial port. [MarshalAs(UnmanagedType.U1)] public readonly bool biosErrorReportSerial; /// Spare area for serial port values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public readonly byte[] spareSerial; /// Send LF after CR in parallel port. [MarshalAs(UnmanagedType.U1)] public readonly bool lfAfterCrParallel; /// Select line supported?. [MarshalAs(UnmanagedType.U1)] public readonly bool selectLine; /// Paper empty supported?. [MarshalAs(UnmanagedType.U1)] public readonly bool paperEmpty; /// Fault line supported?. [MarshalAs(UnmanagedType.U1)] public readonly bool faultLine; /// BIOS error report in parallel port. [MarshalAs(UnmanagedType.U1)] public readonly bool biosErrorReportParallel; /// Spare area for parallel port values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] public readonly byte[] spareParallel; /// Spare area for Winchester values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] public readonly byte[] spareWinchester; /// Parking enabled?. [MarshalAs(UnmanagedType.U1)] public readonly bool parkingEnabled; /// Format protection?. [MarshalAs(UnmanagedType.U1)] public readonly bool formatProtection; /// Spare area for RAM disk values expansion. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public readonly byte[] spareRamDisk; /// List of bad blocks. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public readonly ushort[] badBlocks; /// Array of partition BPBs. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly ApricotParameterBlock[] partitions; /// Spare area. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 63)] public readonly byte[] spare; /// CP/M double side indicator?. public readonly bool cpmDoubleSided; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ApricotParameterBlock { /// Bytes per sector public readonly ushort bps; /// Sectors per cluster public readonly byte spc; /// Reserved sectors between BPB and FAT public readonly ushort rsectors; /// Number of FATs public readonly byte fats_no; /// Number of entries on root directory public readonly ushort root_ent; /// Sectors in volume public readonly ushort sectors; /// Media descriptor public readonly byte media; /// Sectors per FAT public readonly ushort spfat; /// Disk type public readonly byte diskType; /// Volume starting sector public readonly ushort startSector; } } }