From 122141c850f6e966188065f8c01ee76642a63c38 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 6 Sep 2016 23:21:10 +0100 Subject: [PATCH] * TODO: * README.md: * DiscImageChef.DiscImages/NDIF.cs: * DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj: Adds support for Apple NDIF images, closes #47 * Claunia.RsrcFork: * DiscImageChef.DiscImages/UDIF.cs: * DiscImageChef.DiscImages/DiskCopy42.cs: Try to get and use "vers" resource from the Resource Fork to get ImageApplication and its version. * DiscImageChef.Filters/OffsetStream.cs: Corrected OffsetStream arithmetic bug. --- Claunia.RsrcFork | 2 +- DiscImageChef.DiscImages/ChangeLog | 10 + .../DiscImageChef.DiscImages.csproj | 5 +- DiscImageChef.DiscImages/DiskCopy42.cs | 86 +- DiscImageChef.DiscImages/NDIF.cs | 734 ++++++++++++++++++ DiscImageChef.DiscImages/UDIF.cs | 65 +- DiscImageChef.Filters/ChangeLog | 4 + DiscImageChef.Filters/OffsetStream.cs | 6 +- README.md | 1 + TODO | 4 +- 10 files changed, 906 insertions(+), 11 deletions(-) create mode 100644 DiscImageChef.DiscImages/NDIF.cs diff --git a/Claunia.RsrcFork b/Claunia.RsrcFork index d83669a0..78920681 160000 --- a/Claunia.RsrcFork +++ b/Claunia.RsrcFork @@ -1 +1 @@ -Subproject commit d83669a004162cae796d2d379c692bf3d0aa3c65 +Subproject commit 78920681a8b0ccffe7512cd296075b7acf5849c4 diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index 13ddfa88..8c9b89da 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,13 @@ +2016-09-06 Natalia Portillo + + * NDIF.cs: + * DiscImageChef.DiscImages.csproj: Adds support for Apple NDIF + images, closes #47 + + * UDIF.cs: + * DiskCopy42.cs: Try to get and use "vers" resource from the + Resource Fork to get ImageApplication and its version. + 2016-09-05 Natalia Portillo * CDRWin.cs: Show correct filename from filter. diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index d0c4092f..e0450e63 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -63,6 +63,7 @@ + @@ -145,8 +146,8 @@ - - + + diff --git a/DiscImageChef.DiscImages/DiskCopy42.cs b/DiscImageChef.DiscImages/DiskCopy42.cs index bf33f328..256da211 100644 --- a/DiscImageChef.DiscImages/DiskCopy42.cs +++ b/DiscImageChef.DiscImages/DiskCopy42.cs @@ -31,10 +31,12 @@ // ****************************************************************************/ using System; -using System.IO; using System.Collections.Generic; -using DiscImageChef.Console; +using System.IO; +using System.Text.RegularExpressions; +using Claunia.RsrcFork; using DiscImageChef.CommonTypes; +using DiscImageChef.Console; using DiscImageChef.Filters; namespace DiscImageChef.ImagePlugins @@ -432,6 +434,86 @@ namespace DiscImageChef.ImagePlugins } } + System.Console.WriteLine("{0}", imageFilter); + try + { + if(imageFilter.HasResourceFork()) + { + ResourceFork rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); + if(rsrcFork.ContainsKey(0x76657273)) + { + Resource versRsrc = rsrcFork.GetResource(0x76657273); + if(versRsrc != null) + { + byte[] vers = versRsrc.GetResource(versRsrc.GetIds()[0]); + + if(vers != null) + { + Resources.Version version = new Resources.Version(vers); + + string major; + string minor; + string release = null; + string dev = null; + string pre = null; + + major = string.Format("{0}", version.MajorVersion); + minor = string.Format(".{0}", version.MinorVersion / 10); + if(version.MinorVersion % 10 > 0) + release = string.Format(".{0}", version.MinorVersion % 10); + switch(version.DevStage) + { + case Resources.Version.DevelopmentStage.Alpha: + dev = "a"; + break; + case Resources.Version.DevelopmentStage.Beta: + dev = "b"; + break; + case Resources.Version.DevelopmentStage.PreAlpha: + dev = "d"; + break; + } + + if(dev == null && version.PreReleaseVersion > 0) + dev = "f"; + + if(dev != null) + pre = string.Format("{0}", version.PreReleaseVersion); + + ImageInfo.imageApplicationVersion = string.Format("{0}{1}{2}{3}{4}", major, minor, release, dev, pre); + ImageInfo.imageApplication = version.VersionString; + ImageInfo.imageComments = version.VersionMessage; + } + } + } + + if(rsrcFork.ContainsKey(0x64437079)) + { + Resource dCpyRsrc = rsrcFork.GetResource(0x64437079); + if(dCpyRsrc != null) + { + // TODO: Use MacRoman + string dCpy = StringHandlers.PascalToString(dCpyRsrc.GetResource(dCpyRsrc.GetIds()[0])); + string dCpyRegEx = "(?\\S+)\\s(?\\S+)\\rData checksum=\\$(?\\S+)$"; + Regex dCpyEx = new Regex(dCpyRegEx); + Match dCpyMatch = dCpyEx.Match(dCpy); + + if(dCpyMatch.Success) + { + ImageInfo.imageApplication = dCpyMatch.Groups["application"].Value; + ImageInfo.imageApplicationVersion = dCpyMatch.Groups["version"].Value; + + // Until MacRoman is implemented + if(ImageInfo.imageApplication == "ShrinkWrap?") + ImageInfo.imageApplication = "ShrinkWrap™"; + } + } + } + } + } + catch(InvalidCastException) { } + DicConsole.DebugWriteLine("DC42 plugin", "Image application = {0} version {1}", ImageInfo.imageApplication, ImageInfo.imageApplicationVersion); + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; DicConsole.VerboseWriteLine("DiskCopy 4.2 image contains a disk of type {0}", ImageInfo.mediaType); diff --git a/DiscImageChef.DiscImages/NDIF.cs b/DiscImageChef.DiscImages/NDIF.cs new file mode 100644 index 00000000..4077d404 --- /dev/null +++ b/DiscImageChef.DiscImages/NDIF.cs @@ -0,0 +1,734 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : NDIF.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Apple New Disk Image Format. +// +// --[ 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 Claunia.RsrcFork; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; +using DiscImageChef.Filters; +using DiscImageChef.ImagePlugins; + +namespace DiscImageChef.DiscImages +{ + // TODO: Detect ShrinkWrap encrypted images + // TODO: Detect OS X encrypted images + // TODO: Check checksum + // TODO: Implement segments + // TODO: Implement compression + // TODO: Get application version from "vers" resource + public class NDIF : ImagePlugin + { + #region Internal constants + /// + /// Resource OSType for NDIF is "bcem" + /// + const uint NDIF_Resource = 0x6263656D; + /// + /// Resource ID is always 128? Never found another + /// + const short NDIF_ResourceID = 128; + #endregion + + #region Internal Structures + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ChunkHeader + { + /// + /// Type? All OS X generated ones are 12, all DiskCopy 6.3.3 RdWr are 10 but rest are 12 + /// + public short version; + /// + /// Driver? Changes with driver + /// + public short driver; + /// + /// Disk image name, Str63 (Pascal string) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] name; + /// + /// Sectors in image + /// + public uint sectors; + /// + /// Size of buffer, in sectors, for compression/decompression + /// + public uint bufferSize; + /// + /// Always 0? + /// + public uint zero; + /// + /// CRC28 of whole image + /// + public uint crc; + /// + /// Set to 1 if segmented + /// + public uint segmented; + /// + /// Unknown, spare? + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] + public uint[] unknown; + /// + /// Set to 1 by ShrinkWrap if image is encrypted + /// + public uint encrypted; + /// + /// Set by ShrinkWrap if image is encrypted, value is the same for same password + /// + public uint hash; + /// + /// How many chunks follow the header + /// + public uint chunks; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BlockChunk + { + /// + /// Starting sector, 3 bytes + /// + public uint sector; + /// + /// Chunk type + /// + public byte type; + /// + /// Offset in image to start of chunk + /// + public uint offset; + /// + /// Length in bytes of chunk + /// + public uint length; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SegmentHeader + { + /// + /// Segment # + /// + public ushort segment; + /// + /// How many segments + /// + public ushort segments; + /// + /// Seems to be a Guid, changes with different images, same for all segments of same image + /// + public Guid segmentId; + /// + /// Seems to be a CRC28 of this segment, unchecked + /// + public uint crc; + } + #endregion + + ChunkHeader header; + Dictionary chunks; + + const byte ChunkType_NoCopy = 0; + const byte ChunkType_Copy = 2; + const byte ChunkType_KenCode = 0x80; + const byte ChunkType_ADC = 0x83; + /// + /// Created by ShrinkWrap 3.5, dunno which version of the StuffIt algorithm it is using + /// + const byte ChunkType_StuffIt = 0xF0; + const byte ChunkType_End = 0xFF; + + const byte ChunkType_CompressedMask = 0x80; + + const short Driver_OSX = -1; + const short Driver_HFS = 0; + const short Driver_ProDOS = 256; + const short Driver_DOS = 18771; + + Dictionary sectorCache; + const uint MaxCacheSize = 16777216; + const uint sectorSize = 512; + uint maxCachedSectors = MaxCacheSize / sectorSize; + + Stream imageStream; + + public NDIF() + { + Name = "Apple New Disk Image Format"; + PluginUUID = new Guid("5A7FF7D8-491E-458D-8674-5B5EADBECC24"); + 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; + } + + public override bool IdentifyImage(Filter imageFilter) + { + if(!imageFilter.HasResourceFork() || imageFilter.GetResourceForkLength() == 0) + return false; + + ResourceFork rsrcFork; + + try + { + rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); + if(!rsrcFork.ContainsKey(NDIF_Resource)) + return false; + + Resource rsrc = rsrcFork.GetResource(NDIF_Resource); + + if(rsrc.ContainsId(NDIF_ResourceID)) + return true; + } + catch(InvalidCastException) + { + return false; + } + + return false; + } + + public override bool OpenImage(Filter imageFilter) + { + if(!imageFilter.HasResourceFork() || imageFilter.GetResourceForkLength() == 0) + return false; + + ResourceFork rsrcFork; + Resource rsrc; + byte[] bcem; + + try + { + rsrcFork = new ResourceFork(imageFilter.GetResourceForkStream()); + if(!rsrcFork.ContainsKey(NDIF_Resource)) + return false; + + rsrc = rsrcFork.GetResource(NDIF_Resource); + + if(!rsrc.ContainsId(NDIF_ResourceID)) + return false; + + bcem = rsrc.GetResource(NDIF_ResourceID); + } + catch(InvalidCastException) + { + return false; + } + + if(bcem.Length < 128) + return false; + + header = BigEndianMarshal.ByteArrayToStructureBigEndian(bcem); + + DicConsole.DebugWriteLine("NDIF plugin", "footer.type = {0}", header.version); + DicConsole.DebugWriteLine("NDIF plugin", "footer.driver = {0}", header.driver); + DicConsole.DebugWriteLine("NDIF plugin", "footer.name = {0}", StringHandlers.PascalToString(header.name)); + DicConsole.DebugWriteLine("NDIF plugin", "footer.sectors = {0}", header.sectors); + DicConsole.DebugWriteLine("NDIF plugin", "footer.bufferSize = {0}", header.bufferSize); + DicConsole.DebugWriteLine("NDIF plugin", "footer.zero = {0}", header.zero); + DicConsole.DebugWriteLine("NDIF plugin", "footer.crc = 0x{0:X7}", header.crc); + DicConsole.DebugWriteLine("NDIF plugin", "footer.segmented = {0}", header.segmented); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[0] = 0x{0:X8}", header.unknown[0]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[1] = 0x{0:X8}", header.unknown[1]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[2] = 0x{0:X8}", header.unknown[2]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[3] = 0x{0:X8}", header.unknown[3]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[4] = 0x{0:X8}", header.unknown[4]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[5] = 0x{0:X8}", header.unknown[5]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.unknown[6] = 0x{0:X8}", header.unknown[6]); + DicConsole.DebugWriteLine("NDIF plugin", "footer.encrypted = {0}", header.encrypted); + DicConsole.DebugWriteLine("NDIF plugin", "footer.hash = 0x{0:X8}", header.hash); + DicConsole.DebugWriteLine("NDIF plugin", "footer.chunks = {0}", header.chunks); + + // Block chunks and headers + chunks = new Dictionary(); + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + for(int i = 0; i < header.chunks; i++) + { + // Obsolete read-only NDIF only prepended the header and then put the image without any kind of block references. + // So let's falsify a block chunk + BlockChunk bChnk = new BlockChunk(); + byte[] sector = new byte[4]; + Array.Copy(bcem, 128 + 0 + i * 12, sector, 1, 3); + bChnk.sector = BigEndianBitConverter.ToUInt32(sector, 0); + bChnk.type = bcem[128 + 3 + i * 12]; + bChnk.offset = BigEndianBitConverter.ToUInt32(bcem, 128 + 4 + i * 12); + bChnk.length = BigEndianBitConverter.ToUInt32(bcem, 128 + 8 + i * 12); + + DicConsole.DebugWriteLine("NDIF plugin", "bHdr.chunk[{0}].type = 0x{1:X2}", i, bChnk.type); + DicConsole.DebugWriteLine("NDIF plugin", "bHdr.chunk[{0}].sector = {1}", i, bChnk.sector); + DicConsole.DebugWriteLine("NDIF plugin", "bHdr.chunk[{0}].offset = {1}", i, bChnk.offset); + DicConsole.DebugWriteLine("NDIF plugin", "bHdr.chunk[{0}].length = {1}", i, bChnk.length); + + if(bChnk.type == ChunkType_End) + { + ImageInfo.sectors = bChnk.sector; + break; + } + + // TODO: Handle compressed chunks + if((bChnk.type & ChunkType_CompressedMask) == ChunkType_CompressedMask) + throw new ImageNotSupportedException("Compressed chunks are not yet supported."); + + if(bChnk.type != ChunkType_Copy && bChnk.type != ChunkType_NoCopy) + throw new ImageNotSupportedException(string.Format("Unsupported chunk type 0x{0:X8} found", bChnk.type)); + + chunks.Add(bChnk.sector, bChnk); + } + + if(header.segmented > 0) + throw new ImageNotSupportedException("Segmented images are not yet supported."); + + if(header.encrypted > 0) + throw new ImageNotSupportedException("Encrypted images are not yet supported."); + + switch(ImageInfo.sectors) + { + case 1440: + ImageInfo.mediaType = MediaType.DOS_35_DS_DD_9; + break; + case 1600: + ImageInfo.mediaType = MediaType.AppleSonyDS; + break; + case 2880: + ImageInfo.mediaType = MediaType.DOS_35_HD; + break; + case 3360: + ImageInfo.mediaType = MediaType.DMF; + break; + default: + ImageInfo.mediaType = MediaType.GENERIC_HDD; + break; + } + + if(rsrcFork.ContainsKey(0x76657273)) + { + Resource versRsrc = rsrcFork.GetResource(0x76657273); + if(versRsrc != null) + { + byte[] vers = versRsrc.GetResource(versRsrc.GetIds()[0]); + + Resources.Version version = new Resources.Version(vers); + + string major; + string minor; + string release = null; + string dev = null; + string pre = null; + + major = string.Format("{0}", version.MajorVersion); + minor = string.Format(".{0}", version.MinorVersion / 10); + if(version.MinorVersion % 10 > 0) + release = string.Format(".{0}", version.MinorVersion % 10); + switch(version.DevStage) + { + case Resources.Version.DevelopmentStage.Alpha: + dev = "a"; + break; + case Resources.Version.DevelopmentStage.Beta: + dev = "b"; + break; + case Resources.Version.DevelopmentStage.PreAlpha: + dev = "d"; + break; + } + + if(dev == null && version.PreReleaseVersion > 0) + dev = "f"; + + if(dev != null) + pre = string.Format("{0}", version.PreReleaseVersion); + + ImageInfo.imageApplicationVersion = string.Format("{0}{1}{2}{3}{4}", major, minor, release, dev, pre); + ImageInfo.imageApplication = version.VersionString; + ImageInfo.imageComments = version.VersionMessage; + + if(version.MajorVersion == 3) + ImageInfo.imageApplication = "ShrinkWrap™"; + else if(version.MajorVersion == 6) + ImageInfo.imageApplication = "DiskCopy"; + } + } + DicConsole.DebugWriteLine("NDIF plugin", "Image application = {0} version {1}", ImageInfo.imageApplication, ImageInfo.imageApplicationVersion); + + sectorCache = new Dictionary(); + imageStream = imageFilter.GetDataForkStream(); + + ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); + ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); + ImageInfo.imageName = StringHandlers.PascalToString(header.name); + ImageInfo.sectorSize = sectorSize; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.imageSize = ImageInfo.sectors * sectorSize; + ImageInfo.imageApplicationVersion = "6"; + ImageInfo.imageApplication = "Apple DiskCopy"; + + 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)); + + byte[] sector; + + if(sectorCache.TryGetValue(sectorAddress, out sector)) + return sector; + + BlockChunk currentChunk = new BlockChunk(); + bool chunkFound = false; + ulong chunkStartSector = 0; + + foreach(KeyValuePair kvp in chunks) + { + if(sectorAddress >= kvp.Key) + { + currentChunk = kvp.Value; + chunkFound = true; + chunkStartSector = kvp.Key; + } + } + + long relOff = ((long)sectorAddress - (long)chunkStartSector) * sectorSize; + + if(relOff < 0) + throw new ArgumentOutOfRangeException(nameof(relOff), string.Format("Got a negative offset for sector {0}. This should not happen.", sectorAddress)); + + if(!chunkFound) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(currentChunk.type == ChunkType_NoCopy) + { + sector = new byte[sectorSize]; + + if(sectorCache.Count >= maxCachedSectors) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + return sector; + } + + if(currentChunk.type == ChunkType_Copy) + { + imageStream.Seek(currentChunk.offset + relOff, SeekOrigin.Begin); + sector = new byte[sectorSize]; + imageStream.Read(sector, 0, sector.Length); + + if(sectorCache.Count >= maxCachedSectors) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + return sector; + } + + throw new ImageNotSupportedException(string.Format("Unsupported chunk type 0x{0:X8} found", currentChunk.type)); + } + + 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"); + + 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 "Apple New Disk Image Format"; + } + + 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/UDIF.cs b/DiscImageChef.DiscImages/UDIF.cs index 17326b71..2568b830 100644 --- a/DiscImageChef.DiscImages/UDIF.cs +++ b/DiscImageChef.DiscImages/UDIF.cs @@ -262,6 +262,8 @@ namespace DiscImageChef.DiscImages bool fakeBlockChunks = false; + byte[] vers = null; + if(footer.plistLen == 0 && footer.rsrcForkLen != 0) { DicConsole.DebugWriteLine("UDIF plugin", "Reading resource fork."); @@ -284,6 +286,11 @@ namespace DiscImageChef.DiscImages foreach(short blkxId in blkxRez.GetIds()) blkxList.Add(blkxRez.GetResource(blkxId)); + + Resource versRez = rsrc.GetResource(0x76657273); + + if(versRez != null) + vers = versRez.GetResource(versRez.GetIds()[0]); } else if(footer.plistLen != 0) { @@ -307,7 +314,7 @@ namespace DiscImageChef.DiscImages NSObject blkxObj; if(!rsrc.TryGetValue(BlockKey, out blkxObj)) - throw new Exception("Could not retrieve partition array."); + throw new Exception("Could not retrieve block chunks array."); NSObject[] blkx = ((NSArray)blkxObj).GetArray(); @@ -324,6 +331,15 @@ namespace DiscImageChef.DiscImages blkxList.Add(((NSData)dataObj).Bytes); } + + NSObject versObj; + + if(rsrc.TryGetValue("vers", out versObj)) + { + NSObject[] versArray = ((NSArray)versObj).GetArray(); + if(versArray.Length >= 1) + vers = ((NSData)versArray[0]).Bytes; + } } else { @@ -340,6 +356,52 @@ namespace DiscImageChef.DiscImages fakeBlockChunks = true; } + if(vers != null) + { + Resources.Version version = new Resources.Version(vers); + + string major; + string minor; + string release = null; + string dev = null; + string pre = null; + + major = string.Format("{0}", version.MajorVersion); + minor = string.Format(".{0}", version.MinorVersion / 10); + if(version.MinorVersion % 10 > 0) + release = string.Format(".{0}", version.MinorVersion % 10); + switch(version.DevStage) + { + case Resources.Version.DevelopmentStage.Alpha: + dev = "a"; + break; + case Resources.Version.DevelopmentStage.Beta: + dev = "b"; + break; + case Resources.Version.DevelopmentStage.PreAlpha: + dev = "d"; + break; + } + + if(dev == null && version.PreReleaseVersion > 0) + dev = "f"; + + if(dev != null) + pre = string.Format("{0}", version.PreReleaseVersion); + + ImageInfo.imageApplicationVersion = string.Format("{0}{1}{2}{3}{4}", major, minor, release, dev, pre); + ImageInfo.imageApplication = version.VersionString; + ImageInfo.imageComments = version.VersionMessage; + + if(version.MajorVersion == 3) + ImageInfo.imageApplication = "ShrinkWrap™"; + else if(version.MajorVersion == 6) + ImageInfo.imageApplication = "DiskCopy"; + } + else + ImageInfo.imageApplication = "DiskCopy"; + DicConsole.DebugWriteLine("UDIF plugin", "Image application = {0} version {1}", ImageInfo.imageApplication, ImageInfo.imageApplicationVersion); + if(!fakeBlockChunks) { if(blkxList.Count == 0) @@ -420,7 +482,6 @@ namespace DiscImageChef.DiscImages ImageInfo.mediaType = MediaType.GENERIC_HDD; ImageInfo.imageSize = ImageInfo.sectors * sectorSize; ImageInfo.imageVersion = string.Format("{0}", footer.version); - ImageInfo.imageApplication = "Apple DiskCopy"; return true; } diff --git a/DiscImageChef.Filters/ChangeLog b/DiscImageChef.Filters/ChangeLog index abd63543..1c1b9af5 100644 --- a/DiscImageChef.Filters/ChangeLog +++ b/DiscImageChef.Filters/ChangeLog @@ -1,3 +1,7 @@ +2016-09-06 Natalia Portillo + + * OffsetStream.cs: Corrected OffsetStream arithmetic bug. + 2016-09-05 Natalia Portillo * AppleDouble.cs: diff --git a/DiscImageChef.Filters/OffsetStream.cs b/DiscImageChef.Filters/OffsetStream.cs index 14000f77..7b4a9d61 100644 --- a/DiscImageChef.Filters/OffsetStream.cs +++ b/DiscImageChef.Filters/OffsetStream.cs @@ -369,7 +369,7 @@ namespace DiscImageChef.Filters { get { - return streamEnd - streamStart; + return streamEnd - streamStart + 1; } } @@ -428,7 +428,7 @@ namespace DiscImageChef.Filters public override int ReadByte() { - return baseStream.Position == streamEnd ? -1 : baseStream.ReadByte(); + return baseStream.Position == streamEnd + 1 ? -1 : baseStream.ReadByte(); } public override void WriteByte(byte value) @@ -446,7 +446,7 @@ namespace DiscImageChef.Filters public override int Read(byte[] buffer, int offset, int count) { - if(baseStream.Position + count > streamEnd) + if(baseStream.Position + count > streamEnd + 1) throw new IOException("Cannot read past stream end."); return baseStream.Read(buffer, offset, count); diff --git a/README.md b/README.md index 0be1fdf5..2863c6aa 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Supported disk image formats * Microsoft VHDX * VMware VMDK and COWD images * Apple Universal Disk Image Format (UDIF), including obsolete (previous than DiskCopy 6) versions +* Apple New Disk Image Format (NDIF, requires Resource Fork) Supported partitioning schemes ============================== diff --git a/TODO b/TODO index 210275f4..26b75efd 100644 --- a/TODO +++ b/TODO @@ -6,7 +6,6 @@ --- Add support for Kryoflux images --- Add support for DiscFerret images --- Add support for MAME CHDs ---- Add support for Apple NDIF images --- Add support for XPACK images Filesystem plugins: @@ -67,6 +66,9 @@ VMDK plugin: UDIF plugin: --- Add support for compressed chunks +NDIF plugin: +--- Add support for compressed chunks + Filters: --- Add support for BZip2 compressed files --- Add support for XZ compressed files