diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index 6664da043..89e0dca4e 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,11 @@ +2016-08-27 Natalia Portillo + + * Parallels.cs: + * DiscImageChef.DiscImages.csproj: Adds support for Parallels + Hard Disk Image (HDD) version 2. + + * QCOW2.cs: Removed spurious debug line. + 2016-08-27 Natalia Portillo * QCOW2.cs: diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index 519bfc18c..398d91047 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -55,6 +55,7 @@ + diff --git a/DiscImageChef.DiscImages/Parallels.cs b/DiscImageChef.DiscImages/Parallels.cs new file mode 100644 index 000000000..dc0431c3a --- /dev/null +++ b/DiscImageChef.DiscImages/Parallels.cs @@ -0,0 +1,531 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Parallels.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Parallels 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-2016 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.ImagePlugins; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; + +namespace DiscImageChef.DiscImages +{ + public class Parallels : ImagePlugin + { + #region Internal constants + readonly byte[] ParallelsMagic = { 0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x74, 0x46, 0x72, 0x65, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65 }; + readonly byte[] ParallelsExtMagic = { 0x57, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x46, 0x72, 0x65, 0x53, 0x70, 0x61, 0x63, 0x45, 0x78, 0x74 }; + + const uint ParallelsVersion = 2; + + const uint ParallelsInUse = 0x746F6E59; + const uint ParallelsClosed = 0x312E3276; + + const uint ParallelsEmpty = 0x00000001; + #endregion + + #region Internal Structures + [StructLayout(LayoutKind.Sequential, Pack = 1)] + /// + /// Parallels disk image header, little-endian + /// + struct ParallelsHeader + { + /// + /// Magic, or + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] magic; + /// + /// Version + /// + public uint version; + /// + /// Disk geometry parameter + /// + public uint heads; + /// + /// Disk geometry parameter + /// + public uint cylinders; + /// + /// Cluser size in sectors + /// + public uint cluster_size; + /// + /// Entries in BAT (clusters in image) + /// + public uint bat_entries; + /// + /// Disk size in sectors + /// + public ulong sectors; + /// + /// Set to if image is opened by any software, if not, and 0 if old version + /// + public uint in_use; + /// + /// Offset in sectors to start of data + /// + public uint data_off; + /// + /// Flags + /// + public uint flags; + /// + /// Offset in sectors to format extension + /// + public ulong ext_off; + } + #endregion + + bool extended; + ParallelsHeader pHdr; + uint[] BAT; + long dataOffset; + uint clusterBytes; + bool empty; + FileStream imageStream; + + Dictionary sectorCache; + + const uint MaxCacheSize = 16777216; + uint maxCachedSectors = MaxCacheSize / 512; + + public Parallels() + { + Name = "Parallels disk image"; + PluginUUID = new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableMediaTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageVersion = "2"; + ImageInfo.imageApplication = "Parallels"; + 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; + } + + public override bool IdentifyImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] pHdr_b = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdr_b, 0, Marshal.SizeOf(pHdr)); + pHdr = new ParallelsHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdr_b, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); + Marshal.FreeHGlobal(headerPtr); + + return ParallelsMagic.SequenceEqual(pHdr.magic) || ParallelsExtMagic.SequenceEqual(pHdr.magic); + } + + public override bool OpenImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] pHdr_b = new byte[Marshal.SizeOf(pHdr)]; + stream.Read(pHdr_b, 0, Marshal.SizeOf(pHdr)); + pHdr = new ParallelsHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pHdr)); + Marshal.Copy(pHdr_b, 0, headerPtr, Marshal.SizeOf(pHdr)); + pHdr = (ParallelsHeader)Marshal.PtrToStructure(headerPtr, typeof(ParallelsHeader)); + Marshal.FreeHGlobal(headerPtr); + + DicConsole.DebugWriteLine("QED plugin", "pHdr.magic = {0}", StringHandlers.CToString(pHdr.magic)); + DicConsole.DebugWriteLine("QED plugin", "pHdr.version = {0}", pHdr.version); + DicConsole.DebugWriteLine("QED plugin", "pHdr.heads = {0}", pHdr.heads); + DicConsole.DebugWriteLine("QED plugin", "pHdr.cylinders = {0}", pHdr.cylinders); + DicConsole.DebugWriteLine("QED plugin", "pHdr.cluster_size = {0}", pHdr.cluster_size); + DicConsole.DebugWriteLine("QED plugin", "pHdr.bat_entries = {0}", pHdr.bat_entries); + DicConsole.DebugWriteLine("QED plugin", "pHdr.sectors = {0}", pHdr.sectors); + DicConsole.DebugWriteLine("QED plugin", "pHdr.in_use = 0x{0:X8}", pHdr.in_use); + DicConsole.DebugWriteLine("QED plugin", "pHdr.data_off = {0}", pHdr.data_off); + DicConsole.DebugWriteLine("QED plugin", "pHdr.flags = {0}", pHdr.flags); + DicConsole.DebugWriteLine("QED plugin", "pHdr.ext_off = {0}", pHdr.ext_off); + + extended = ParallelsExtMagic.SequenceEqual(pHdr.magic); + DicConsole.DebugWriteLine("QED plugin", "pHdr.extended = {0}", extended); + + DicConsole.DebugWriteLine("QED plugin", "Reading BAT"); + BAT = new uint[pHdr.bat_entries]; + byte[] BAT_b = new byte[pHdr.bat_entries * 4]; + stream.Read(BAT_b, 0, BAT_b.Length); + for(int i = 0; i < BAT.Length; i++) + BAT[i] = BitConverter.ToUInt32(BAT_b, i * 4); + + clusterBytes = pHdr.cluster_size * 512; + if(pHdr.data_off > 0) + dataOffset = pHdr.data_off * 512; + else + dataOffset = ((stream.Position / clusterBytes) + (stream.Position % clusterBytes)) * clusterBytes; + + sectorCache = new Dictionary(); + + empty = (pHdr.flags & ParallelsEmpty) == ParallelsEmpty; + + FileInfo fi = new FileInfo(imagePath); + ImageInfo.imageCreationTime = fi.CreationTimeUtc; + ImageInfo.imageLastModificationTime = fi.LastWriteTimeUtc; + ImageInfo.imageName = Path.GetFileNameWithoutExtension(imagePath); + ImageInfo.sectors = pHdr.sectors; + ImageInfo.sectorSize = 512; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.mediaType = MediaType.GENERIC_HDD; + ImageInfo.imageSize = pHdr.sectors * 512; + imageStream = stream; + + return true; + } + + public override byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(empty) + return new byte[512]; + + byte[] sector; + + if(sectorCache.TryGetValue(sectorAddress, out sector)) + return sector; + + ulong index = sectorAddress / pHdr.cluster_size; + ulong secOff = sectorAddress % pHdr.cluster_size; + + uint batOff = BAT[index]; + ulong imageOff; + + if(batOff == 0) + return new byte[512]; + + if(extended) + imageOff = batOff * clusterBytes; + else + imageOff = batOff * 512; + + byte[] cluster = new byte[clusterBytes]; + imageStream.Seek((long)imageOff, SeekOrigin.Begin); + imageStream.Read(cluster, 0, (int)clusterBytes); + sector = new byte[512]; + Array.Copy(cluster, (int)(secOff * 512), sector, 0, 512); + + if(sectorCache.Count > maxCachedSectors) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + if(empty) + return new byte[512 * length]; + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + 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() + { + return "Parallels"; + } + + 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 string GetImageComments() + { + return ImageInfo.imageComments; + } + + public override MediaType GetMediaType() + { + return ImageInfo.mediaType; + } + + #region Unsupported features + + public override byte[] ReadSectorTag(ulong sectorAddress, 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[] ReadDiskTag(MediaTagType 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[] 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 + } +} + diff --git a/DiscImageChef.DiscImages/QCOW2.cs b/DiscImageChef.DiscImages/QCOW2.cs index 696355117..b2deb5350 100644 --- a/DiscImageChef.DiscImages/QCOW2.cs +++ b/DiscImageChef.DiscImages/QCOW2.cs @@ -189,8 +189,6 @@ namespace DiscImageChef.ImagePlugins public override bool IdentifyImage(string imagePath) { - System.Console.WriteLine("Entering QCOW2 identify"); - FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); stream.Seek(0, SeekOrigin.Begin); diff --git a/README.md b/README.md index b655281a1..129959391 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Supported disk image formats * CPCEMU Disk file and Extended Disk File * QEMU Copy-On-Write versions 1, 2 and 3 (QCOW and QCOW2) * QEMU Enhanced Disk (QED) +* Parallels Hard Disk Image (HDD) version 2 Supported partitioning schemes ==============================