diff --git a/DiscImageChef.DiscImages/CHD.cs b/DiscImageChef.DiscImages/CHD.cs index ba5dac1c..64d9a048 100644 --- a/DiscImageChef.DiscImages/CHD.cs +++ b/DiscImageChef.DiscImages/CHD.cs @@ -5,11 +5,11 @@ // Filename : CHD.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Manages MAME Compressed Hunks of Data disk images. // // --[ License ] -------------------------------------------------------------- // @@ -29,14 +29,2451 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; -namespace DiscImageChef.DiscImages +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Claunia.RsrcFork; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; +using DiscImageChef.Filters; +using System.Linq; +using System.Text; +using SharpCompress.Compressor.Deflate; +using SharpCompress.Compressor; +using System.Reflection; +using System.Reflection.Emit; + +namespace DiscImageChef.ImagePlugins { - public class CHD - { - public CHD() - { - } - } + // TODO: Implement PCMCIA support + class CHD : ImagePlugin + { + #region Internal Structures + + enum CHDCompression : uint + { + None = 0, + Zlib = 1, + ZlibPlus = 2, + AV = 3 + } + + enum CHDFlags : uint + { + HasParent = 1, + Writable = 2 + } + + enum CHDV3EntryFlags : byte + { + /// Invalid + Invalid = 0, + /// Compressed with primary codec + Compressed = 1, + /// Uncompressed + Uncompressed = 2, + /// Use offset as data + Mini = 3, + /// Same as another hunk in file + SelfHunk = 4, + /// Same as another hunk in parent + ParentHunk = 5, + /// Compressed with secondary codec (FLAC) + SecondCompressed = 6 + } + + enum CHDOldTrackType : uint + { + Mode1 = 0, + Mode1_Raw, + Mode2, + Mode2Form1, + Mode2Form2, + Mode2FormMix, + Mode2Raw, + Audio + } + + enum CHDOldSubType : uint + { + Cooked = 0, + Raw, + None + } + + // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length + // Sectors are fixed at 512 bytes/sector + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDHeaderV1 + { + /// + /// Magic identifier, 'MComprHD' + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// + /// Length of header + /// + public uint length; + /// + /// Image format version + /// + public uint version; + /// + /// Image flags, + /// + public uint flags; + /// + /// Compression algorithm, + /// + public uint compression; + /// + /// Sectors per hunk + /// + public uint hunksize; + /// + /// Total # of hunk in image + /// + public uint totalhunks; + /// + /// Cylinders on disk + /// + public uint cylinders; + /// + /// Heads per cylinder + /// + public uint heads; + /// + /// Sectors per track + /// + public uint sectors; + /// + /// MD5 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// + /// MD5 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + } + + // Hunks are represented in a 64 bit integer with 44 bit as offset, 20 bits as length + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDHeaderV2 + { + /// + /// Magic identifier, 'MComprHD' + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// + /// Length of header + /// + public uint length; + /// + /// Image format version + /// + public uint version; + /// + /// Image flags, + /// + public uint flags; + /// + /// Compression algorithm, + /// + public uint compression; + /// + /// Sectors per hunk + /// + public uint hunksize; + /// + /// Total # of hunk in image + /// + public uint totalhunks; + /// + /// Cylinders on disk + /// + public uint cylinders; + /// + /// Heads per cylinder + /// + public uint heads; + /// + /// Sectors per track + /// + public uint sectors; + /// + /// MD5 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// + /// MD5 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + /// + /// Bytes per sector + /// + public uint seclen; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDHeaderV3 + { + /// + /// Magic identifier, 'MComprHD' + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// + /// Length of header + /// + public uint length; + /// + /// Image format version + /// + public uint version; + /// + /// Image flags, + /// + public uint flags; + /// + /// Compression algorithm, + /// + public uint compression; + /// + /// Total # of hunk in image + /// + public uint totalhunks; + /// + /// Total bytes in image + /// + public ulong logicalbytes; + /// + /// Offset to first metadata blob + /// + public ulong metaoffset; + /// + /// MD5 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] md5; + /// + /// MD5 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] parentmd5; + /// + /// Bytes per hunk + /// + public uint hunkbytes; + /// + /// SHA1 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// + /// SHA1 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDMapV3Entry + { + /// + /// Offset to hunk from start of image + /// + public ulong offset; + /// + /// CRC32 of uncompressed hunk + /// + public uint crc; + /// + /// Lower 16 bits of length + /// + public ushort lengthLsb; + /// + /// Upper 8 bits of length + /// + public byte length; + /// + /// Hunk flags + /// + public byte flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDTrackOld + { + public uint type; + public uint subType; + public uint dataSize; + public uint subSize; + public uint frames; + public uint extraFrames; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDHeaderV4 + { + /// + /// Magic identifier, 'MComprHD' + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// + /// Length of header + /// + public uint length; + /// + /// Image format version + /// + public uint version; + /// + /// Image flags, + /// + public uint flags; + /// + /// Compression algorithm, + /// + public uint compression; + /// + /// Total # of hunk in image + /// + public uint totalhunks; + /// + /// Total bytes in image + /// + public ulong logicalbytes; + /// + /// Offset to first metadata blob + /// + public ulong metaoffset; + /// + /// Bytes per hunk + /// + public uint hunkbytes; + /// + /// SHA1 of raw+meta data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// + /// SHA1 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + /// + /// SHA1 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] rawsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDHeaderV5 + { + /// + /// Magic identifier, 'MComprHD' + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] tag; + /// + /// Length of header + /// + public uint length; + /// + /// Image format version + /// + public uint version; + /// + /// Compressor 0 + /// + public uint compressor0; + /// + /// Compressor 1 + /// + public uint compressor1; + /// + /// Compressor 2 + /// + public uint compressor2; + /// + /// Compressor 3 + /// + public uint compressor3; + /// + /// Total bytes in image + /// + public ulong logicalbytes; + /// + /// Offset to hunk map + /// + public ulong mapoffset; + /// + /// Offset to first metadata blob + /// + public ulong metaoffset; + /// + /// Bytes per hunk + /// + public uint hunkbytes; + /// + /// Bytes per unit within hunk + /// + public uint unitbytes; + /// + /// SHA1 of raw data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] rawsha1; + /// + /// SHA1 of raw+meta data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] sha1; + /// + /// SHA1 of parent file + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] parentsha1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDCompressedMapHeaderV5 + { + /// + /// Length of compressed map + /// + public uint length; + /// + /// Offset of first block (48 bits) and CRC16 of map (16 bits) + /// + public ulong startAndCrc; + /// + /// Bits used to encode compressed length on map entry + /// + public byte bitsUsedToEncodeCompLength; + /// + /// Bits used to encode self-refs + /// + public byte bitsUsedToEncodeSelfRefs; + /// + /// Bits used to encode parent unit refs + /// + public byte bitsUsedToEncodeParentUnits; + public byte reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDMapV5Entry + { + /// + /// Compression (8 bits) and length (24 bits) + /// + public uint compAndLength; + /// + /// Offset (48 bits) and CRC (16 bits) + /// + public ulong offsetAndCrc; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CHDMetadataHeader + { + public uint tag; + public uint flagsAndLength; + public ulong next; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HunkSector + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public ulong[] hunkEntry; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HunkSectorSmall + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public uint[] hunkEntry; + } + #endregion + + #region Internal Constants + + /// "MComprHD" + readonly byte[] chdTag = { 0x4D, 0x43, 0x6F, 0x6D, 0x70, 0x72, 0x48, 0x44 }; + /// "GDDD" + const uint hardDiskMetadata = 0x47444444; + /// "IDNT" + const uint hardDiskIdentMetadata = 0x49444E54; + /// "KEY " + const uint hardDiskKeyMetadata = 0x4B455920; + /// "CIS " + const uint pcmciaCisMetadata = 0x43495320; + /// "CHCD" + const uint cdromOldMetadata = 0x43484344; + /// "CHTR" + const uint cdromTrackMetadata = 0x43485452; + /// "CHT2" + const uint cdromTrackMetadata2 = 0x43485432; + /// "CHGT" + const uint gdromOldMetadata = 0x43484754; + /// "CHGD" + const uint gdromMetadata = 0x43484744; + /// "AVAV" + const uint avMetadata = 0x41564156; + /// "AVLD" + const uint avLaserDiscMetadata = 0x41564C44; + + const string hardDiskMetadataRegEx = "CYLS:(?\\d+),HEADS:(?\\d+),SECS:(?\\d+),BPS:(?\\d+)"; + const string CdromMetadataRegEx = "TRACK:(?\\d+) TYPE:(?\\S+) SUBTYPE:(?\\S+) FRAMES:(?\\d+)"; + const string CdromMetadata2RegEx = "TRACK:(?\\d+) TYPE:(?\\S+) SUBTYPE:(?\\S+) FRAMES:(?\\d+) PREGAP:(?\\d+) PGTYPE:(?\\S+) PGSUB:(?\\S+) POSTGAP:(?\\d+)"; + const string GdromMetadataRegEx = "TRACK:(?\\d+) TYPE:(?\\S+) SUBTYPE:(?\\S+) FRAMES:(?\\d+) PAD:(?\\d+) PREGAP:(?\\d+) PGTYPE:(?\\S+) PGSUB:(?\\S+) POSTGAP:(?\\d+)"; + + const string TrackTypeMode1 = "MODE1"; + const string TrackTypeMode1_2k = "MODE1/2048"; + const string TrackTypeMode1Raw = "MODE1_RAW"; + const string TrackTypeMode1Raw_2k = "MODE1/2352"; + const string TrackTypeMode2 = "MODE2"; + const string TrackTypeMode2_2k = "MODE2/2336"; + const string TrackTypeMode2F1 = "MODE2_FORM1"; + const string TrackTypeMode2F1_2k = "MODE2/2048"; + const string TrackTypeMode2F2 = "MODE2_FORM2"; + const string TrackTypeMode2F2_2k = "MODE2/2324"; + const string TrackTypeMode2FM = "MODE2_FORM_MIX"; + const string TrackTypeMode2Raw = "MODE2_RAW"; + const string TrackTypeMode2Raw_2k = "MODE2/2352"; + const string TrackTypeAudio = "AUDIO"; + + const string SubTypeCooked = "RW"; + const string SubTypeRaw = "RW_RAW"; + const string SubTypeNone = "NONE"; + + #endregion + + #region Internal variables + + ulong[] hunkTable; + uint[] hunkTableSmall; + uint hdrCompression; + uint hdrCompression1; + uint hdrCompression2; + uint hdrCompression3; + Stream imageStream; + uint sectorsPerHunk; + byte[] hunkMap; + uint mapVersion; + uint bytesPerHunk; + uint totalHunks; + byte[] expectedChecksum; + bool isCdrom; + bool isHdd; + bool isGdrom; + bool swapAudio; + + const int MaxCacheSize = 16777216; + int maxBlockCache; + int maxSectorCache; + + Dictionary sectorCache; + Dictionary hunkCache; + + Dictionary tracks; + List partitions; + Dictionary offsetmap; + + byte[] identify; + + #endregion + + public CHD() + { + Name = "MAME Compressed Hunks of Data"; + PluginUUID = new Guid("0D50233A-08BD-47D4-988B-27EAA0358597"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableMediaTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageApplication = "MAME"; + 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) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + byte[] magic = new byte[8]; + stream.Read(magic, 0, 8); + + return chdTag.SequenceEqual(magic); + } + + public override bool OpenImage(Filter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[8]; + byte[] magic = new byte[8]; + stream.Read(magic, 0, 8); + if(!chdTag.SequenceEqual(magic)) + return false; + // Read length + buffer = new byte[4]; + stream.Read(buffer, 0, 4); + uint length = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0); + buffer = new byte[4]; + stream.Read(buffer, 0, 4); + uint version = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0); + + buffer = new byte[length]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(buffer, 0, (int)length); + + ulong nextMetaOff = 0; + + switch(version) + { + case 1: + { + CHDHeaderV1 hdrV1 = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV1.tag)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.length = {0} bytes", hdrV1.length); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.version = {0}", hdrV1.version); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.flags = {0}", (CHDFlags)hdrV1.flags); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.compression = {0}", (CHDCompression)hdrV1.compression); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.hunksize = {0}", hdrV1.hunksize); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.totalhunks = {0}", hdrV1.totalhunks); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.cylinders = {0}", hdrV1.cylinders); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.heads = {0}", hdrV1.heads); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.sectors = {0}", hdrV1.sectors); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV1.md5)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV1.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV1.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV1.parentmd5)); + + DicConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); + DateTime start = DateTime.UtcNow; + + hunkTable = new ulong[hdrV1.totalhunks]; + + uint hunkSectorCount = (uint)Math.Ceiling(((double)hdrV1.totalhunks * 8) / 512); + + byte[] hunkSectorBytes = new byte[512]; + HunkSector hunkSector = new HunkSector(); + + for(int i = 0; i < hunkSectorCount; i++) + { + stream.Read(hunkSectorBytes, 0, 512); + // This does the big-endian trick but reverses the order of elements also + Array.Reverse(hunkSectorBytes); + GCHandle handle = GCHandle.Alloc(hunkSectorBytes, GCHandleType.Pinned); + hunkSector = (HunkSector)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(HunkSector)); + handle.Free(); + // This restores the order of elements + Array.Reverse(hunkSector.hunkEntry); + if(hunkTable.Length >= (i * 512) / 8 + 512 / 8) + Array.Copy(hunkSector.hunkEntry, 0, hunkTable, (i * 512) / 8, 512 / 8); + else + Array.Copy(hunkSector.hunkEntry, 0, hunkTable, (i * 512) / 8, hunkTable.Length - (i * 512) / 8); + } + DateTime end = DateTime.UtcNow; + System.Console.WriteLine("Took {0} seconds", (end - start).TotalSeconds); + + ImageInfo.mediaType = MediaType.GENERIC_HDD; + ImageInfo.sectors = hdrV1.hunksize * hdrV1.totalhunks; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.sectorSize = 512; + ImageInfo.imageVersion = "1"; + ImageInfo.imageSize = ImageInfo.sectorSize * hdrV1.hunksize * hdrV1.totalhunks; + + totalHunks = hdrV1.totalhunks; + sectorsPerHunk = hdrV1.hunksize; + hdrCompression = hdrV1.compression; + mapVersion = 1; + isHdd = true; + + break; + } + case 2: + { + CHDHeaderV2 hdrV2 = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV2.tag)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.length = {0} bytes", hdrV2.length); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.version = {0}", hdrV2.version); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.flags = {0}", (CHDFlags)hdrV2.flags); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.compression = {0}", (CHDCompression)hdrV2.compression); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.hunksize = {0}", hdrV2.hunksize); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.totalhunks = {0}", hdrV2.totalhunks); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.cylinders = {0}", hdrV2.cylinders); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.heads = {0}", hdrV2.heads); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.sectors = {0}", hdrV2.sectors); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV2.md5)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV2.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV2.parentmd5)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV2.seclen = {0}", hdrV2.seclen); + + DicConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); + DateTime start = DateTime.UtcNow; + + hunkTable = new ulong[hdrV2.totalhunks]; + + // How many sectors uses the BAT + uint hunkSectorCount = (uint)Math.Ceiling(((double)hdrV2.totalhunks * 8) / 512); + + byte[] hunkSectorBytes = new byte[512]; + HunkSector hunkSector = new HunkSector(); + + for(int i = 0; i < hunkSectorCount; i++) + { + stream.Read(hunkSectorBytes, 0, 512); + // This does the big-endian trick but reverses the order of elements also + Array.Reverse(hunkSectorBytes); + GCHandle handle = GCHandle.Alloc(hunkSectorBytes, GCHandleType.Pinned); + hunkSector = (HunkSector)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(HunkSector)); + handle.Free(); + // This restores the order of elements + Array.Reverse(hunkSector.hunkEntry); + if(hunkTable.Length >= (i * 512) / 8 + 512 / 8) + Array.Copy(hunkSector.hunkEntry, 0, hunkTable, (i * 512) / 8, 512 / 8); + else + Array.Copy(hunkSector.hunkEntry, 0, hunkTable, (i * 512) / 8, hunkTable.Length - (i * 512) / 8); + } + DateTime end = DateTime.UtcNow; + System.Console.WriteLine("Took {0} seconds", (end - start).TotalSeconds); + + ImageInfo.mediaType = MediaType.GENERIC_HDD; + ImageInfo.sectors = hdrV2.hunksize * hdrV2.totalhunks; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.sectorSize = hdrV2.seclen; + ImageInfo.imageVersion = "2"; + ImageInfo.imageSize = ImageInfo.sectorSize * hdrV2.hunksize * hdrV2.totalhunks; + + totalHunks = hdrV2.totalhunks; + sectorsPerHunk = hdrV2.hunksize; + hdrCompression = hdrV2.compression; + mapVersion = 1; + isHdd = true; + + break; + } + case 3: + { + CHDHeaderV3 hdrV3 = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV3.tag)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.length = {0} bytes", hdrV3.length); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.version = {0}", hdrV3.version); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.flags = {0}", (CHDFlags)hdrV3.flags); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.compression = {0}", (CHDCompression)hdrV3.compression); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.totalhunks = {0}", hdrV3.totalhunks); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.logicalbytes = {0}", hdrV3.logicalbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.metaoffset = {0}", hdrV3.metaoffset); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.md5)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV3.parentmd5)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.hunkbytes = {0}", hdrV3.hunkbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.sha1)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV3.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV3.parentsha1)); + + DicConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); + DateTime start = DateTime.UtcNow; + + hunkMap = new byte[hdrV3.totalhunks * 16]; + stream.Read(hunkMap, 0, hunkMap.Length); + + DateTime end = DateTime.UtcNow; + System.Console.WriteLine("Took {0} seconds", (end - start).TotalSeconds); + + nextMetaOff = hdrV3.metaoffset; + + ImageInfo.imageSize = hdrV3.logicalbytes; + ImageInfo.imageVersion = "3"; + + totalHunks = hdrV3.totalhunks; + bytesPerHunk = hdrV3.hunkbytes; + hdrCompression = hdrV3.compression; + mapVersion = 3; + + break; + } + case 4: + { + CHDHeaderV4 hdrV4 = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV4.tag)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.length = {0} bytes", hdrV4.length); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.version = {0}", hdrV4.version); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.flags = {0}", (CHDFlags)hdrV4.flags); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.compression = {0}", (CHDCompression)hdrV4.compression); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.totalhunks = {0}", hdrV4.totalhunks); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.logicalbytes = {0}", hdrV4.logicalbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.metaoffset = {0}", hdrV4.metaoffset); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.hunkbytes = {0}", hdrV4.hunkbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.sha1)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV4.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV4.parentsha1)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV4.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.rawsha1)); + + DicConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); + DateTime start = DateTime.UtcNow; + + hunkMap = new byte[hdrV4.totalhunks * 16]; + stream.Read(hunkMap, 0, hunkMap.Length); + + DateTime end = DateTime.UtcNow; + System.Console.WriteLine("Took {0} seconds", (end - start).TotalSeconds); + + nextMetaOff = hdrV4.metaoffset; + + ImageInfo.imageSize = hdrV4.logicalbytes; + ImageInfo.imageVersion = "4"; + + totalHunks = hdrV4.totalhunks; + bytesPerHunk = hdrV4.hunkbytes; + hdrCompression = hdrV4.compression; + mapVersion = 3; + + break; + } + case 5: + { + CHDHeaderV5 hdrV5 = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV5.tag)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.length = {0} bytes", hdrV5.length); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.version = {0}", hdrV5.version); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor0 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor0))); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor1 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor1))); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor2 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor2))); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor3 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor3))); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.logicalbytes = {0}", hdrV5.logicalbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.mapoffset = {0}", hdrV5.mapoffset); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.metaoffset = {0}", hdrV5.metaoffset); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.hunkbytes = {0}", hdrV5.hunkbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.unitbytes = {0}", hdrV5.unitbytes); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.sha1)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV5.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV5.parentsha1)); + DicConsole.DebugWriteLine("CHD plugin", "hdrV5.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.rawsha1)); + + // TODO: Implement compressed CHD v5 + if(hdrV5.compressor0 == 0) + { + DicConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); + DateTime start = DateTime.UtcNow; + + hunkTableSmall = new uint[hdrV5.logicalbytes / hdrV5.hunkbytes]; + + uint hunkSectorCount = (uint)Math.Ceiling(((double)hunkTableSmall.Length * 4) / 512); + + byte[] hunkSectorBytes = new byte[512]; + HunkSectorSmall hunkSector = new HunkSectorSmall(); + + stream.Seek((long)hdrV5.mapoffset, SeekOrigin.Begin); + + for(int i = 0; i < hunkSectorCount; i++) + { + stream.Read(hunkSectorBytes, 0, 512); + // This does the big-endian trick but reverses the order of elements also + Array.Reverse(hunkSectorBytes); + GCHandle handle = GCHandle.Alloc(hunkSectorBytes, GCHandleType.Pinned); + hunkSector = (HunkSectorSmall)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(HunkSectorSmall)); + handle.Free(); + // This restores the order of elements + Array.Reverse(hunkSector.hunkEntry); + if(hunkTableSmall.Length >= (i * 512) / 4 + 512 / 4) + Array.Copy(hunkSector.hunkEntry, 0, hunkTableSmall, (i * 512) / 4, 512 / 4); + else + Array.Copy(hunkSector.hunkEntry, 0, hunkTableSmall, (i * 512) / 4, hunkTableSmall.Length - (i * 512) / 4); + } + DateTime end = DateTime.UtcNow; + System.Console.WriteLine("Took {0} seconds", (end - start).TotalSeconds); + } + else + throw new ImageNotSupportedException("Cannot read compressed CHD version 5"); + + nextMetaOff = hdrV5.metaoffset; + + ImageInfo.imageSize = hdrV5.logicalbytes; + ImageInfo.imageVersion = "5"; + + totalHunks = (uint)(hdrV5.logicalbytes / hdrV5.hunkbytes); + bytesPerHunk = hdrV5.hunkbytes; + hdrCompression = hdrV5.compressor0; + hdrCompression1 = hdrV5.compressor1; + hdrCompression2 = hdrV5.compressor2; + hdrCompression3 = hdrV5.compressor3; + mapVersion = 5; + + break; + } + default: + throw new ImageNotSupportedException(string.Format("Unsupported CHD version {0}", version)); + } + + if(mapVersion >= 3) + { + byte[] meta; + isCdrom = false; + isHdd = false; + isGdrom = false; + swapAudio = false; + tracks = new Dictionary(); + + DicConsole.DebugWriteLine("CHD plugin", "Reading metadata."); + + ulong currentSector = 0; + uint currentTrack = 1; + + while(nextMetaOff > 0) + { + byte[] hdrBytes = new byte[16]; + stream.Seek((long)nextMetaOff, SeekOrigin.Begin); + stream.Read(hdrBytes, 0, hdrBytes.Length); + CHDMetadataHeader header = BigEndianMarshal.ByteArrayToStructureBigEndian(hdrBytes); + meta = new byte[header.flagsAndLength & 0xFFFFFF]; + stream.Read(meta, 0, meta.Length); + DicConsole.DebugWriteLine("CHD plugin", "Found metadata \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(header.tag))); + + switch(header.tag) + { + // "GDDD" + case hardDiskMetadata: + if(isCdrom || isGdrom) + throw new ImageNotSupportedException("Image cannot be a hard disk and a C/GD-ROM at the same time, aborting."); + + string gddd = StringHandlers.CToString(meta); + Regex gdddRegEx = new Regex(hardDiskMetadataRegEx); + Match gdddMatch = gdddRegEx.Match(gddd); + if(gdddMatch.Success) + { + isHdd = true; + ImageInfo.sectorSize = uint.Parse(gdddMatch.Groups["bps"].Value); + } + break; + // "CHCD" + case cdromOldMetadata: + if(isHdd) + throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); + + if(isGdrom) + throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); + + uint _tracks = BigEndianBitConverter.ToUInt32(meta, 0); + + // Byteswapped + if(_tracks > 99) + { + BigEndianBitConverter.IsLittleEndian = !BitConverter.IsLittleEndian; + _tracks = BigEndianBitConverter.ToUInt32(meta, 0); + } + + currentSector = 0; + + for(uint i = 0; i < _tracks; i++) + { + CHDTrackOld _trk = new CHDTrackOld(); + _trk.type = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 0)); + _trk.subType = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 4)); + _trk.dataSize = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 8)); + _trk.subSize = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 12)); + _trk.frames = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 16)); + _trk.extraFrames = BigEndianBitConverter.ToUInt32(meta, (int)(4 + i * 24 + 20)); + + Track _track = new Track(); + switch((CHDOldTrackType)_trk.type) + { + case CHDOldTrackType.Audio: + _track.TrackBytesPerSector = 2352; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.Audio; + break; + case CHDOldTrackType.Mode1: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode1; + break; + case CHDOldTrackType.Mode1_Raw: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode1; + break; + case CHDOldTrackType.Mode2: + case CHDOldTrackType.Mode2FormMix: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2336; + _track.TrackType = TrackType.CDMode2Formless; + break; + case CHDOldTrackType.Mode2Form1: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode2Form1; + break; + case CHDOldTrackType.Mode2Form2: + _track.TrackBytesPerSector = 2324; + _track.TrackRawBytesPerSector = 2324; + _track.TrackType = TrackType.CDMode2Form2; + break; + case CHDOldTrackType.Mode2Raw: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode2Formless; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported track type {0}", _trk.type)); + } + + switch((CHDOldSubType)_trk.subType) + { + case CHDOldSubType.Cooked: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + case CHDOldSubType.None: + _track.TrackSubchannelType = TrackSubchannelType.None; + break; + case CHDOldSubType.Raw: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported subchannel type {0}", _trk.type)); + } + + _track.Indexes = new Dictionary(); + _track.TrackDescription = string.Format("Track {0}", i + 1); + _track.TrackEndSector = currentSector + _trk.frames - 1; + _track.TrackFile = imageFilter.GetFilename(); + _track.TrackFileType = "BINARY"; + _track.TrackFilter = imageFilter; + _track.TrackStartSector = currentSector; + _track.TrackSequence = i + 1; + _track.TrackSession = 1; + currentSector += _trk.frames + _trk.extraFrames; + tracks.Add(_track.TrackSequence, _track); + } + + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + isCdrom = true; + + break; + // "CHTR" + case cdromTrackMetadata: + if(isHdd) + throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); + + if(isGdrom) + throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); + + string chtr = StringHandlers.CToString(meta); + Regex chtrRegEx = new Regex(CdromMetadataRegEx); + Match chtrMatch = chtrRegEx.Match(chtr); + if(chtrMatch.Success) + { + isCdrom = true; + + uint trackNo = uint.Parse(chtrMatch.Groups["track"].Value); + uint frames = uint.Parse(chtrMatch.Groups["frames"].Value); + string subtype = chtrMatch.Groups["sub_type"].Value; + string tracktype = chtrMatch.Groups["track_type"].Value; + + if(trackNo != currentTrack) + throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); + + Track _track = new Track(); + switch(tracktype) + { + case TrackTypeAudio: + _track.TrackBytesPerSector = 2352; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.Audio; + break; + case TrackTypeMode1: + case TrackTypeMode1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode1Raw: + case TrackTypeMode1Raw_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode2: + case TrackTypeMode2_2k: + case TrackTypeMode2FM: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2336; + _track.TrackType = TrackType.CDMode2Formless; + break; + case TrackTypeMode2F1: + case TrackTypeMode2F1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode2Form1; + break; + case TrackTypeMode2F2: + case TrackTypeMode2F2_2k: + _track.TrackBytesPerSector = 2324; + _track.TrackRawBytesPerSector = 2324; + _track.TrackType = TrackType.CDMode2Form2; + break; + case TrackTypeMode2Raw: + case TrackTypeMode2Raw_2k: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode2Formless; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported track type {0}", tracktype)); + } + + switch(subtype) + { + case SubTypeCooked: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + case SubTypeNone: + _track.TrackSubchannelType = TrackSubchannelType.None; + break; + case SubTypeRaw: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported subchannel type {0}", subtype)); + } + + _track.Indexes = new Dictionary(); + _track.TrackDescription = string.Format("Track {0}", trackNo); + _track.TrackEndSector = currentSector + frames - 1; + _track.TrackFile = imageFilter.GetFilename(); + _track.TrackFileType = "BINARY"; + _track.TrackFilter = imageFilter; + _track.TrackStartSector = currentSector; + _track.TrackSequence = trackNo; + _track.TrackSession = 1; + currentSector += frames; + currentTrack++; + tracks.Add(_track.TrackSequence, _track); + } + break; + // "CHT2" + case cdromTrackMetadata2: + if(isHdd) + throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); + + if(isGdrom) + throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); + + string cht2 = StringHandlers.CToString(meta); + Regex cht2RegEx = new Regex(CdromMetadata2RegEx); + Match cht2Match = cht2RegEx.Match(cht2); + if(cht2Match.Success) + { + isCdrom = true; + + uint trackNo = uint.Parse(cht2Match.Groups["track"].Value); + uint frames = uint.Parse(cht2Match.Groups["frames"].Value); + string subtype = cht2Match.Groups["sub_type"].Value; + string tracktype = cht2Match.Groups["track_type"].Value; + // TODO: Check pregap and postgap behaviour + uint pregap = uint.Parse(cht2Match.Groups["pregap"].Value); + string pregapType = cht2Match.Groups["pgtype"].Value; + string pregapSubType = cht2Match.Groups["pgsub"].Value; + uint postgap = uint.Parse(cht2Match.Groups["postgap"].Value); + + if(trackNo != currentTrack) + throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); + + Track _track = new Track(); + switch(tracktype) + { + case TrackTypeAudio: + _track.TrackBytesPerSector = 2352; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.Audio; + break; + case TrackTypeMode1: + case TrackTypeMode1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode1Raw: + case TrackTypeMode1Raw_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode2: + case TrackTypeMode2_2k: + case TrackTypeMode2FM: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2336; + _track.TrackType = TrackType.CDMode2Formless; + break; + case TrackTypeMode2F1: + case TrackTypeMode2F1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode2Form1; + break; + case TrackTypeMode2F2: + case TrackTypeMode2F2_2k: + _track.TrackBytesPerSector = 2324; + _track.TrackRawBytesPerSector = 2324; + _track.TrackType = TrackType.CDMode2Form2; + break; + case TrackTypeMode2Raw: + case TrackTypeMode2Raw_2k: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode2Formless; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported track type {0}", tracktype)); + } + + switch(subtype) + { + case SubTypeCooked: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + case SubTypeNone: + _track.TrackSubchannelType = TrackSubchannelType.None; + break; + case SubTypeRaw: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported subchannel type {0}", subtype)); + } + + _track.Indexes = new Dictionary(); + _track.TrackDescription = string.Format("Track {0}", trackNo); + _track.TrackEndSector = currentSector + frames - 1; + _track.TrackFile = imageFilter.GetFilename(); + _track.TrackFileType = "BINARY"; + _track.TrackFilter = imageFilter; + _track.TrackStartSector = currentSector; + _track.TrackSequence = trackNo; + _track.TrackSession = 1; + currentSector += frames; + currentTrack++; + tracks.Add(_track.TrackSequence, _track); + } + break; + // "CHGT" + case gdromOldMetadata: + swapAudio = true; + goto case gdromMetadata; + // "CHGD" + case gdromMetadata: + if(isHdd) + throw new ImageNotSupportedException("Image cannot be a hard disk and a GD-ROM at the same time, aborting."); + + if(isCdrom) + throw new ImageNotSupportedException("Image cannot be a CD-ROM and a GD-ROM at the same time, aborting."); + + string chgd = StringHandlers.CToString(meta); + Regex chgdRegEx = new Regex(GdromMetadataRegEx); + Match chgdMatch = chgdRegEx.Match(chgd); + if(chgdMatch.Success) + { + isGdrom = true; + + uint trackNo = uint.Parse(chgdMatch.Groups["track"].Value); + uint frames = uint.Parse(chgdMatch.Groups["frames"].Value); + string subtype = chgdMatch.Groups["sub_type"].Value; + string tracktype = chgdMatch.Groups["track_type"].Value; + // TODO: Check pregap, postgap and pad behaviour + uint pregap = uint.Parse(chgdMatch.Groups["pregap"].Value); + string pregapType = chgdMatch.Groups["pgtype"].Value; + string pregapSubType = chgdMatch.Groups["pgsub"].Value; + uint postgap = uint.Parse(chgdMatch.Groups["postgap"].Value); + uint pad = uint.Parse(chgdMatch.Groups["pad"].Value); + + if(trackNo != currentTrack) + throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); + + Track _track = new Track(); + switch(tracktype) + { + case TrackTypeAudio: + _track.TrackBytesPerSector = 2352; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.Audio; + break; + case TrackTypeMode1: + case TrackTypeMode1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode1Raw: + case TrackTypeMode1Raw_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode1; + break; + case TrackTypeMode2: + case TrackTypeMode2_2k: + case TrackTypeMode2FM: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2336; + _track.TrackType = TrackType.CDMode2Formless; + break; + case TrackTypeMode2F1: + case TrackTypeMode2F1_2k: + _track.TrackBytesPerSector = 2048; + _track.TrackRawBytesPerSector = 2048; + _track.TrackType = TrackType.CDMode2Form1; + break; + case TrackTypeMode2F2: + case TrackTypeMode2F2_2k: + _track.TrackBytesPerSector = 2324; + _track.TrackRawBytesPerSector = 2324; + _track.TrackType = TrackType.CDMode2Form2; + break; + case TrackTypeMode2Raw: + case TrackTypeMode2Raw_2k: + _track.TrackBytesPerSector = 2336; + _track.TrackRawBytesPerSector = 2352; + _track.TrackType = TrackType.CDMode2Formless; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported track type {0}", tracktype)); + } + + switch(subtype) + { + case SubTypeCooked: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + case SubTypeNone: + _track.TrackSubchannelType = TrackSubchannelType.None; + break; + case SubTypeRaw: + _track.TrackSubchannelFile = imageFilter.GetFilename(); + _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + _track.TrackSubchannelFilter = imageFilter; + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported subchannel type {0}", subtype)); + } + + _track.Indexes = new Dictionary(); + _track.TrackDescription = string.Format("Track {0}", trackNo); + _track.TrackEndSector = currentSector + frames - 1; + _track.TrackFile = imageFilter.GetFilename(); + _track.TrackFileType = "BINARY"; + _track.TrackFilter = imageFilter; + _track.TrackStartSector = currentSector; + _track.TrackSequence = trackNo; + _track.TrackSession = (ushort)(trackNo > 2 ? 2 : 1); + currentSector += frames; + currentTrack++; + tracks.Add(_track.TrackSequence, _track); + } + break; + // "IDNT" + case hardDiskIdentMetadata: + Decoders.ATA.Identify.IdentifyDevice? idnt = Decoders.ATA.Identify.Decode(meta); + if(idnt.HasValue) + { + ImageInfo.mediaManufacturer = idnt.Value.MediaManufacturer; + ImageInfo.mediaSerialNumber = idnt.Value.MediaSerial; + ImageInfo.driveModel = idnt.Value.Model; + ImageInfo.driveSerialNumber = idnt.Value.SerialNumber; + ImageInfo.driveFirmwareRevision = idnt.Value.FirmwareRevision; + } + identify = meta; + if(!ImageInfo.readableMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) + ImageInfo.readableMediaTags.Add(MediaTagType.ATA_IDENTIFY); + break; + } + + nextMetaOff = header.next; + } + + if(isHdd) + { + sectorsPerHunk = bytesPerHunk / ImageInfo.sectorSize; + ImageInfo.sectors = ImageInfo.imageSize / ImageInfo.sectorSize; + ImageInfo.mediaType = MediaType.GENERIC_HDD; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + } + else if(isCdrom) + { + // Hardcoded on MAME for CD-ROM + sectorsPerHunk = 8; + ImageInfo.mediaType = MediaType.CDROM; + ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; + + foreach(Track _trk in tracks.Values) + ImageInfo.sectors += (_trk.TrackEndSector - _trk.TrackStartSector + 1); + } + else if(isGdrom) + { + // Hardcoded on MAME for GD-ROM + sectorsPerHunk = 8; + ImageInfo.mediaType = MediaType.GDROM; + ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; + + foreach(Track _trk in tracks.Values) + ImageInfo.sectors += (_trk.TrackEndSector - _trk.TrackStartSector + 1); + } + else + throw new ImageNotSupportedException("Image does not represent a known media, aborting"); + } + + if(isCdrom || isGdrom) + { + offsetmap = new Dictionary(); + partitions = new List(); + ulong partPos = 0; + foreach(Track _track in tracks.Values) + { + Partition partition = new Partition(); + partition.PartitionDescription = _track.TrackDescription; + partition.PartitionLength = (_track.TrackEndSector - _track.TrackStartSector + 1) * (ulong)_track.TrackRawBytesPerSector; + partition.PartitionSectors = (_track.TrackEndSector - _track.TrackStartSector + 1); + partition.PartitionSequence = _track.TrackSequence; + partition.PartitionStart = partPos; + partition.PartitionStartSector = _track.TrackStartSector; + partition.PartitionType = _track.TrackType.ToString(); + partPos += partition.PartitionSectors; + offsetmap.Add(_track.TrackStartSector, _track.TrackSequence); + + if(_track.TrackSubchannelType != TrackSubchannelType.None) + { + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubchannel)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubchannel); + } + + switch(_track.TrackType) + { + case TrackType.CDMode1: + case TrackType.CDMode2Form1: + if(_track.TrackRawBytesPerSector == 2352) + { + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC_P)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC_P); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC_Q)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC_Q); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); + } + break; + case TrackType.CDMode2Form2: + if(_track.TrackRawBytesPerSector == 2352) + { + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); + } + break; + case TrackType.CDMode2Formless: + if(_track.TrackRawBytesPerSector == 2352) + { + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + } + break; + } + + if(_track.TrackBytesPerSector > ImageInfo.sectorSize) + ImageInfo.sectorSize = (uint)_track.TrackBytesPerSector; + + partitions.Add(partition); + } + + ImageInfo.imageHasPartitions = true; + ImageInfo.imageHasSessions = true; + } + + maxBlockCache = (int)(MaxCacheSize / (ImageInfo.sectorSize * sectorsPerHunk)); + maxSectorCache = (int)(MaxCacheSize / ImageInfo.sectorSize); + + imageStream = stream; + + sectorCache = new Dictionary(); + hunkCache = new Dictionary(); + + return true; + } + + Track GetTrack(ulong sector) + { + Track track = new Track(); + foreach(KeyValuePair kvp in offsetmap) + { + if(sector >= kvp.Key) + tracks.TryGetValue(kvp.Value, out track); + } + return track; + } + + ulong GetAbsoluteSector(ulong relativeSector, uint track) + { + Track _track = new Track(); + tracks.TryGetValue(track, out _track); + return _track.TrackStartSector + relativeSector; + } + + byte[] GetHunk(ulong hunkNo) + { + byte[] hunk; + + if(!hunkCache.TryGetValue(hunkNo, out hunk)) + { + switch(mapVersion) + { + case 1: + ulong offset = (hunkTable[hunkNo] & 0x00000FFFFFFFFFFF); + ulong length = hunkTable[hunkNo] >> 44; + + byte[] compHunk = new byte[length]; + imageStream.Seek((long)offset, SeekOrigin.Begin); + imageStream.Read(compHunk, 0, compHunk.Length); + + if(length == (sectorsPerHunk * ImageInfo.sectorSize)) + { + hunk = compHunk; + } + else if((CHDCompression)hdrCompression > CHDCompression.Zlib) + throw new ImageNotSupportedException(string.Format("Unsupported compression {0}", (CHDCompression)hdrCompression)); + else + { + DeflateStream zStream = new DeflateStream(new MemoryStream(compHunk), CompressionMode.Decompress); + hunk = new byte[sectorsPerHunk * ImageInfo.sectorSize]; + int read = zStream.Read(hunk, 0, (int)(sectorsPerHunk * ImageInfo.sectorSize)); + if(read != sectorsPerHunk * ImageInfo.sectorSize) + throw new IOException(string.Format("Unable to decompress hunk correctly, got {0} bytes, expected {1}", read, sectorsPerHunk * ImageInfo.sectorSize)); + zStream.Close(); + zStream = null; + } + break; + case 3: + byte[] entryBytes = new byte[16]; + Array.Copy(hunkMap, (int)(hunkNo * 16), entryBytes, 0, 16); + CHDMapV3Entry entry = BigEndianMarshal.ByteArrayToStructureBigEndian(entryBytes); + switch((CHDV3EntryFlags)(entry.flags & 0x0F)) + { + case CHDV3EntryFlags.Invalid: + throw new ArgumentException("Invalid hunk found."); + case CHDV3EntryFlags.Compressed: + switch((CHDCompression)hdrCompression) + { + case CHDCompression.None: + goto uncompressedV3; + case CHDCompression.Zlib: + case CHDCompression.ZlibPlus: + if(isHdd) + { + byte[] zHunk = new byte[(entry.lengthLsb << 16) + entry.lengthLsb]; + imageStream.Seek((long)entry.offset, SeekOrigin.Begin); + imageStream.Read(zHunk, 0, zHunk.Length); + DeflateStream zStream = new DeflateStream(new MemoryStream(zHunk), CompressionMode.Decompress); + hunk = new byte[bytesPerHunk]; + int read = zStream.Read(hunk, 0, (int)(bytesPerHunk)); + if(read != bytesPerHunk) + throw new IOException(string.Format("Unable to decompress hunk correctly, got {0} bytes, expected {1}", read, bytesPerHunk)); + zStream.Close(); + zStream = null; + } + // TODO: Guess wth is MAME doing with these hunks + else + throw new ImageNotSupportedException("Compressed CD/GD-ROM hunks are not yet supported"); + break; + case CHDCompression.AV: + throw new ImageNotSupportedException(string.Format("Unsupported compression {0}", (CHDCompression)hdrCompression)); + } + break; + case CHDV3EntryFlags.Uncompressed: + uncompressedV3: + hunk = new byte[bytesPerHunk]; + imageStream.Seek((long)entry.offset, SeekOrigin.Begin); + imageStream.Read(hunk, 0, hunk.Length); + break; + case CHDV3EntryFlags.Mini: + hunk = new byte[bytesPerHunk]; + byte[] mini = new byte[8]; + mini = BigEndianBitConverter.GetBytes(entry.offset); + for(int i = 0; i < bytesPerHunk; i++) + hunk[i] = mini[i % 8]; + break; + case CHDV3EntryFlags.SelfHunk: + return GetHunk(entry.offset); + case CHDV3EntryFlags.ParentHunk: + throw new ImageNotSupportedException("Parent images are not supported"); + case CHDV3EntryFlags.SecondCompressed: + throw new ImageNotSupportedException("FLAC is not supported"); + default: + throw new ImageNotSupportedException(string.Format("Hunk type {0} is not supported", entry.flags & 0xF)); + } + break; + case 5: + if(hdrCompression == 0) + { + hunk = new byte[bytesPerHunk]; + imageStream.Seek(hunkTableSmall[hunkNo] * bytesPerHunk, SeekOrigin.Begin); + imageStream.Read(hunk, 0, hunk.Length); + } + else + throw new ImageNotSupportedException("Compressed v5 hunks not yet supported"); + break; + default: + throw new ImageNotSupportedException(string.Format("Unsupported hunk map version {0}", mapVersion)); + } + + if(hunkCache.Count >= maxBlockCache) + hunkCache.Clear(); + + hunkCache.Add(hunkNo, hunk); + } + + return hunk; + } + + public override bool? VerifySector(ulong sectorAddress) + { + if(isHdd) + return null; + + byte[] buffer = ReadSectorLong(sectorAddress); + return Checksums.CDChecksums.CheckCDSector(buffer); + } + + public override bool? VerifySector(ulong sectorAddress, uint track) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return VerifySector(GetAbsoluteSector(sectorAddress, track)); + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + UnknownLBAs = new List(); + FailingLBAs = new List(); + if(isHdd) + return null; + + byte[] buffer = ReadSectorsLong(sectorAddress, length); + int bps = (int)(buffer.Length / length); + byte[] sector = new byte[bps]; + + for(int i = 0; i < length; i++) + { + Array.Copy(buffer, i * bps, sector, 0, bps); + bool? sectorStatus = Checksums.CDChecksums.CheckCDSector(sector); + + switch(sectorStatus) + { + case null: + UnknownLBAs.Add((ulong)i + sectorAddress); + break; + case false: + FailingLBAs.Add((ulong)i + sectorAddress); + break; + } + } + + if(UnknownLBAs.Count > 0) + return null; + if(FailingLBAs.Count > 0) + return false; + return true; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + UnknownLBAs = new List(); + FailingLBAs = new List(); + if(isHdd) + return null; + + byte[] buffer = ReadSectorsLong(sectorAddress, length, track); + int bps = (int)(buffer.Length / length); + byte[] sector = new byte[bps]; + + for(int i = 0; i < length; i++) + { + Array.Copy(buffer, i * bps, sector, 0, bps); + bool? sectorStatus = Checksums.CDChecksums.CheckCDSector(sector); + + switch(sectorStatus) + { + case null: + UnknownLBAs.Add((ulong)i + sectorAddress); + break; + case false: + FailingLBAs.Add((ulong)i + sectorAddress); + break; + } + } + + if(UnknownLBAs.Count > 0) + return null; + if(FailingLBAs.Count > 0) + return false; + return true; + } + + public override bool? VerifyMediaImage() + { + byte[] calculated; + if(mapVersion >= 3) + { + Checksums.SHA1Context sha1Ctx = new Checksums.SHA1Context(); + sha1Ctx.Init(); + for(uint i = 0; i < totalHunks; i++) + sha1Ctx.Update(GetHunk(i)); + calculated = sha1Ctx.Final(); + } + else + { + Checksums.MD5Context md5Ctx = new Checksums.MD5Context(); + md5Ctx.Init(); + for(uint i = 0; i < totalHunks; i++) + md5Ctx.Update(GetHunk(i)); + calculated = md5Ctx.Final(); + } + + return expectedChecksum.SequenceEqual(calculated); + } + + public override bool ImageHasPartitions() + { + return ImageInfo.imageHasPartitions; + } + + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + + 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; + Track track = new Track(); + + if(!sectorCache.TryGetValue(sectorAddress, out sector)) + { + uint sectorSize; + + if(isHdd) + sectorSize = ImageInfo.sectorSize; + else + { + track = GetTrack(sectorAddress); + sectorSize = (uint)track.TrackRawBytesPerSector; + } + + ulong hunkNo = sectorAddress / sectorsPerHunk; + ulong secOff = (sectorAddress * sectorSize) % (sectorsPerHunk * sectorSize); + + byte[] hunk = GetHunk(hunkNo); + + sector = new byte[ImageInfo.sectorSize]; + Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); + + if(sectorCache.Count >= maxSectorCache) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + } + + if(isHdd) + return sector; + + uint sector_offset; + uint sector_size; + + switch(track.TrackType) + { + case TrackType.CDMode1: + case TrackType.CDMode2Form1: + { + if(track.TrackRawBytesPerSector == 2352) + { + sector_offset = 16; + sector_size = 2048; + } + else + { + sector_offset = 0; + sector_size = 2048; + } + break; + } + case TrackType.CDMode2Form2: + { + if(track.TrackRawBytesPerSector == 2352) + { + sector_offset = 16; + sector_size = 2324; + } + else + { + sector_offset = 0; + sector_size = 2324; + } + break; + } + case TrackType.CDMode2Formless: + { + if(track.TrackRawBytesPerSector == 2352) + { + sector_offset = 16; + sector_size = 2336; + } + else + { + sector_offset = 0; + sector_size = 2336; + } + break; + } + case TrackType.Audio: + { + sector_offset = 0; + sector_size = 2352; + break; + } + default: + throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); + } + + byte[] buffer = new byte[sector_size]; + + if(track.TrackType == TrackType.Audio && swapAudio) + { + for(int i = 0; i < 2352; i += 2) + { + buffer[i + 1] = sector[i]; + buffer[i] = sector[i + 1]; + } + } + else + Array.Copy(sector, sector_offset, buffer, 0, sector_size); + + return buffer; + } + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + if(isHdd) + throw new FeatureNotPresentImageException("Hard disk images do not have sector tags"); + + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + byte[] sector; + Track track = new Track(); + + if(!sectorCache.TryGetValue(sectorAddress, out sector)) + { + uint sectorSize; + + track = GetTrack(sectorAddress); + sectorSize = (uint)track.TrackRawBytesPerSector; + + ulong hunkNo = sectorAddress / sectorsPerHunk; + ulong secOff = (sectorAddress * sectorSize) % (sectorsPerHunk * sectorSize); + + byte[] hunk = GetHunk(hunkNo); + + sector = new byte[ImageInfo.sectorSize]; + Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); + + if(sectorCache.Count >= maxSectorCache) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + } + + if(isHdd) + return sector; + + uint sector_offset; + uint sector_size; + + if(tag == SectorTagType.CDSectorSubchannel) + { + if(track.TrackSubchannelType == TrackSubchannelType.None) + throw new FeatureNotPresentImageException("Requested sector does not contain subchannel"); + else if(track.TrackSubchannelType == TrackSubchannelType.RawInterleaved) + { + sector_offset = (uint)track.TrackRawBytesPerSector; + sector_size = 96; + } + else + throw new FeatureSupportedButNotImplementedImageException(string.Format("Unsupported subchannel type {0}", track.TrackSubchannelType)); + } + else + { + switch(track.TrackType) + { + case TrackType.CDMode1: + case TrackType.CDMode2Form1: + { + if(track.TrackRawBytesPerSector == 2352) + { + switch(tag) + { + case SectorTagType.CDSectorSync: + { + sector_offset = 0; + sector_size = 12; + break; + } + case SectorTagType.CDSectorHeader: + { + sector_offset = 12; + sector_size = 4; + break; + } + case SectorTagType.CDSectorSubHeader: + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + case SectorTagType.CDSectorECC: + { + sector_offset = 2076; + sector_size = 276; + break; + } + case SectorTagType.CDSectorECC_P: + { + sector_offset = 2076; + sector_size = 172; + break; + } + case SectorTagType.CDSectorECC_Q: + { + sector_offset = 2248; + sector_size = 104; + break; + } + case SectorTagType.CDSectorEDC: + { + sector_offset = 2064; + sector_size = 4; + break; + } + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + } + else + throw new FeatureNotPresentImageException("Requested sector does not contain tags"); + break; + } + case TrackType.CDMode2Form2: + { + if(track.TrackRawBytesPerSector == 2352) + { + switch(tag) + { + case SectorTagType.CDSectorSync: + { + sector_offset = 0; + sector_size = 12; + break; + } + case SectorTagType.CDSectorHeader: + { + sector_offset = 12; + sector_size = 4; + break; + } + case SectorTagType.CDSectorSubHeader: + { + sector_offset = 16; + sector_size = 8; + break; + } + case SectorTagType.CDSectorEDC: + { + sector_offset = 2348; + sector_size = 4; + break; + } + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + } + else + { + switch(tag) + { + case SectorTagType.CDSectorSync: + case SectorTagType.CDSectorHeader: + case SectorTagType.CDSectorSubchannel: + case SectorTagType.CDSectorECC: + case SectorTagType.CDSectorECC_P: + case SectorTagType.CDSectorECC_Q: + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + case SectorTagType.CDSectorSubHeader: + { + sector_offset = 0; + sector_size = 8; + break; + } + case SectorTagType.CDSectorEDC: + { + sector_offset = 2332; + sector_size = 4; + break; + } + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + } + break; + } + case TrackType.CDMode2Formless: + { + if(track.TrackRawBytesPerSector == 2352) + { + switch(tag) + { + case SectorTagType.CDSectorSync: + case SectorTagType.CDSectorHeader: + case SectorTagType.CDSectorECC: + case SectorTagType.CDSectorECC_P: + case SectorTagType.CDSectorECC_Q: + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + case SectorTagType.CDSectorSubHeader: + { + sector_offset = 0; + sector_size = 8; + break; + } + case SectorTagType.CDSectorEDC: + { + sector_offset = 2332; + sector_size = 4; + break; + } + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + } + else + throw new FeatureNotPresentImageException("Requested sector does not contain tags"); + break; + } + case TrackType.Audio: + throw new FeatureNotPresentImageException("Requested sector does not contain tags"); + default: + throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); + } + } + + byte[] buffer = new byte[sector_size]; + + if(track.TrackType == TrackType.Audio && swapAudio) + { + for(int i = 0; i < 2352; i += 2) + { + buffer[i + 1] = sector[i]; + buffer[i] = sector[i + 1]; + } + } + else + Array.Copy(sector, sector_offset, buffer, 0, sector_size); + + if(track.TrackType == TrackType.Audio && swapAudio) + { + for(int i = 0; i < 2352; i += 2) + { + buffer[i + 1] = sector[i]; + buffer[i] = sector[i + 1]; + } + } + else + Array.Copy(sector, sector_offset, buffer, 0, sector_size); + + return buffer; + } + + 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), string.Format("Requested more sectors ({0}) than available ({1})", sectorAddress + length, ImageInfo.sectors)); + + 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 byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + 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), string.Format("Requested more sectors ({0}) than available ({1})", sectorAddress + length, ImageInfo.sectors)); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSectorTag(sectorAddress + i, tag); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + public override byte[] ReadSectorLong(ulong sectorAddress) + { + if(isHdd) + return ReadSector(sectorAddress); + + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + byte[] sector; + Track track = new Track(); + + if(!sectorCache.TryGetValue(sectorAddress, out sector)) + { + uint sectorSize; + + track = GetTrack(sectorAddress); + sectorSize = (uint)track.TrackRawBytesPerSector; + + ulong hunkNo = sectorAddress / sectorsPerHunk; + ulong secOff = (sectorAddress * sectorSize) % (sectorsPerHunk * sectorSize); + + byte[] hunk = GetHunk(hunkNo); + + sector = new byte[ImageInfo.sectorSize]; + Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); + + if(sectorCache.Count >= maxSectorCache) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + } + + byte[] buffer = new byte[track.TrackRawBytesPerSector]; + + if(track.TrackType == TrackType.Audio && swapAudio) + { + for(int i = 0; i < 2352; i += 2) + { + buffer[i + 1] = sector[i]; + buffer[i] = sector[i + 1]; + } + } + else + Array.Copy(sector, 0, buffer, 0, track.TrackRawBytesPerSector); + + return buffer; + } + + public override byte[] ReadSectorsLong(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), string.Format("Requested more sectors ({0}) than available ({1})", sectorAddress + length, ImageInfo.sectors)); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSectorLong(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + public override string GetImageFormat() + { + return "Compressed Hunks of Data"; + } + + public override string GetImageVersion() + { + return ImageInfo.imageVersion; + } + + public override string GetImageApplication() + { + return ImageInfo.imageApplication; + } + + public override string GetImageApplicationVersion() + { + return ImageInfo.imageApplicationVersion; + } + + 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 ImageInfo.mediaType; + } + + #region Unsupported features + + public override byte[] ReadDiskTag(MediaTagType tag) + { + if(ImageInfo.readableMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) + return identify; + + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override string GetImageCreator() + { + return ImageInfo.imageCreator; + } + + public override string GetImageComments() + { + return ImageInfo.imageComments; + } + + public override string GetMediaManufacturer() + { + return ImageInfo.mediaManufacturer; + } + + public override string GetMediaModel() + { + return ImageInfo.mediaModel; + } + + public override string GetMediaSerialNumber() + { + return ImageInfo.mediaSerialNumber; + } + + public override string GetMediaBarcode() + { + return ImageInfo.mediaBarcode; + } + + public override string GetMediaPartNumber() + { + return ImageInfo.mediaPartNumber; + } + + public override int GetMediaSequence() + { + return ImageInfo.mediaSequence; + } + + public override int GetLastDiskSequence() + { + return ImageInfo.lastMediaSequence; + } + + public override string GetDriveManufacturer() + { + return ImageInfo.driveManufacturer; + } + + public override string GetDriveModel() + { + return ImageInfo.driveModel; + } + + public override string GetDriveSerialNumber() + { + return ImageInfo.driveSerialNumber; + } + + public override List GetPartitions() + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return partitions; + } + + public override List GetTracks() + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + List _trks = new List(); + foreach(Track track in tracks.Values) + _trks.Add(track); + + return _trks; + } + + public override List GetSessionTracks(Session session) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return GetSessionTracks(session.SessionSequence); + } + + public override List GetSessionTracks(ushort session) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + List _trks = new List(); + foreach(Track track in tracks.Values) + { + if(track.TrackSession == session) + _trks.Add(track); + } + + return _trks; + } + + public override List GetSessions() + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical sessions on a hard disk image"); + + throw new NotImplementedException(); + } + + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSector(GetAbsoluteSector(sectorAddress, track)); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSectorTag(GetAbsoluteSector(sectorAddress, track), tag); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSectors(GetAbsoluteSector(sectorAddress, track), length); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSectorsTag(GetAbsoluteSector(sectorAddress, track), length, tag); + } + + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSectorLong(GetAbsoluteSector(sectorAddress, track)); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + if(isHdd) + throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); + + return ReadSectorLong(GetAbsoluteSector(sectorAddress, track), length); + } + + #endregion Unsupported features + } } diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index 50cef69b..3c414ce6 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -68,6 +68,7 @@ + diff --git a/README.md b/README.md index 832cb16e..408a489b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Supported disk image formats * Apple Universal Disk Image Format (UDIF), including obsolete (previous than DiskCopy 6) versions * Apple New Disk Image Format (NDIF, requires Resource Fork) * Apple Disk Archival/Retrieval Tool (DART) +* MAME Compressed Hunks of Data (CHD) Supported partitioning schemes ============================== diff --git a/TODO b/TODO index bd1d0f71..ad575f85 100644 --- a/TODO +++ b/TODO @@ -5,7 +5,6 @@ --- Add support for IMD images --- Add support for Kryoflux images --- Add support for DiscFerret images ---- Add support for MAME CHDs --- Add support for XPACK images Filesystem plugins: @@ -60,6 +59,11 @@ NDIF plugin: DART plugin: --- Add support for chunks compressed with RLE or LZH +CHD plugin: +--- Add support for compressed hunks on CD disc images +--- Add support for compressed version 5 disk images +--- Add support for PCMCIA images + Filters: --- Add support for XZ compressed files --- Add support for ZIP archives