// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : VHD.cs // Author(s) : Natalia Portillo // // Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages Connectix and Microsoft Virtual PC disk images. // // --[ 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-2017 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Runtime.InteropServices; using DiscImageChef.Console; using DiscImageChef.CommonTypes; using DiscImageChef.Filters; 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 ulong cookie; /// /// Offset 0x08, Specific feature support /// public uint features; /// /// Offset 0x0C, File format version /// public uint version; /// /// Offset 0x10, Offset from beginning of file to next structure /// public ulong offset; /// /// Offset 0x18, Creation date seconds since 2000/01/01 00:00:00 UTC /// public uint timestamp; /// /// Offset 0x1C, Application that created this disk image /// public uint creatorApplication; /// /// Offset 0x20, Version of the application that created this disk image /// public uint creatorVersion; /// /// Offset 0x24, Host operating system of the application that created this disk image /// public uint creatorHostOS; /// /// Offset 0x28, Original hard disk size, in bytes /// public ulong originalSize; /// /// Offset 0x30, Current hard disk size, in bytes /// public ulong currentSize; /// /// Offset 0x38, CHS geometry /// Cylinder mask = 0xFFFF0000 /// Heads mask = 0x0000FF00 /// Sectors mask = 0x000000FF /// public uint diskGeometry; /// /// Offset 0x3C, Disk image type /// public uint diskType; /// /// Offset 0x40, Checksum for this structure /// public uint 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 { /// /// Offset 0x00, Describes the platform specific type this entry belongs to /// public uint platformCode; /// /// Offset 0x04, Describes the number of 512 bytes sectors used by this entry /// public uint platformDataSpace; /// /// Offset 0x08, Describes this entry's size in bytes /// public uint platformDataLength; /// /// Offset 0x0c, Reserved /// public uint reserved; /// /// Offset 0x10, Offset on disk image this entry resides on /// public ulong platformDataOffset; } struct DynamicDiskHeader { /// /// Offset 0x00, Header magic, /// public ulong cookie; /// /// Offset 0x08, Offset to next structure on disk image. /// Currently unused, 0xFFFFFFFF /// public ulong dataOffset; /// /// Offset 0x10, Offset of the Block Allocation Table (BAT) /// public ulong tableOffset; /// /// Offset 0x18, Version of this header /// public uint headerVersion; /// /// Offset 0x1C, Maximum entries present in the BAT /// public uint maxTableEntries; /// /// Offset 0x20, Size of a block in bytes /// Should always be a power of two of 512 /// public uint blockSize; /// /// Offset 0x24, Checksum of this header /// public uint checksum; /// /// Offset 0x28, UUID of parent disk image for differencing type /// public Guid parentID; /// /// Offset 0x38, Timestamp of parent disk image /// public uint parentTimestamp; /// /// Offset 0x3C, Reserved /// public uint reserved; /// /// Offset 0x40, 512 bytes UTF-16 of parent disk image filename /// public string parentName; /// /// Offset 0x240, Parent disk image locator entry, /// public ParentLocatorEntry[] locatorEntries; /// /// Offset 0x300, 256 reserved bytes /// public byte[] reserved2; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BATSector { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public uint[] blockPointer; } #endregion #region Internal Constants /// /// File magic number, "conectix" /// const ulong ImageCookie = 0x636F6E6563746978; /// /// Dynamic disk header magic, "cxsparse" /// const ulong DynamicCookie = 0x6378737061727365; /// /// Disk image is candidate for deletion on shutdown /// const uint FeaturesTemporary = 0x00000001; /// /// Unknown, set from Virtual PC for Mac 7 onwards /// const uint FeaturesReserved = 0x00000002; /// /// Unknown /// const uint FeaturesUnknown = 0x00000100; /// /// Only known version /// const uint Version1 = 0x00010000; /// /// Created by Virtual PC, "vpc " /// const uint CreatorVirtualPC = 0x76706320; /// /// Created by Virtual Server, "vs " /// const uint CreatorVirtualServer = 0x76732020; /// /// Created by QEMU, "qemu" /// const uint CreatorQEMU = 0x71656D75; /// /// Created by VirtualBox, "vbox" /// const uint CreatorVirtualBox = 0x76626F78; /// /// Disk image created by Virtual Server 2004 /// const uint VersionVirtualServer2004 = 0x00010000; /// /// Disk image created by Virtual PC 2004 /// const uint VersionVirtualPC2004 = 0x00050000; /// /// Disk image created by Virtual PC 2007 /// const uint VersionVirtualPC2007 = 0x00050003; /// /// Disk image created by Virtual PC for Mac 5, 6 or 7 /// const uint VersionVirtualPCMac = 0x00040000; /// /// Disk image created in Windows, "Wi2k" /// const uint CreatorWindows = 0x5769326B; /// /// Disk image created in Macintosh, "Mac " /// const uint CreatorMacintosh = 0x4D616320; /// /// Seen in Virtual PC for Mac for dynamic and fixed images /// const uint CreatorMacintoshOld = 0x00000000; /// /// Disk image type is none, useless? /// const uint typeNone = 0; /// /// Deprecated disk image type /// const uint typeDeprecated1 = 1; /// /// Fixed disk image type /// const uint typeFixed = 2; /// /// Dynamic disk image type /// const uint typeDynamic = 3; /// /// Differencing disk image type /// const uint typeDifferencing = 4; /// /// Deprecated disk image type /// const uint typeDeprecated2 = 5; /// /// Deprecated disk image type /// const uint typeDeprecated3 = 6; /// /// Means platform locator is unused /// const uint platformCodeUnused = 0x00000000; /// /// Stores a relative path string for Windows, unknown locale used, deprecated, "Wi2r" /// const uint platformCodeWindowsRelative = 0x57693272; /// /// Stores an absolute path string for Windows, unknown locale used, deprecated, "Wi2k" /// const uint platformCodeWindowsAbsolute = 0x5769326B; /// /// Stores a relative path string for Windows in UTF-16, "W2ru" /// const uint platformCodeWindowsRelativeU = 0x57327275; /// /// Stores an absolute path string for Windows in UTF-16, "W2ku" /// const uint platformCodeWindowsAbsoluteU = 0x57326B75; /// /// Stores a Mac OS alias as a blob, "Mac " /// const uint platformCodeMacintoshAlias = 0x4D616320; /// /// Stores a Mac OS X URI (RFC-2396) absolute path in UTF-8, "MacX" /// const uint platformCodeMacintoshURI = 0x4D616358; #endregion #region Internal variables HardDiskFooter thisFooter; DynamicDiskHeader thisDynamic; DateTime thisDateTime; DateTime parentDateTime; Filter thisFilter; uint[] blockAllocationTable; uint bitmapSize; byte[][] locatorEntriesData; ImagePlugin parentImage; #endregion public VHD() { Name = "VirtualPC"; PluginUUID = new Guid("8014d88f-64cd-4484-9441-7635c632958a"); ImageInfo = new ImageInfo(); ImageInfo.readableSectorTags = new List(); ImageInfo.readableMediaTags = new List(); ImageInfo.imageHasPartitions = false; ImageInfo.imageHasSessions = false; ImageInfo.imageVersion = null; ImageInfo.imageApplication = null; ImageInfo.imageApplicationVersion = null; ImageInfo.imageCreator = null; ImageInfo.imageComments = null; ImageInfo.mediaManufacturer = null; ImageInfo.mediaModel = null; ImageInfo.mediaSerialNumber = null; ImageInfo.mediaBarcode = null; ImageInfo.mediaPartNumber = null; ImageInfo.mediaSequence = 0; ImageInfo.lastMediaSequence = 0; ImageInfo.driveManufacturer = null; ImageInfo.driveModel = null; ImageInfo.driveSerialNumber = null; ImageInfo.driveFirmwareRevision = null; } #region public methods public override bool IdentifyImage(Filter imageFilter) { Stream imageStream = imageFilter.GetDataForkStream(); ulong headerCookie; ulong 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(Filter imageFilter) { Stream imageStream = imageFilter.GetDataForkStream(); 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; uint headerChecksum = BigEndianBitConverter.ToUInt32(header, 0x40); uint footerChecksum = BigEndianBitConverter.ToUInt32(footer, 0x40); ulong headerCookie = BigEndianBitConverter.ToUInt64(header, 0); ulong 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; uint headerCalculatedChecksum = VHDChecksum(header); uint footerCalculatedChecksum = VHDChecksum(footer); DicConsole.DebugWriteLine("VirtualPC plugin", "Header checksum = 0x{0:X8}, calculated = 0x{1:X8}", headerChecksum, headerCalculatedChecksum); DicConsole.DebugWriteLine("VirtualPC plugin", "Header checksum = 0x{0:X8}, calculated = 0x{1:X8}", footerChecksum, footerCalculatedChecksum); byte[] usableHeader; uint 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); Checksums.SHA1Context sha1Ctx = new Checksums.SHA1Context(); sha1Ctx.Init(); sha1Ctx.Update(thisFooter.reserved); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.cookie = 0x{0:X8}", thisFooter.cookie); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.features = 0x{0:X8}", thisFooter.features); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.version = 0x{0:X8}", thisFooter.version); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.offset = {0}", thisFooter.offset); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.timestamp = 0x{0:X8} ({1})", thisFooter.timestamp, thisDateTime); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.creatorApplication = 0x{0:X8} (\"{1}\")", thisFooter.creatorApplication, Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorApplication))); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.creatorVersion = 0x{0:X8}", thisFooter.creatorVersion); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.creatorHostOS = 0x{0:X8} (\"{1}\")", thisFooter.creatorHostOS, Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisFooter.creatorHostOS))); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.originalSize = {0}", thisFooter.originalSize); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.currentSize = {0}", thisFooter.currentSize); DicConsole.DebugWriteLine("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)); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.diskType = 0x{0:X8}", thisFooter.diskType); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.checksum = 0x{0:X8}", thisFooter.checksum); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.uniqueId = {0}", thisFooter.uniqueId); DicConsole.DebugWriteLine("VirtualPC plugin", "footer.savedState = 0x{0:X2}", thisFooter.savedState); DicConsole.DebugWriteLine("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; } } thisFilter = imageFilter; ImageInfo.imageSize = thisFooter.currentSize; ImageInfo.sectors = thisFooter.currentSize / 512; ImageInfo.sectorSize = 512; ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); ImageInfo.imageLastModificationTime = thisDateTime; ImageInfo.imageName = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); if(thisFooter.diskType == typeDynamic || thisFooter.diskType == typeDifferencing) { imageStream.Seek((long)thisFooter.offset, SeekOrigin.Begin); byte[] dynamicBytes = new byte[1024]; imageStream.Read(dynamicBytes, 0, 1024); uint dynamicChecksum = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x24); dynamicBytes[0x24] = 0; dynamicBytes[0x25] = 0; dynamicBytes[0x26] = 0; dynamicBytes[0x27] = 0; uint dynamicChecksumCalculated = VHDChecksum(dynamicBytes); DicConsole.DebugWriteLine("VirtualPC plugin", "Dynamic header checksum = 0x{0:X8}, calculated = 0x{1:X8}", dynamicChecksum, dynamicChecksumCalculated); if(dynamicChecksum != dynamicChecksumCalculated) throw new ImageNotSupportedException("(VirtualPC plugin): Both header and footer are corrupt, image cannot be opened."); thisDynamic = new DynamicDiskHeader(); thisDynamic.locatorEntries = new ParentLocatorEntry[8]; thisDynamic.reserved2 = new byte[256]; for(int i = 0; i < 8; i++) thisDynamic.locatorEntries[i] = new ParentLocatorEntry(); thisDynamic.cookie = BigEndianBitConverter.ToUInt64(dynamicBytes, 0x00); thisDynamic.dataOffset = BigEndianBitConverter.ToUInt64(dynamicBytes, 0x08); thisDynamic.tableOffset = BigEndianBitConverter.ToUInt64(dynamicBytes, 0x10); thisDynamic.headerVersion = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x18); thisDynamic.maxTableEntries = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x1C); thisDynamic.blockSize = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x20); thisDynamic.checksum = dynamicChecksum; thisDynamic.parentID = BigEndianBitConverter.ToGuid(dynamicBytes, 0x28); thisDynamic.parentTimestamp = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x38); thisDynamic.reserved = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x3C); thisDynamic.parentName = Encoding.BigEndianUnicode.GetString(dynamicBytes, 0x40, 512); for(int i = 0; i < 8; i++) { thisDynamic.locatorEntries[i].platformCode = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x240 + 0x00 + 24 * i); thisDynamic.locatorEntries[i].platformDataSpace = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x240 + 0x04 + 24 * i); thisDynamic.locatorEntries[i].platformDataLength = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x240 + 0x08 + 24 * i); thisDynamic.locatorEntries[i].reserved = BigEndianBitConverter.ToUInt32(dynamicBytes, 0x240 + 0x0C + 24 * i); thisDynamic.locatorEntries[i].platformDataOffset = BigEndianBitConverter.ToUInt64(dynamicBytes, 0x240 + 0x10 + 24 * i); } Array.Copy(dynamicBytes, 0x300, thisDynamic.reserved2, 0, 256); parentDateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); parentDateTime = parentDateTime.AddSeconds(thisDynamic.parentTimestamp); sha1Ctx = new Checksums.SHA1Context(); sha1Ctx.Init(); sha1Ctx.Update(thisDynamic.reserved2); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.cookie = 0x{0:X8}", thisDynamic.cookie); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.dataOffset = {0}", thisDynamic.dataOffset); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.tableOffset = {0}", thisDynamic.tableOffset); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.headerVersion = 0x{0:X8}", thisDynamic.headerVersion); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.maxTableEntries = {0}", thisDynamic.maxTableEntries); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.blockSize = {0}", thisDynamic.blockSize); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.checksum = 0x{0:X8}", thisDynamic.checksum); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.parentID = {0}", thisDynamic.parentID); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.parentTimestamp = 0x{0:X8} ({1})", thisDynamic.parentTimestamp, parentDateTime); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.reserved = 0x{0:X8}", thisDynamic.reserved); for(int i = 0; i < 8; i++) { DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}].platformCode = 0x{1:X8} (\"{2}\")", i, thisDynamic.locatorEntries[i].platformCode, Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(thisDynamic.locatorEntries[i].platformCode))); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}].platformDataSpace = {1}", i, thisDynamic.locatorEntries[i].platformDataSpace); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}].platformDataLength = {1}", i, thisDynamic.locatorEntries[i].platformDataLength); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}].reserved = 0x{1:X8}", i, thisDynamic.locatorEntries[i].reserved); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}].platformDataOffset = {1}", i, thisDynamic.locatorEntries[i].platformDataOffset); } DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.parentName = \"{0}\"", thisDynamic.parentName); DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.reserved2's SHA1 = 0x{0}", sha1Ctx.End()); if(thisDynamic.headerVersion != Version1) throw new ImageNotSupportedException(string.Format("(VirtualPC plugin): Unknown image type {0} found. Please submit a bug with an example image.", thisFooter.diskType)); DateTime startTime = DateTime.UtcNow; blockAllocationTable = new uint[thisDynamic.maxTableEntries]; // Safe and slow code. It takes 76,572 ms to fill a 30720 entries BAT /* byte[] bat = new byte[thisDynamic.maxTableEntries * 4]; imageStream.Seek((long)thisDynamic.tableOffset, SeekOrigin.Begin); imageStream.Read(bat, 0, (int)(thisDynamic.maxTableEntries * 4)); for (int i = 0; i < thisDynamic.maxTableEntries; i++) blockAllocationTable[i] = BigEndianBitConverter.ToUInt32(bat, 4 * i); DateTime endTime = DateTime.UtcNow; DicConsole.DebugWriteLine("VirtualPC plugin", "Filling the BAT took {0} seconds", (endTime-startTime).TotalSeconds); */ // How many sectors uses the BAT uint batSectorCount = (uint)Math.Ceiling(((double)thisDynamic.maxTableEntries * 4) / 512); byte[] batSectorBytes = new byte[512]; BATSector batSector = new BATSector(); // Unsafe and fast code. It takes 4 ms to fill a 30720 entries BAT for(int i = 0; i < batSectorCount; i++) { imageStream.Seek((long)thisDynamic.tableOffset + i * 512, SeekOrigin.Begin); imageStream.Read(batSectorBytes, 0, 512); // This does the big-endian trick but reverses the order of elements also Array.Reverse(batSectorBytes); GCHandle handle = GCHandle.Alloc(batSectorBytes, GCHandleType.Pinned); batSector = (BATSector)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(BATSector)); handle.Free(); // This restores the order of elements Array.Reverse(batSector.blockPointer); if(blockAllocationTable.Length >= (i * 512) / 4 + 512 / 4) Array.Copy(batSector.blockPointer, 0, blockAllocationTable, (i * 512) / 4, 512 / 4); else Array.Copy(batSector.blockPointer, 0, blockAllocationTable, (i * 512) / 4, blockAllocationTable.Length - (i * 512) / 4); } DateTime endTime = DateTime.UtcNow; DicConsole.DebugWriteLine("VirtualPC plugin", "Filling the BAT took {0} seconds", (endTime - startTime).TotalSeconds); // Too noisy /* for (int i = 0; i < thisDynamic.maxTableEntries; i++) DicConsole.DebugWriteLine("VirtualPC plugin", "blockAllocationTable[{0}] = {1}", i, blockAllocationTable[i]); */ // Get the roundest number of sectors needed to store the block bitmap bitmapSize = (uint)Math.Ceiling(( // How many sectors do a block store ((double)thisDynamic.blockSize / 512) // 1 bit per sector on the bitmap / 8 // and aligned to 512 byte boundary / 512)); DicConsole.DebugWriteLine("VirtualPC plugin", "Bitmap is {0} sectors", bitmapSize); } ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; switch(thisFooter.diskType) { case typeFixed: case typeDynamic: { // Nothing to do here, really. return true; } case typeDifferencing: { locatorEntriesData = new byte[8][]; for(int i = 0; i < 8; i++) { if(thisDynamic.locatorEntries[i].platformCode != 0x00000000) { locatorEntriesData[i] = new byte[thisDynamic.locatorEntries[i].platformDataLength]; imageStream.Seek((long)thisDynamic.locatorEntries[i].platformDataOffset, SeekOrigin.Begin); imageStream.Read(locatorEntriesData[i], 0, (int)thisDynamic.locatorEntries[i].platformDataLength); switch(thisDynamic.locatorEntries[i].platformCode) { case platformCodeWindowsAbsolute: case platformCodeWindowsRelative: DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}] = \"{1}\"", i, Encoding.ASCII.GetString(locatorEntriesData[i])); break; case platformCodeWindowsAbsoluteU: case platformCodeWindowsRelativeU: DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}] = \"{1}\"", i, Encoding.BigEndianUnicode.GetString(locatorEntriesData[i])); break; case platformCodeMacintoshURI: DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}] = \"{1}\"", i, Encoding.UTF8.GetString(locatorEntriesData[i])); break; default: DicConsole.DebugWriteLine("VirtualPC plugin", "dynamic.locatorEntries[{0}] =", i); PrintHex.PrintHexArray(locatorEntriesData[i], 64); break; } } } int currentLocator = 0; bool locatorFound = false; string parentPath = null; FiltersList filters = new FiltersList(); while(!locatorFound && currentLocator < 8) { switch(thisDynamic.locatorEntries[currentLocator].platformCode) { case platformCodeWindowsAbsolute: case platformCodeWindowsRelative: parentPath = Encoding.ASCII.GetString(locatorEntriesData[currentLocator]); break; case platformCodeWindowsAbsoluteU: case platformCodeWindowsRelativeU: parentPath = Encoding.BigEndianUnicode.GetString(locatorEntriesData[currentLocator]); break; case platformCodeMacintoshURI: parentPath = Uri.UnescapeDataString(Encoding.UTF8.GetString(locatorEntriesData[currentLocator])); if(parentPath.StartsWith("file://localhost", StringComparison.InvariantCulture)) parentPath = parentPath.Remove(0, 16); else { DicConsole.DebugWriteLine("VirtualPC plugin", "Unsupported protocol classified found in URI parent path: \"{0}\"", parentPath); parentPath = null; } break; } if(parentPath != null) { DicConsole.DebugWriteLine("VirtualPC plugin", "Possible parent path: \"{0}\"", parentPath); Filter parentFilter = filters.GetFilter(Path.Combine(imageFilter.GetParentFolder(), parentPath)); if(parentFilter != null) locatorFound = true; if(!locatorFound) parentPath = null; } currentLocator++; } if(!locatorFound || parentPath == null) throw new FileNotFoundException("(VirtualPC plugin): Cannot find parent file for differencing disk image"); else { parentImage = new VHD(); Filter parentFilter = filters.GetFilter(Path.Combine(imageFilter.GetParentFolder(), parentPath)); if(parentFilter == null) throw new ImageNotSupportedException("(VirtualPC plugin): Cannot find parent image filter"); /* PluginBase plugins = new PluginBase(); plugins.RegisterAllPlugins(); if (!plugins.ImagePluginsList.TryGetValue(Name.ToLower(), out parentImage)) throw new SystemException("(VirtualPC plugin): Unable to open myself");*/ if(!parentImage.IdentifyImage(parentFilter)) throw new ImageNotSupportedException("(VirtualPC plugin): Parent image is not a Virtual PC disk image"); if(!parentImage.OpenImage(parentFilter)) throw new ImageNotSupportedException("(VirtualPC plugin): Cannot open parent disk image"); // While specification says that parent and child disk images should contain UUID relationship // in reality it seems that old differencing disk images stored a parent UUID that, nonetheless // the parent never stored itself. So the only real way to know that images are related is // because the parent IS found and SAME SIZE. Ugly... // More funny even, tested parent images show an empty host OS, and child images a correct one. if(parentImage.GetSectors() != GetSectors()) throw new ImageNotSupportedException("(VirtualPC plugin): Parent image is of different size"); } return true; } 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 MediaType GetMediaType() { return MediaType.GENERIC_HDD; } public override byte[] ReadSector(ulong sectorAddress) { switch(thisFooter.diskType) { case typeDifferencing: { // Block number for BAT searching uint blockNumber = (uint)Math.Floor((double)(sectorAddress / (thisDynamic.blockSize / 512))); // Sector number inside of block uint sectorInBlock = (uint)(sectorAddress % (thisDynamic.blockSize / 512)); byte[] bitmap = new byte[bitmapSize * 512]; // Offset of block in file uint blockOffset = blockAllocationTable[blockNumber] * 512; int bitmapByte = (int)Math.Floor((double)sectorInBlock / 8); int bitmapBit = (int)(sectorInBlock % 8); Stream thisStream = thisFilter.GetDataForkStream(); thisStream.Seek(blockOffset, SeekOrigin.Begin); thisStream.Read(bitmap, 0, (int)bitmapSize * 512); byte mask = (byte)(1 << (7 - bitmapBit)); bool dirty = false || (bitmap[bitmapByte] & mask) == mask; /* DicConsole.DebugWriteLine("VirtualPC plugin", "bitmapSize = {0}", bitmapSize); DicConsole.DebugWriteLine("VirtualPC plugin", "blockNumber = {0}", blockNumber); DicConsole.DebugWriteLine("VirtualPC plugin", "sectorInBlock = {0}", sectorInBlock); DicConsole.DebugWriteLine("VirtualPC plugin", "blockOffset = {0}", blockOffset); DicConsole.DebugWriteLine("VirtualPC plugin", "bitmapByte = {0}", bitmapByte); DicConsole.DebugWriteLine("VirtualPC plugin", "bitmapBit = {0}", bitmapBit); DicConsole.DebugWriteLine("VirtualPC plugin", "mask = 0x{0:X2}", mask); DicConsole.DebugWriteLine("VirtualPC plugin", "dirty = 0x{0}", dirty); DicConsole.DebugWriteLine("VirtualPC plugin", "bitmap = "); PrintHex.PrintHexArray(bitmap, 64); */ // Sector has been written, read from child image if(dirty) { /* Too noisy DicConsole.DebugWriteLine("VirtualPC plugin", "Sector {0} is dirty", sectorAddress); */ byte[] data = new byte[512]; uint sectorOffset = blockAllocationTable[blockNumber] + bitmapSize + sectorInBlock; thisStream = thisFilter.GetDataForkStream(); thisStream.Seek((sectorOffset * 512), SeekOrigin.Begin); thisStream.Read(data, 0, 512); return data; } /* Too noisy DicConsole.DebugWriteLine("VirtualPC plugin", "Sector {0} is clean", sectorAddress); */ // Read sector from parent image return parentImage.ReadSector(sectorAddress); } default: return ReadSectors(sectorAddress, 1); } } public override byte[] ReadSectors(ulong sectorAddress, uint length) { switch(thisFooter.diskType) { case typeFixed: { Stream thisStream; byte[] data = new byte[512 * length]; thisStream = thisFilter.GetDataForkStream(); thisStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); thisStream.Read(data, 0, (int)(512 * length)); return data; } // Contrary to Microsoft's specifications that tell us to check the bitmap // and in case of unused sector just return zeros, as blocks are allocated // as a whole, this would waste time and miss cache, so we read any sector // as long as it is in the block. case typeDynamic: { Stream thisStream; // Block number for BAT searching uint blockNumber = (uint)Math.Floor((double)(sectorAddress / (thisDynamic.blockSize / 512))); // Sector number inside of block uint sectorInBlock = (uint)(sectorAddress % (thisDynamic.blockSize / 512)); // How many sectors before reaching end of block uint remainingInBlock = (thisDynamic.blockSize / 512) - sectorInBlock; // Data that can be read in this block byte[] prefix; // Data that needs to be read from another block byte[] suffix = null; // How many sectors to read from this block uint sectorsToReadHere; // Asked to read more sectors than are remaining in block if(length > remainingInBlock) { suffix = ReadSectors(sectorAddress + remainingInBlock, length - remainingInBlock); sectorsToReadHere = remainingInBlock; } else sectorsToReadHere = length; // Offset of sector in file uint sectorOffset = blockAllocationTable[blockNumber] + bitmapSize + sectorInBlock; prefix = new byte[sectorsToReadHere * 512]; // 0xFFFFFFFF means unallocated if(sectorOffset != 0xFFFFFFFF) { thisStream = thisFilter.GetDataForkStream(); thisStream.Seek((sectorOffset * 512), SeekOrigin.Begin); thisStream.Read(prefix, 0, (int)(512 * sectorsToReadHere)); } // If it is unallocated, just fill with zeroes else Array.Clear(prefix, 0, prefix.Length); // If we needed to read from another block, join all the data if(suffix != null) { byte[] data = new byte[512 * length]; Array.Copy(prefix, 0, data, 0, prefix.Length); Array.Copy(suffix, 0, data, prefix.Length, suffix.Length); return data; } return prefix; } case typeDifferencing: { // As on differencing images, each independent sector can be read from child or parent // image, we must read sector one by one byte[] fullData = new byte[512 * length]; for(ulong i = 0; i < length; i++) { byte[] oneSector = ReadSector(sectorAddress + i); Array.Copy(oneSector, 0, fullData, (int)(i * 512), 512); } return fullData; } 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 static uint VHDChecksum(byte[] data) { uint 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(MediaTagType 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 GetMediaManufacturer() { return null; } public override string GetMediaModel() { return null; } public override string GetMediaSerialNumber() { return null; } public override string GetMediaBarcode() { return null; } public override string GetMediaPartNumber() { return null; } public override int GetMediaSequence() { 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 List GetPartitions() { throw new FeatureUnsupportedImageException("Feature not supported by image format"); } public override List GetTracks() { throw new FeatureUnsupportedImageException("Feature not supported by image format"); } public override List GetSessionTracks(Session session) { throw new FeatureUnsupportedImageException("Feature not supported by image format"); } public override List GetSessionTracks(ushort session) { throw new FeatureUnsupportedImageException("Feature not supported by image format"); } public override 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? VerifyMediaImage() { return null; } #endregion } }