diff --git a/DiscImageChef/ChangeLog b/DiscImageChef/ChangeLog index cf90f5a6..238a512e 100644 --- a/DiscImageChef/ChangeLog +++ b/DiscImageChef/ChangeLog @@ -1,3 +1,9 @@ +2015-04-22 Natalia Portillo + + * ImagePlugins/VHD.cs: + * DiscImageChef.csproj: + Implemented support for Virtual PC fixed size disk images. + 2015-04-21 Natalia Portillo * BigEndianBitConverter.cs: diff --git a/DiscImageChef/DiscImageChef.csproj b/DiscImageChef/DiscImageChef.csproj index 490f2e78..8d3b5786 100644 --- a/DiscImageChef/DiscImageChef.csproj +++ b/DiscImageChef/DiscImageChef.csproj @@ -113,6 +113,7 @@ + diff --git a/DiscImageChef/ImagePlugins/VHD.cs b/DiscImageChef/ImagePlugins/VHD.cs new file mode 100644 index 00000000..7cbd55dc --- /dev/null +++ b/DiscImageChef/ImagePlugins/VHD.cs @@ -0,0 +1,944 @@ +/*************************************************************************** +The Disc Image Chef +---------------------------------------------------------------------------- + +Filename : VHD.cs +Version : 1.0 +Author(s) : Natalia Portillo + +Component : Disc image plugins + +Revision : $Revision$ +Last change by : $Author$ +Date : $Date$ + +--[ Description ] ---------------------------------------------------------- + +Manages Connectix and Microsoft Virtual PC disk images. + +--[ License ] -------------------------------------------------------------- + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +---------------------------------------------------------------------------- +Copyright (C) 2011-2015 Claunia.com +****************************************************************************/ +//$Id$ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace DiscImageChef.ImagePlugins +{ + /// + /// Supports Connectix/Microsoft Virtual PC hard disk image format + /// Until Virtual PC 5 there existed no format, and the hard disk image was + /// merely a sector by sector (RAW) image with a resource fork giving + /// information to Virtual PC itself. + /// + public class VHD : ImagePlugin + { + #region Internal Structures + struct HardDiskFooter + { + /// + /// Offset 0x00, File magic number, + /// + public UInt64 cookie; + /// + /// Offset 0x08, Specific feature support + /// + public UInt32 features; + /// + /// Offset 0x0C, File format version + /// + public UInt32 version; + /// + /// Offset 0x10, Offset from beginning of file to next structure + /// + public UInt64 offset; + /// + /// Offset 0x18, Creation date seconds since 2000/01/01 00:00:00 UTC + /// + public UInt32 timestamp; + /// + /// Offset 0x1C, Application that created this disk image + /// + public UInt32 creatorApplication; + /// + /// Offset 0x20, Version of the application that created this disk image + /// + public UInt32 creatorVersion; + /// + /// Offset 0x24, Host operating system of the application that created this disk image + /// + public UInt32 creatorHostOS; + /// + /// Offset 0x28, Original hard disk size, in bytes + /// + public UInt64 originalSize; + /// + /// Offset 0x30, Current hard disk size, in bytes + /// + public UInt64 currentSize; + /// + /// Offset 0x38, CHS geometry + /// Cylinder mask = 0xFFFF0000 + /// Heads mask = 0x0000FF00 + /// Sectors mask = 0x000000FF + /// + public UInt32 diskGeometry; + /// + /// Offset 0x3C, Disk image type + /// + public UInt32 diskType; + /// + /// Offset 0x40, Checksum for this structure + /// + public UInt32 checksum; + /// + /// Offset 0x44, UUID, used to associate parent with differencing disk images + /// + public Guid uniqueId; + /// + /// Offset 0x54, If set, system is saved, so compaction and expansion cannot be performed + /// + public byte savedState; + /// + /// Offset 0x55, 427 bytes reserved, should contain zeros. + /// + public byte[] reserved; + } + + struct ParentLocatorEntry + { + /// + /// Describes the platform specific type this entry belongs to + /// + public UInt32 platformCode; + /// + /// Describes the number of 512 bytes sectors used by this entry + /// + public UInt32 platformDataSpace; + /// + /// Describes this entry's size in bytes + /// + public UInt32 platformDataLength; + /// + /// Reserved + /// + public UInt32 reserved; + /// + /// Offset on disk image this entry resides on + /// + public UInt64 platformDataOffset; + } + + struct DynamicDiskHeader + { + /// + /// Header magic, + /// + public UInt64 cookie; + /// + /// Offset to next structure on disk image. + /// Currently unused, 0xFFFFFFFF + /// + public UInt64 dataOffset; + /// + /// Offset of the Block Allocation Table (BAT) + /// + public UInt64 tableOffset; + /// + /// Version of this header + /// + public UInt32 headerVersion; + /// + /// Maximum entries present in the BAT + /// + public UInt32 maxTableEntries; + /// + /// Size of a block in bytes + /// Should always be a power of two of 512 + /// + public UInt32 blockSize; + /// + /// Checksum of this header + /// + public UInt32 checksum; + /// + /// UUID of parent disk image for differencing type + /// + public Guid parentID; + /// + /// Timestamp of parent disk image + /// + public UInt32 parentTimestamp; + /// + /// Reserved + /// + public UInt32 reserved; + /// + /// 512 bytes UTF-16 of paren disk image filename + /// + public string parentName; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry1; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry2; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry3; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry4; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry5; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry6; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry7; + /// + /// Parent disk image locator entry, + /// + public ParentLocatorEntry locatorEntry8; + /// + /// 256 reserved bytes + /// + public byte[] reserved2; + } + #endregion + + #region Internal Constants + /// + /// File magic number, "conectix" + /// + const UInt64 ImageCookie = 0x636F6E6563746978; + /// + /// Dynamic disk header magic, "cxsparse" + /// + const UInt64 DynamicCookie = 0x6378737061727365; + + /// + /// Disk image is candidate for deletion on shutdown + /// + const UInt32 FeaturesTemporary = 0x00000001; + /// + /// Unknown, set from Virtual PC for Mac 7 onwards + /// + const UInt32 FeaturesReserved = 0x00000002; + /// + /// Unknown + /// + const UInt32 FeaturesUnknown = 0x00000100; + + /// + /// Only known version + /// + const UInt32 Version1 = 0x00010000; + + /// + /// Created by Virtual PC, "vpc " + /// + const UInt32 CreatorVirtualPC = 0x76706320; + /// + /// Created by Virtual Server, "vs " + /// + const UInt32 CreatorVirtualServer = 0x76732020; + /// + /// Created by QEMU, "qemu" + /// + const UInt32 CreatorQEMU = 0x71656D75; + /// + /// Created by VirtualBox, "vbox" + /// + const UInt32 CreatorVirtualBox = 0x76626F78; + + /// + /// Disk image created by Virtual Server 2004 + /// + const UInt32 VersionVirtualServer2004 = 0x00010000; + /// + /// Disk image created by Virtual PC 2004 + /// + const UInt32 VersionVirtualPC2004 = 0x00050000; + /// + /// Disk image created by Virtual PC 2007 + /// + const UInt32 VersionVirtualPC2007 = 0x00050003; + /// + /// Disk image created by Virtual PC for Mac 5, 6 or 7 + /// + const UInt32 VersionVirtualPCMac = 0x00040000; + + /// + /// Disk image created in Windows, "Wi2k" + /// + const UInt32 CreatorWindows = 0x5769326B; + /// + /// Disk image created in Macintosh, "Mac " + /// + const UInt32 CreatorMacintosh = 0x4D616320; + /// + /// Seen in Virtual PC for Mac for dynamic and fixed images + /// + const UInt32 CreatorMacintoshOld = 0x00000000; + + /// + /// Disk image type is none, useless? + /// + const UInt32 typeNone = 0; + /// + /// Deprecated disk image type + /// + const UInt32 typeDeprecated1 = 1; + /// + /// Fixed disk image type + /// + const UInt32 typeFixed = 2; + /// + /// Dynamic disk image type + /// + const UInt32 typeDynamic = 3; + /// + /// Differencing disk image type + /// + const UInt32 typeDifferencing = 4; + /// + /// Deprecated disk image type + /// + const UInt32 typeDeprecated2 = 5; + /// + /// Deprecated disk image type + /// + const UInt32 typeDeprecated3 = 6; + + /// + /// Means platform locator is unused + /// + const UInt32 platformCodeUnused = 0x00000000; + /// + /// Stores a relative path string for Windows, unknown locale used, deprecated, "Wi2r" + /// + const UInt32 platformCodeWindowsRelative = 0x57693272; + /// + /// Stores an absolute path string for Windows, unknown locale used, deprecated, "Wi2k" + /// + const UInt32 platformCodeWindowsAbsolute = 0x5769326B; + /// + /// Stores a relative path string for Windows in UTF-16, "W2ru" + /// + const UInt32 platformCodeWindowsRelativeU = 0x57327275; + /// + /// Stores an absolute path string for Windows in UTF-16, "W2ku" + /// + const UInt32 platformCodeWindowsAbsoluteU = 0x57326B75; + /// + /// Stores a Mac OS alias as a blob, "Mac " + /// + const UInt32 platformCodeMacintoshAlias = 0x4D616320; + /// + /// Stores a Mac OS X URI (RFC-2396) absolute path in UTF-8, "MacX" + /// + const UInt32 platformCodeMacintoshURI = 0x4D616358; + #endregion + + #region Internal variables + HardDiskFooter thisFooter; + HardDiskFooter parentFooter; + DynamicDiskHeader thisDynamic; + DynamicDiskHeader parentDynamic; + DateTime thisDateTime; + DateTime parentDateTime; + string thisPath; + string parentPath; + #endregion + + public VHD(PluginBase Core) + { + Name = "VirtualPC"; + PluginUUID = new Guid("8014d88f-64cd-4484-9441-7635c632958a"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableDiskTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageVersion = null; + ImageInfo.imageApplication = null; + ImageInfo.imageApplicationVersion = null; + ImageInfo.imageCreator = null; + ImageInfo.imageComments = null; + ImageInfo.diskManufacturer = null; + ImageInfo.diskModel = null; + ImageInfo.diskSerialNumber = null; + ImageInfo.diskBarcode = null; + ImageInfo.diskPartNumber = null; + ImageInfo.diskSequence = 0; + ImageInfo.lastDiskSequence = 0; + ImageInfo.driveManufacturer = null; + ImageInfo.driveModel = null; + ImageInfo.driveSerialNumber = null; + } + + #region public methods + public override bool IdentifyImage(string imagePath) + { + FileStream imageStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + UInt64 headerCookie; + UInt64 footerCookie; + + byte[] headerCookieBytes = new byte[8]; + byte[] footerCookieBytes = new byte[8]; + + if ((imageStream.Length % 2) == 0) + imageStream.Seek(-512, SeekOrigin.End); + else + imageStream.Seek(-511, SeekOrigin.End); + + imageStream.Read(footerCookieBytes, 0, 8); + imageStream.Seek(0, SeekOrigin.Begin); + imageStream.Read(headerCookieBytes, 0, 8); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + headerCookie = BigEndianBitConverter.ToUInt64(headerCookieBytes, 0); + footerCookie = BigEndianBitConverter.ToUInt64(footerCookieBytes, 0); + + return (headerCookie == ImageCookie || footerCookie == ImageCookie); + } + + public override bool OpenImage(string imagePath) + { + FileStream imageStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + byte[] header = new byte[512]; + byte[] footer; + + imageStream.Seek(0, SeekOrigin.Begin); + imageStream.Read(header, 0, 512); + + if ((imageStream.Length % 2) == 0) + { + footer = new byte[512]; + imageStream.Seek(-512, SeekOrigin.End); + imageStream.Read(footer, 0, 512); + } + else + { + footer = new byte[511]; + imageStream.Seek(-511, SeekOrigin.End); + imageStream.Read(footer, 0, 511); + } + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + UInt32 headerChecksum = BigEndianBitConverter.ToUInt32(header, 0x40); + UInt32 footerChecksum = BigEndianBitConverter.ToUInt32(footer, 0x40); + UInt64 headerCookie = BigEndianBitConverter.ToUInt64(header, 0); + UInt64 footerCookie = BigEndianBitConverter.ToUInt64(footer, 0); + + header[0x40] = 0; + header[0x41] = 0; + header[0x42] = 0; + header[0x43] = 0; + footer[0x40] = 0; + footer[0x41] = 0; + footer[0x42] = 0; + footer[0x43] = 0; + + UInt32 headerCalculatedChecksum = VHDChecksum(header); + UInt32 footerCalculatedChecksum = VHDChecksum(footer); + + if (MainClass.isDebug) + { + Console.WriteLine("DEBUG (VirtualPC plugin): Header checksum = 0x{0:X8}, calculated = 0x{1:X8}", headerChecksum, headerCalculatedChecksum); + Console.WriteLine("DEBUG (VirtualPC plugin): Header checksum = 0x{0:X8}, calculated = 0x{1:X8}", footerChecksum, footerCalculatedChecksum); + } + + byte[] usableHeader; + UInt32 usableChecksum; + + if (headerCookie == ImageCookie && headerChecksum == headerCalculatedChecksum) + { + usableHeader = header; + usableChecksum = headerChecksum; + } + else if (footerCookie == ImageCookie && footerChecksum == footerCalculatedChecksum) + { + usableHeader = footer; + usableChecksum = footerChecksum; + } + else + throw new ImageNotSupportedException("(VirtualPC plugin): Both header and footer are corrupt, image cannot be opened."); + + thisFooter = new HardDiskFooter(); + thisFooter.cookie = BigEndianBitConverter.ToUInt64(usableHeader, 0x00); + thisFooter.features = BigEndianBitConverter.ToUInt32(usableHeader, 0x08); + thisFooter.version = BigEndianBitConverter.ToUInt32(usableHeader, 0x0C); + thisFooter.offset = BigEndianBitConverter.ToUInt64(usableHeader, 0x10); + thisFooter.timestamp = BigEndianBitConverter.ToUInt32(usableHeader, 0x18); + thisFooter.creatorApplication = BigEndianBitConverter.ToUInt32(usableHeader, 0x1C); + thisFooter.creatorVersion = BigEndianBitConverter.ToUInt32(usableHeader, 0x20); + thisFooter.creatorHostOS = BigEndianBitConverter.ToUInt32(usableHeader, 0x24); + thisFooter.originalSize = BigEndianBitConverter.ToUInt64(usableHeader, 0x28); + thisFooter.currentSize = BigEndianBitConverter.ToUInt64(usableHeader, 0x30); + thisFooter.diskGeometry = BigEndianBitConverter.ToUInt32(usableHeader, 0x38); + thisFooter.diskType = BigEndianBitConverter.ToUInt32(usableHeader, 0x3C); + thisFooter.checksum = usableChecksum; + thisFooter.uniqueId = BigEndianBitConverter.ToGuid(usableHeader, 0x44); + thisFooter.savedState = usableHeader[0x54]; + thisFooter.reserved = new byte[usableHeader.Length - 0x55]; + Array.Copy(usableHeader, 0x55, thisFooter.reserved, 0, (usableHeader.Length - 0x55)); + + thisDateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + thisDateTime = thisDateTime.AddSeconds(thisFooter.timestamp); + + if (MainClass.isDebug) + { + Checksums.SHA1Context sha1Ctx = new Checksums.SHA1Context(); + sha1Ctx.Init(); + sha1Ctx.Update(thisFooter.reserved); + + Console.WriteLine("DEBUG (VirtualPC plugin): footer.cookie = 0x{0:X8}", thisFooter.cookie); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.features = 0x{0:X8}", thisFooter.features); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.version = 0x{0:X8}", thisFooter.version); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.offset = {0}", thisFooter.offset); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.timestamp = 0x{0:X8} ({1})", thisFooter.timestamp, thisDateTime); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.creatorApplication = 0x{0:X8} (\"{1}\")", thisFooter.creatorApplication, + Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorApplication))); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.creatorVersion = 0x{0:X8}", thisFooter.creatorVersion); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.creatorHostOS = 0x{0:X8} (\"{1}\")", thisFooter.creatorHostOS, + Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorHostOS))); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.originalSize = {0}", thisFooter.originalSize); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.currentSize = {0}", thisFooter.currentSize); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.diskGeometry = 0x{0:X8} (C/H/S: {1}/{2}/{3})", thisFooter.diskGeometry, + (thisFooter.diskGeometry&0xFFFF0000)>>16, (thisFooter.diskGeometry&0xFF00)>>8, (thisFooter.diskGeometry&0xFF)); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.diskType = 0x{0:X8}", thisFooter.diskType); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.checksum = 0x{0:X8}", thisFooter.checksum); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.uniqueId = {0}", thisFooter.uniqueId); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.savedState = 0x{0:X2}", thisFooter.savedState); + Console.WriteLine("DEBUG (VirtualPC plugin): footer.reserved's SHA1 = 0x{0}", sha1Ctx.End()); + } + + if(thisFooter.version == Version1) + ImageInfo.imageVersion = "1.0"; + else + throw new ImageNotSupportedException(String.Format("(VirtualPC plugin): Unknown image type {0} found. Please submit a bug with an example image.", thisFooter.diskType)); + + switch (thisFooter.creatorApplication) + { + case CreatorQEMU: + { + ImageInfo.imageApplication = "QEMU"; + // QEMU always set same version + ImageInfo.imageApplicationVersion = "Unknown"; + + break; + } + case CreatorVirtualBox: + { + ImageInfo.imageApplicationVersion = String.Format("{0}.{1:D2}", (thisFooter.creatorVersion & 0xFFFF0000) >> 16, (thisFooter.creatorVersion & 0x0000FFFF)); + switch (thisFooter.creatorHostOS) + { + case CreatorMacintosh: + case CreatorMacintoshOld: + ImageInfo.imageApplication = "VirtualBox for Mac"; + break; + case CreatorWindows: + // VirtualBox uses Windows creator for any other OS + ImageInfo.imageApplication = "VirtualBox"; + break; + default: + ImageInfo.imageApplication = String.Format("VirtualBox for unknown OS \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorHostOS))); + break; + } + break; + } + case CreatorVirtualServer: + { + ImageInfo.imageApplication = "Microsoft Virtual Server"; + switch(thisFooter.creatorVersion) + { + case VersionVirtualServer2004: + ImageInfo.imageApplicationVersion = "2004"; + break; + default: + ImageInfo.imageApplicationVersion = String.Format("Unknown version 0x{0:X8}", thisFooter.creatorVersion); + break; + } + break; + } + case CreatorVirtualPC: + { + switch (thisFooter.creatorHostOS) + { + case CreatorMacintosh: + case CreatorMacintoshOld: + switch(thisFooter.creatorVersion) + { + case VersionVirtualPCMac: + ImageInfo.imageApplication = "Connectix Virtual PC"; + ImageInfo.imageApplicationVersion = "5, 6 or 7"; + break; + default: + ImageInfo.imageApplicationVersion = String.Format("Unknown version 0x{0:X8}", thisFooter.creatorVersion); + break; + } + break; + case CreatorWindows: + switch(thisFooter.creatorVersion) + { + case VersionVirtualPCMac: + ImageInfo.imageApplication = "Connectix Virtual PC"; + ImageInfo.imageApplicationVersion = "5, 6 or 7"; + break; + case VersionVirtualPC2004: + ImageInfo.imageApplication = "Microsoft Virtual PC"; + ImageInfo.imageApplicationVersion = "2004"; + break; + case VersionVirtualPC2007: + ImageInfo.imageApplication = "Microsoft Virtual PC"; + ImageInfo.imageApplicationVersion = "2007"; + break; + default: + ImageInfo.imageApplicationVersion = String.Format("Unknown version 0x{0:X8}", thisFooter.creatorVersion); + break; + } + break; + default: + ImageInfo.imageApplication = String.Format("Virtual PC for unknown OS \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorHostOS))); + ImageInfo.imageApplicationVersion = String.Format("Unknown version 0x{0:X8}", thisFooter.creatorVersion); + break; + } + break; + } + default: + { + ImageInfo.imageApplication = String.Format("Unknown application \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorHostOS))); + ImageInfo.imageApplicationVersion = String.Format("Unknown version 0x{0:X8}", thisFooter.creatorVersion); + break; + } + } + + thisPath = imagePath; + ImageInfo.imageSize = thisFooter.currentSize; + ImageInfo.sectors = thisFooter.currentSize/512; + ImageInfo.sectorSize = 512; + + FileInfo fi = new FileInfo(imagePath); + ImageInfo.imageCreationTime = fi.CreationTimeUtc; + ImageInfo.imageLastModificationTime = thisDateTime; + ImageInfo.imageName = Path.GetFileNameWithoutExtension(imagePath); + + switch (thisFooter.diskType) + { + case typeFixed: + { + // Nothing to do here, really. + imageStream.Close(); + return true; + } + case typeDynamic: + { + throw new NotImplementedException("(VirtualPC plugin): Dynamic disk images not yet supported"); + } + case typeDifferencing: + { + throw new NotImplementedException("(VirtualPC plugin): Differencing disk images not yet supported"); + } + case typeDeprecated1: + case typeDeprecated2: + case typeDeprecated3: + { + throw new ImageNotSupportedException("(VirtualPC plugin): Deprecated image type found. Please submit a bug with an example image."); + } + default: + { + throw new ImageNotSupportedException(String.Format("(VirtualPC plugin): Unknown image type {0} found. Please submit a bug with an example image.", thisFooter.diskType)); + } + } + } + + public override bool ImageHasPartitions() + { + return false; + } + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + public override string GetImageFormat() + { + switch(thisFooter.diskType) + { + case typeFixed: + return "Virtual PC fixed size disk image"; + case typeDynamic: + return "Virtual PC dynamic size disk image"; + case typeDifferencing: + return "Virtual PC differencing disk image"; + default: + return "Virtual PC disk image"; + } + } + public override string GetImageVersion() + { + return ImageInfo.imageVersion; + } + + public override string GetImageApplication() + { + return ImageInfo.imageApplication; + } + + public override string GetImageApplicationVersion() + { + return ImageInfo.imageApplicationVersion; + } + + public override string GetImageCreator() + { + return ImageInfo.imageCreator; + } + + public override DateTime GetImageCreationTime() + { + return ImageInfo.imageCreationTime; + } + + public override DateTime GetImageLastModificationTime() + { + return ImageInfo.imageLastModificationTime; + } + + public override string GetImageName() + { + return ImageInfo.imageName; + } + + public override DiskType GetDiskType() + { + return DiskType.GENERIC_HDD; + } + + public override byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + FileStream thisStream; + + switch (thisFooter.diskType) + { + case typeFixed: + { + byte[] data = new byte[512 * length]; + thisStream = new FileStream(thisPath, FileMode.Open, FileAccess.Read); + + thisStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); + thisStream.Read(data, 0, (int)(512 * length)); + + thisStream.Close(); + return data; + } + case typeDynamic: + { + throw new NotImplementedException("(VirtualPC plugin): Dynamic disk images not yet supported"); + } + case typeDifferencing: + { + throw new NotImplementedException("(VirtualPC plugin): Differencing disk images not yet supported"); + } + case typeDeprecated1: + case typeDeprecated2: + case typeDeprecated3: + { + throw new ImageNotSupportedException("(VirtualPC plugin): Deprecated image type found. Please submit a bug with an example image."); + } + default: + { + throw new ImageNotSupportedException(String.Format("(VirtualPC plugin): Unknown image type {0} found. Please submit a bug with an example image.", thisFooter.diskType)); + } + } + } + #endregion + + #region private methods + UInt32 VHDChecksum(byte[] data) + { + UInt32 checksum = 0; + foreach (byte b in data) + checksum += b; + return ~checksum; + } + #endregion + + #region Unsupported features + public override string GetImageComments() + { + return null; + } + public override byte[] ReadDiskTag(DiskTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override string GetDiskManufacturer() + { + return null; + } + public override string GetDiskModel() + { + return null; + } + public override string GetDiskSerialNumber() + { + return null; + } + public override string GetDiskBarcode() + { + return null; + } + public override string GetDiskPartNumber() + { + return null; + } + public override int GetDiskSequence() + { + return 0; + } + public override int GetLastDiskSequence() + { + return 0; + } + public override string GetDriveManufacturer() + { + return null; + } + public override string GetDriveModel() + { + return null; + } + public override string GetDriveSerialNumber() + { + return null; + } + public override System.Collections.Generic.List GetPartitions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override System.Collections.Generic.List GetTracks() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override System.Collections.Generic.List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override System.Collections.Generic.List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override System.Collections.Generic.List GetSessions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override bool? VerifySector(ulong sectorAddress) + { + return null; + } + public override bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + for (ulong i = 0; i < ImageInfo.sectors; i++) + UnknownLBAs.Add(i); + return null; + } + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override bool? VerifyDiskImage() + { + return null; + } + #endregion + } +} + diff --git a/README.md b/README.md index 86eaad82..70a1560f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Supported disk image formats * TeleDisk (without compression) * Nero Burning ROM (both image formats) * Apple 2IMG (used with Apple // emulators) +* Virtual PC fixed size disk images Supported partitioning schemes ============================== diff --git a/TODO b/TODO index a22d9819..ad804edd 100644 --- a/TODO +++ b/TODO @@ -13,7 +13,7 @@ --- Add support for Apple UDIF images --- Add support for XPACK images --- Add support for QEMU QCOW and QCOW2 images ---- Add support for VHD and VHDX images +--- Add support for VHDX images --- Add support for VirtualBox images --- Add support for VMWare images --- Add support foe QEMU QED images