// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // using System; using System.Globalization; using DiscUtils.Streams; namespace DiscUtils { /// /// Class whose instances represent disk geometries. /// /// Instances of this class are immutable. public sealed class Geometry { /// /// Initializes a new instance of the Geometry class. The default 512 bytes per sector is assumed. /// /// The number of cylinders of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. public Geometry(int cylinders, int headsPerCylinder, int sectorsPerTrack) { Cylinders = cylinders; HeadsPerCylinder = headsPerCylinder; SectorsPerTrack = sectorsPerTrack; BytesPerSector = 512; } /// /// Initializes a new instance of the Geometry class. /// /// The number of cylinders of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. /// The number of bytes per sector of the disk. public Geometry(int cylinders, int headsPerCylinder, int sectorsPerTrack, int bytesPerSector) { Cylinders = cylinders; HeadsPerCylinder = headsPerCylinder; SectorsPerTrack = sectorsPerTrack; BytesPerSector = bytesPerSector; } /// /// Initializes a new instance of the Geometry class. /// /// The total capacity of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. /// The number of bytes per sector of the disk. public Geometry(long capacity, int headsPerCylinder, int sectorsPerTrack, int bytesPerSector) { Cylinders = (int)(capacity / (headsPerCylinder * (long)sectorsPerTrack * bytesPerSector)); HeadsPerCylinder = headsPerCylinder; SectorsPerTrack = sectorsPerTrack; BytesPerSector = bytesPerSector; } /// /// Gets the number of bytes in each sector. /// public int BytesPerSector { get; } /// /// Gets the total capacity of the disk (in bytes). /// public long Capacity { get { return TotalSectorsLong * BytesPerSector; } } /// /// Gets the number of cylinders. /// public int Cylinders { get; } /// /// Gets the number of heads (aka platters). /// public int HeadsPerCylinder { get; } /// /// Gets a value indicating whether the Geometry is representable both by the BIOS and by IDE. /// public bool IsBiosAndIdeSafe { get { return Cylinders <= 1024 && HeadsPerCylinder <= 16 && SectorsPerTrack <= 63; } } /// /// Gets a value indicating whether the Geometry is consistent with the values a BIOS can support. /// public bool IsBiosSafe { get { return Cylinders <= 1024 && HeadsPerCylinder <= 255 && SectorsPerTrack <= 63; } } /// /// Gets a value indicating whether the Geometry is consistent with the values IDE can represent. /// public bool IsIdeSafe { get { return Cylinders <= 65536 && HeadsPerCylinder <= 16 && SectorsPerTrack <= 255; } } /// /// Gets the address of the last sector on the disk. /// public ChsAddress LastSector { get { return new ChsAddress(Cylinders - 1, HeadsPerCylinder - 1, SectorsPerTrack); } } /// /// Gets a null geometry, which has 512-byte sectors but zero sectors, tracks or cylinders. /// public static Geometry Null { get { return new Geometry(0, 0, 0, 512); } } /// /// Gets the number of sectors per track. /// public int SectorsPerTrack { get; } /// /// Gets the total size of the disk (in sectors). /// [Obsolete("Use TotalSectorsLong instead, to support very large disks.")] public int TotalSectors { get { return Cylinders * HeadsPerCylinder * SectorsPerTrack; } } /// /// Gets the total size of the disk (in sectors). /// public long TotalSectorsLong { get { return Cylinders * (long)HeadsPerCylinder * SectorsPerTrack; } } /// /// Gets the 'Large' BIOS geometry for a disk, given it's physical geometry. /// /// The physical (aka IDE) geometry of the disk. /// The geometry a BIOS using the 'Large' method for calculating disk geometry will indicate for the disk. public static Geometry LargeBiosGeometry(Geometry ideGeometry) { int cylinders = ideGeometry.Cylinders; int heads = ideGeometry.HeadsPerCylinder; int sectors = ideGeometry.SectorsPerTrack; while (cylinders > 1024 && heads <= 127) { cylinders >>= 1; heads <<= 1; } return new Geometry(cylinders, heads, sectors); } /// /// Gets the 'LBA Assisted' BIOS geometry for a disk, given it's capacity. /// /// The capacity of the disk. /// The geometry a BIOS using the 'LBA Assisted' method for calculating disk geometry will indicate for the disk. public static Geometry LbaAssistedBiosGeometry(long capacity) { int heads; if (capacity <= 504 * Sizes.OneMiB) { heads = 16; } else if (capacity <= 1008 * Sizes.OneMiB) { heads = 32; } else if (capacity <= 2016 * Sizes.OneMiB) { heads = 64; } else if (capacity <= 4032 * Sizes.OneMiB) { heads = 128; } else { heads = 255; } int sectors = 63; int cylinders = (int)Math.Min(1024, capacity / (sectors * (long)heads * Sizes.Sector)); return new Geometry(cylinders, heads, sectors, Sizes.Sector); } /// /// Converts a geometry into one that is BIOS-safe, if not already. /// /// The geometry to make BIOS-safe. /// The capacity of the disk. /// The new geometry. /// This method returns the LBA-Assisted geometry if the given geometry isn't BIOS-safe. public static Geometry MakeBiosSafe(Geometry geometry, long capacity) { if (geometry == null) { return LbaAssistedBiosGeometry(capacity); } if (geometry.IsBiosSafe) { return geometry; } return LbaAssistedBiosGeometry(capacity); } /// /// Calculates a sensible disk geometry for a disk capacity using the VHD algorithm (errs under). /// /// The desired capacity of the disk. /// The appropriate disk geometry. /// The geometry returned tends to produce a disk with less capacity /// than requested (an exact capacity is not always possible). The geometry returned is the IDE /// (aka Physical) geometry of the disk, not necessarily the geometry used by the BIOS. public static Geometry FromCapacity(long capacity) { return FromCapacity(capacity, Sizes.Sector); } /// /// Calculates a sensible disk geometry for a disk capacity using the VHD algorithm (errs under). /// /// The desired capacity of the disk. /// The logical sector size of the disk. /// The appropriate disk geometry. /// The geometry returned tends to produce a disk with less capacity /// than requested (an exact capacity is not always possible). The geometry returned is the IDE /// (aka Physical) geometry of the disk, not necessarily the geometry used by the BIOS. public static Geometry FromCapacity(long capacity, int sectorSize) { int totalSectors; int cylinders; int headsPerCylinder; int sectorsPerTrack; // If more than ~128GB truncate at ~128GB if (capacity > 65535 * (long)16 * 255 * sectorSize) { totalSectors = 65535 * 16 * 255; } else { totalSectors = (int)(capacity / sectorSize); } // If more than ~32GB, break partition table compatibility. // Partition table has max 63 sectors per track. Otherwise // we're looking for a geometry that's valid for both BIOS // and ATA. if (totalSectors > 65535 * 16 * 63) { sectorsPerTrack = 255; headsPerCylinder = 16; } else { sectorsPerTrack = 17; int cylindersTimesHeads = totalSectors / sectorsPerTrack; headsPerCylinder = (cylindersTimesHeads + 1023) / 1024; if (headsPerCylinder < 4) { headsPerCylinder = 4; } // If we need more than 1023 cylinders, or 16 heads, try more sectors per track if (cylindersTimesHeads >= headsPerCylinder * 1024U || headsPerCylinder > 16) { sectorsPerTrack = 31; headsPerCylinder = 16; cylindersTimesHeads = totalSectors / sectorsPerTrack; } // We need 63 sectors per track to keep the cylinder count down if (cylindersTimesHeads >= headsPerCylinder * 1024U) { sectorsPerTrack = 63; headsPerCylinder = 16; } } cylinders = totalSectors / sectorsPerTrack / headsPerCylinder; return new Geometry(cylinders, headsPerCylinder, sectorsPerTrack, sectorSize); } /// /// Converts a CHS (Cylinder,Head,Sector) address to a LBA (Logical Block Address). /// /// The CHS address to convert. /// The Logical Block Address (in sectors). public long ToLogicalBlockAddress(ChsAddress chsAddress) { return ToLogicalBlockAddress(chsAddress.Cylinder, chsAddress.Head, chsAddress.Sector); } /// /// Converts a CHS (Cylinder,Head,Sector) address to a LBA (Logical Block Address). /// /// The cylinder of the address. /// The head of the address. /// The sector of the address. /// The Logical Block Address (in sectors). public long ToLogicalBlockAddress(int cylinder, int head, int sector) { if (cylinder < 0) { throw new ArgumentOutOfRangeException(nameof(cylinder), cylinder, "cylinder number is negative"); } if (head >= HeadsPerCylinder) { throw new ArgumentOutOfRangeException(nameof(head), head, "head number is larger than disk geometry"); } if (head < 0) { throw new ArgumentOutOfRangeException(nameof(head), head, "head number is negative"); } if (sector > SectorsPerTrack) { throw new ArgumentOutOfRangeException(nameof(sector), sector, "sector number is larger than disk geometry"); } if (sector < 1) { throw new ArgumentOutOfRangeException(nameof(sector), sector, "sector number is less than one (sectors are 1-based)"); } return (cylinder * (long)HeadsPerCylinder + head) * SectorsPerTrack + sector - 1; } /// /// Converts a LBA (Logical Block Address) to a CHS (Cylinder, Head, Sector) address. /// /// The logical block address (in sectors). /// The address in CHS form. public ChsAddress ToChsAddress(long logicalBlockAddress) { if (logicalBlockAddress < 0) { throw new ArgumentOutOfRangeException(nameof(logicalBlockAddress), logicalBlockAddress, "Logical Block Address is negative"); } int cylinder = (int)(logicalBlockAddress / (HeadsPerCylinder * SectorsPerTrack)); int temp = (int)(logicalBlockAddress % (HeadsPerCylinder * SectorsPerTrack)); int head = temp / SectorsPerTrack; int sector = temp % SectorsPerTrack + 1; return new ChsAddress(cylinder, head, sector); } /// /// Translates an IDE (aka Physical) geometry to a BIOS (aka Logical) geometry. /// /// The translation to perform. /// The translated disk geometry. public Geometry TranslateToBios(GeometryTranslation translation) { return TranslateToBios(0, translation); } /// /// Translates an IDE (aka Physical) geometry to a BIOS (aka Logical) geometry. /// /// The capacity of the disk, required if the geometry is an approximation on the actual disk size. /// The translation to perform. /// The translated disk geometry. public Geometry TranslateToBios(long capacity, GeometryTranslation translation) { if (capacity <= 0) { capacity = TotalSectorsLong * 512L; } switch (translation) { case GeometryTranslation.None: return this; case GeometryTranslation.Auto: if (IsBiosSafe) { return this; } return LbaAssistedBiosGeometry(capacity); case GeometryTranslation.Lba: return LbaAssistedBiosGeometry(capacity); case GeometryTranslation.Large: return LargeBiosGeometry(this); default: throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, "Translation mode '{0}' not yet implemented", translation), nameof(translation)); } } /// /// Determines if this object is equivalent to another. /// /// The object to test against. /// true if the is equivalent, else false. public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) { return false; } Geometry other = (Geometry)obj; return Cylinders == other.Cylinders && HeadsPerCylinder == other.HeadsPerCylinder && SectorsPerTrack == other.SectorsPerTrack && BytesPerSector == other.BytesPerSector; } /// /// Calculates the hash code for this object. /// /// The hash code. public override int GetHashCode() { return Cylinders.GetHashCode() ^ HeadsPerCylinder.GetHashCode() ^ SectorsPerTrack.GetHashCode() ^ BytesPerSector.GetHashCode(); } /// /// Gets a string representation of this object, in the form (C/H/S). /// /// The string representation. public override string ToString() { if (BytesPerSector == 512) { return "(" + Cylinders + "/" + HeadsPerCylinder + "/" + SectorsPerTrack + ")"; } return "(" + Cylinders + "/" + HeadsPerCylinder + "/" + SectorsPerTrack + ":" + BytesPerSector + ")"; } } }