//
// 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 + ")";
}
}
}