// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : DiscImageChef.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages DiscImageChef format disk images. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2018 Natalia Portillo // ****************************************************************************/ /* The idea of the format is being able to easily store, retrieve, and access any data that can be read from media. At the start of a file there's a header that contains a format version, application creator name, and a pointer to the index. The index points to one or several DeDuplication Tables, or media tag blocks. A deduplication table is a table of offsets to blocks and sectors inside blocks. Each entry equals to an LBA and points to a byte offset in the file shift left to the number of sectors contained in a block, plus the number of sector inside the block. Each block must contain sectors of equal size, but that size can be different between blocks. The deduplication table should be stored decompressed if its size is too big to be stored on-memory. This is chosen at creation time but it is a good idea to set the limit to 256MiB (this allows for a device of 33 million sectors, 17Gb at 512 bps, 68Gb at 2048 bps and 137Gb at 4096 bps). Sector tags that are simply too small to be deduplicated are contained in a single block pointed by the index (e.g. Apple GCR sector tags). Optical disks contain a track block that describes the tracks. Streaming tapes contain a file block that describes the files and an optional partition block that describes the tape partitions. There are also blocks for image metadata, contents metadata and dump hardware information. A differencing image will have all the metadata and deduplication tables, but the entries in these ones will be set to 0 if the block is stored in the parent image. This is not yet implemented. Also because the file becomes useless without the index and deduplication table, each can be stored twice. In case of the index it should just be searched for. In case of deduplication tables, both copies should be indexed. Finally, writing new data to an existing image is just Copy-On-Write. Create a new block with the modified data, change the pointer in the corresponding deduplication table. P.S.: Data Position Measurement is doable, as soon as I know how to do it. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using DiscImageChef.Checksums; using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Filters; using SharpCompress.Compressors.LZMA; namespace DiscImageChef.DiscImages { // TODO: Work in progress public class DiscImageChef : IWritableImage { const ulong DIC_MAGIC = 0x544D464444434944; const byte DICF_VERSION = 0; const uint MAX_CACHE_SIZE = 256 * 1024 * 1024; const int LZMA_PROPERTIES_LENGTH = 5; const int MAX_DDT_ENTRY_CACHE = 16000000; Dictionary blockCache; Dictionary blockHeaderCache; MemoryStream blockStream; SHA256 checksumProvider; LzmaStream compressedBlockStream; Crc64Context crc64; BlockHeader currentBlockHeader; uint currentBlockOffset; uint currentCacheSize; Dictionary ddtEntryCache; Dictionary deduplicationTable; GeometryBlock geometryBlock; DicHeader header; ImageInfo imageInfo; Stream imageStream; List index; bool inMemoryDdt; Dictionary mediaTags; long outMemoryDdtPosition; byte shift; byte[] structureBytes; IntPtr structurePointer; ulong[] userDataDdt; public DiscImageChef() { imageInfo = new ImageInfo { ReadableSectorTags = new List(), ReadableMediaTags = new List(), HasPartitions = false, HasSessions = false, Version = null, Application = "DiscImageChef", ApplicationVersion = null, Creator = null, Comments = null, MediaManufacturer = null, MediaModel = null, MediaSerialNumber = null, MediaBarcode = null, MediaPartNumber = null, MediaSequence = 0, LastMediaSequence = 0, DriveManufacturer = null, DriveModel = null, DriveSerialNumber = null, DriveFirmwareRevision = null }; } public ImageInfo Info => imageInfo; public string Name => "DiscImageChef format"; public Guid Id => new Guid("49360069-1784-4A2F-B723-0C844D610B0A"); public string Format => "DiscImageChef"; public List Partitions { get; } public List Tracks { get; private set; } public List Sessions { get; } public bool Identify(IFilter imageFilter) { imageStream = imageFilter.GetDataForkStream(); imageStream.Seek(0, SeekOrigin.Begin); if(imageStream.Length < 512) return false; header = new DicHeader(); structureBytes = new byte[Marshal.SizeOf(header)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); Marshal.FreeHGlobal(structurePointer); return header.identifier == DIC_MAGIC && header.imageMajorVersion == 0; } public bool Open(IFilter imageFilter) { imageStream = imageFilter.GetDataForkStream(); imageStream.Seek(0, SeekOrigin.Begin); if(imageStream.Length < 512) return false; header = new DicHeader(); structureBytes = new byte[Marshal.SizeOf(header)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(header)); header = (DicHeader)Marshal.PtrToStructure(structurePointer, typeof(DicHeader)); Marshal.FreeHGlobal(structurePointer); if(header.imageMajorVersion > DICF_VERSION) throw new FeatureUnsupportedImageException($"Image version {header.imageMajorVersion} not recognized."); imageInfo.Application = header.application; imageInfo.ApplicationVersion = $"{header.applicationMajorVersion}.{header.applicationMinorVersion}"; imageInfo.Version = $"{header.imageMajorVersion}.{header.imageMinorVersion}"; imageInfo.MediaType = header.mediaType; imageStream.Position = (long)header.indexOffset; IndexHeader idxHeader = new IndexHeader(); structureBytes = new byte[Marshal.SizeOf(idxHeader)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(idxHeader)); idxHeader = (IndexHeader)Marshal.PtrToStructure(structurePointer, typeof(IndexHeader)); Marshal.FreeHGlobal(structurePointer); if(idxHeader.identifier != BlockType.Index) throw new FeatureUnsupportedImageException("Index not found!"); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", header.indexOffset, idxHeader.entries); index = new List(); for(ushort i = 0; i < idxHeader.entries; i++) { IndexEntry entry = new IndexEntry(); structureBytes = new byte[Marshal.SizeOf(entry)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(entry)); entry = (IndexEntry)Marshal.PtrToStructure(structurePointer, typeof(IndexEntry)); Marshal.FreeHGlobal(structurePointer); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Block type {0} with data type {1} is indexed to be at {2}", entry.blockType, entry.dataType, entry.offset); index.Add(entry); } imageInfo.ImageSize = 0; bool foundUserDataDdt = false; mediaTags = new Dictionary(); foreach(IndexEntry entry in index) { imageStream.Position = (long)entry.offset; switch(entry.blockType) { // TODO: Non-deduplicatable sector tags are data blocks case BlockType.DataBlock: // NOP block, skip if(entry.dataType == DataType.NoData || // Unused, skip entry.dataType == DataType.UserData) break; imageStream.Position = (long)entry.offset; BlockHeader blockHeader = new BlockHeader(); structureBytes = new byte[Marshal.SizeOf(blockHeader)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); Marshal.FreeHGlobal(structurePointer); imageInfo.ImageSize += blockHeader.cmpLength; if(blockHeader.identifier != entry.blockType) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect identifier for data block at position {0}", entry.offset); break; } if(blockHeader.type != entry.dataType) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Expected block with data type {0} at position {1} but found data type {2}", entry.dataType, entry.offset, blockHeader.type); break; } byte[] data; MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found media tag {0} at position {1}", mediaTagType, entry.offset); if(blockHeader.compression == CompressionType.Lzma) { byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); imageStream.Read(compressedTag, 0, compressedTag.Length); MemoryStream compressedTagMs = new MemoryStream(compressedTag); LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); data = new byte[blockHeader.length]; lzmaBlock.Read(data, 0, (int)blockHeader.length); lzmaBlock.Close(); compressedTagMs.Close(); compressedTag = null; } else if(blockHeader.compression == CompressionType.None) { data = new byte[blockHeader.length]; imageStream.Read(data, 0, (int)blockHeader.length); } else { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found unknown compression type {0}, continuing...", (ushort)blockHeader.compression); break; } Crc64Context.Data(data, out byte[] blockCrc); if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); break; } if(mediaTags.ContainsKey(mediaTagType)) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Media tag type {0} duplicated, removing previous entry...", mediaTagType); mediaTags.Remove(mediaTagType); } mediaTags.Add(mediaTagType, data); break; case BlockType.DeDuplicationTable: // Only user data deduplication tables are used right now if(entry.dataType != DataType.UserData) break; DdtHeader ddtHeader = new DdtHeader(); structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(ddtHeader)); ddtHeader = (DdtHeader)Marshal.PtrToStructure(structurePointer, typeof(DdtHeader)); Marshal.FreeHGlobal(structurePointer); imageInfo.ImageSize += ddtHeader.cmpLength; if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; // TODO: Check CRC64 imageInfo.Sectors = ddtHeader.entries; shift = ddtHeader.shift; switch(ddtHeader.compression) { case CompressionType.Lzma: DicConsole.DebugWriteLine("DiscImageChef format plugin", "Decompressing DDT..."); DateTime ddtStart = DateTime.UtcNow; byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); imageStream.Read(compressedDdt, 0, compressedDdt.Length); MemoryStream compressedDdtMs = new MemoryStream(compressedDdt); LzmaStream lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); byte[] decompressedDdt = new byte[ddtHeader.length]; lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); lzmaDdt.Close(); compressedDdtMs.Close(); compressedDdt = null; userDataDdt = new ulong[ddtHeader.entries]; for(ulong i = 0; i < ddtHeader.entries; i++) userDataDdt[i] = BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); decompressedDdt = null; DateTime ddtEnd = DateTime.UtcNow; inMemoryDdt = true; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to decompress DDT", (ddtEnd - ddtStart).TotalSeconds); break; case CompressionType.None: inMemoryDdt = false; outMemoryDdtPosition = (long)entry.offset; break; default: throw new ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); } foundUserDataDdt = true; break; case BlockType.GeometryBlock: geometryBlock = new GeometryBlock(); structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(geometryBlock)); geometryBlock = (GeometryBlock)Marshal.PtrToStructure(structurePointer, typeof(GeometryBlock)); Marshal.FreeHGlobal(structurePointer); if(geometryBlock.identifier == BlockType.GeometryBlock) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Geometry set to {0} cylinders {1} heads {2} sectors per track", geometryBlock.cylinders, geometryBlock.heads, geometryBlock.sectorsPerTrack); imageInfo.Cylinders = geometryBlock.cylinders; imageInfo.Heads = geometryBlock.heads; imageInfo.SectorsPerTrack = geometryBlock.sectorsPerTrack; } break; case BlockType.MetadataBlock: MetadataBlock metadataBlock = new MetadataBlock(); structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(metadataBlock)); metadataBlock = (MetadataBlock)Marshal.PtrToStructure(structurePointer, typeof(MetadataBlock)); Marshal.FreeHGlobal(structurePointer); if(metadataBlock.identifier != entry.blockType) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect identifier for data block at position {0}", entry.offset); break; } DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found metadata block at position {0}", entry.offset); byte[] metadata = new byte[metadataBlock.blockSize]; imageStream.Position = (long)entry.offset; imageStream.Read(metadata, 0, metadata.Length); if(metadataBlock.mediaSequence > 0 && metadataBlock.lastMediaSequence > 0) { imageInfo.MediaSequence = metadataBlock.mediaSequence; imageInfo.LastMediaSequence = metadataBlock.lastMediaSequence; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media sequence as {0} of {1}", imageInfo.MediaSequence, imageInfo.LastMediaSequence); } if(metadataBlock.creatorLength > 0 && metadataBlock.creatorLength + metadataBlock.creatorOffset <= metadata.Length) { imageInfo.Creator = Encoding.Unicode.GetString(metadata, (int)metadataBlock.creatorOffset, (int)(metadataBlock.creatorLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting creator: {0}", imageInfo.Creator); } if(metadataBlock.commentsOffset > 0 && metadataBlock.commentsLength + metadataBlock.commentsOffset <= metadata.Length) { imageInfo.Comments = Encoding.Unicode.GetString(metadata, (int)metadataBlock.commentsOffset, (int)(metadataBlock.commentsLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting comments: {0}", imageInfo.Comments); } if(metadataBlock.mediaTitleOffset > 0 && metadataBlock.mediaTitleLength + metadataBlock.mediaTitleOffset <= metadata.Length) { imageInfo.MediaTitle = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaTitleOffset, (int)(metadataBlock.mediaTitleLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media title: {0}", imageInfo.MediaTitle); } if(metadataBlock.mediaManufacturerOffset > 0 && metadataBlock.mediaManufacturerLength + metadataBlock.mediaManufacturerOffset <= metadata.Length) { imageInfo.MediaManufacturer = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaManufacturerOffset, (int)(metadataBlock.mediaManufacturerLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media manufacturer: {0}", imageInfo.MediaManufacturer); } if(metadataBlock.mediaModelOffset > 0 && metadataBlock.mediaModelLength + metadataBlock.mediaModelOffset <= metadata.Length) { imageInfo.MediaModel = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaModelOffset, (int)(metadataBlock.mediaModelLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media model: {0}", imageInfo.MediaModel); } if(metadataBlock.mediaSerialNumberOffset > 0 && metadataBlock.mediaSerialNumberLength + metadataBlock.mediaSerialNumberOffset <= metadata.Length) { imageInfo.MediaSerialNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaSerialNumberOffset, (int)(metadataBlock.mediaSerialNumberLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media serial number: {0}", imageInfo.MediaSerialNumber); } if(metadataBlock.mediaBarcodeOffset > 0 && metadataBlock.mediaBarcodeLength + metadataBlock.mediaBarcodeOffset <= metadata.Length) { imageInfo.MediaBarcode = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaBarcodeOffset, (int)(metadataBlock.mediaBarcodeLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media barcode: {0}", imageInfo.MediaBarcode); } if(metadataBlock.mediaPartNumberOffset > 0 && metadataBlock.mediaPartNumberLength + metadataBlock.mediaPartNumberOffset <= metadata.Length) { imageInfo.MediaPartNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaPartNumberOffset, (int)(metadataBlock.mediaPartNumberLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting media part number: {0}", imageInfo.MediaPartNumber); } if(metadataBlock.driveManufacturerOffset > 0 && metadataBlock.driveManufacturerLength + metadataBlock.driveManufacturerOffset <= metadata.Length) { imageInfo.DriveManufacturer = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveManufacturerOffset, (int)(metadataBlock.driveManufacturerLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive manufacturer: {0}", imageInfo.DriveManufacturer); } if(metadataBlock.driveModelOffset > 0 && metadataBlock.driveModelLength + metadataBlock.driveModelOffset <= metadata.Length) { imageInfo.DriveModel = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveModelOffset, (int)(metadataBlock.driveModelLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive model: {0}", imageInfo.DriveModel); } if(metadataBlock.driveSerialNumberOffset > 0 && metadataBlock.driveSerialNumberLength + metadataBlock.driveSerialNumberOffset <= metadata.Length) { imageInfo.DriveSerialNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveSerialNumberOffset, (int)(metadataBlock.driveSerialNumberLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive serial number: {0}", imageInfo.DriveSerialNumber); } if(metadataBlock.driveFirmwareRevisionOffset > 0 && metadataBlock.driveFirmwareRevisionLength + metadataBlock.driveFirmwareRevisionOffset <= metadata.Length) { imageInfo.DriveFirmwareRevision = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveFirmwareRevisionOffset, (int)(metadataBlock.driveFirmwareRevisionLength - 2)); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Setting drive firmware revision: {0}", imageInfo.DriveFirmwareRevision); } break; } } if(!foundUserDataDdt) throw new ImageNotSupportedException("Could not find user data deduplication table."); // TODO: Sector size! imageInfo.SectorSize = 512; imageInfo.CreationTime = DateTime.FromFileTimeUtc(header.creationTime); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image created on", imageInfo.CreationTime); imageInfo.LastModificationTime = DateTime.FromFileTimeUtc(header.lastWrittenTime); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Image last written on", imageInfo.LastModificationTime); // TODO: Calculate //imageInfo.ImageSize = qHdr.size; if(geometryBlock.identifier != BlockType.GeometryBlock && imageInfo.XmlMediaType == XmlMediaType.BlockMedia) { imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); imageInfo.Heads = 16; imageInfo.SectorsPerTrack = 63; } imageInfo.XmlMediaType = GetXmlMediaType(header.mediaType); imageInfo.ReadableMediaTags.AddRange(mediaTags.Keys); // Initialize caches blockCache = new Dictionary(); blockHeaderCache = new Dictionary(); currentCacheSize = 0; if(!inMemoryDdt) ddtEntryCache = new Dictionary(); return true; } public byte[] ReadDiskTag(MediaTagType tag) { if(mediaTags.TryGetValue(tag, out byte[] data)) return data; throw new FeatureNotPresentImageException("Requested tag is not present in image"); } public byte[] ReadSector(ulong sectorAddress) { if(sectorAddress > imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); ulong ddtEntry = GetDdtEntry(sectorAddress); uint offsetMask = (uint)((1 << shift) - 1); ulong offset = ddtEntry & offsetMask; ulong blockOffset = ddtEntry >> shift; // Partially written image... as we can't know the real sector size just assume it's common :/ if(ddtEntry == 0) return new byte[imageInfo.SectorSize]; byte[] sector; if(blockCache.TryGetValue(blockOffset, out byte[] block) && blockHeaderCache.TryGetValue(blockOffset, out BlockHeader blockHeader)) { sector = new byte[blockHeader.sectorSize]; Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); return sector; } imageStream.Position = (long)blockOffset; blockHeader = new BlockHeader(); structureBytes = new byte[Marshal.SizeOf(blockHeader)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(blockHeader)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(blockHeader)); blockHeader = (BlockHeader)Marshal.PtrToStructure(structurePointer, typeof(BlockHeader)); Marshal.FreeHGlobal(structurePointer); switch(blockHeader.compression) { case CompressionType.None: block = new byte[blockHeader.length]; imageStream.Read(block, 0, (int)blockHeader.length); break; case CompressionType.Lzma: byte[] compressedBlock = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; imageStream.Read(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); imageStream.Read(compressedBlock, 0, compressedBlock.Length); MemoryStream compressedBlockMs = new MemoryStream(compressedBlock); LzmaStream lzmaBlock = new LzmaStream(lzmaProperties, compressedBlockMs); block = new byte[blockHeader.length]; lzmaBlock.Read(block, 0, (int)blockHeader.length); lzmaBlock.Close(); compressedBlockMs.Close(); compressedBlock = null; break; default: throw new ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)blockHeader.compression}"); } if(currentCacheSize + blockHeader.length >= MAX_CACHE_SIZE) { currentCacheSize = 0; blockHeaderCache = new Dictionary(); blockCache = new Dictionary(); } currentCacheSize += blockHeader.length; blockHeaderCache.Add(blockOffset, blockHeader); blockCache.Add(blockOffset, block); sector = new byte[blockHeader.sectorSize]; Array.Copy(block, (long)(offset * blockHeader.sectorSize), sector, 0, blockHeader.sectorSize); return sector; } public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) { throw new NotImplementedException(); } public byte[] ReadSector(ulong sectorAddress, uint track) { throw new NotImplementedException(); } public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) { throw new NotImplementedException(); } public byte[] ReadSectors(ulong sectorAddress, uint length) { if(sectorAddress > imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); 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 byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) { throw new NotImplementedException(); } public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) { throw new NotImplementedException(); } public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) { throw new NotImplementedException(); } public byte[] ReadSectorLong(ulong sectorAddress) { throw new NotImplementedException(); } public byte[] ReadSectorLong(ulong sectorAddress, uint track) { throw new NotImplementedException(); } public byte[] ReadSectorsLong(ulong sectorAddress, uint length) { throw new NotImplementedException(); } public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) { throw new NotImplementedException(); } public List GetSessionTracks(Session session) { return Tracks.Where(t => t.TrackSequence == session.SessionSequence).ToList(); } public List GetSessionTracks(ushort session) { return Tracks.Where(t => t.TrackSequence == session).ToList(); } public bool? VerifySector(ulong sectorAddress) { throw new NotImplementedException(); } public bool? VerifySector(ulong sectorAddress, uint track) { throw new NotImplementedException(); } public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, out List unknownLbas) { throw new NotImplementedException(); } public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, out List unknownLbas) { throw new NotImplementedException(); } public bool? VerifyMediaImage() { throw new NotImplementedException(); } public IEnumerable SupportedMediaTags => Enum.GetValues(typeof(MediaType)).Cast(); public IEnumerable SupportedSectorTags => Enum.GetValues(typeof(MediaType)).Cast(); public IEnumerable SupportedMediaTypes => Enum.GetValues(typeof(MediaType)).Cast(); public IEnumerable<(string name, Type type, string description)> SupportedOptions => new[] { ("sectors_per_block", typeof(uint), "How many sectors to store per block (will be rounded to next power of two)") }; public IEnumerable KnownExtensions => new[] {".dicf"}; public bool IsWriting { get; private set; } public string ErrorMessage { get; private set; } // TODO: Support resume public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, uint sectorSize) { uint sectorsPerBlock; if(options != null) if(options.TryGetValue("sectors_per_block", out string tmpValue)) { if(!uint.TryParse(tmpValue, out sectorsPerBlock)) { ErrorMessage = "Invalid value for sectors_per_block option"; return false; } } else sectorsPerBlock = 4096; else sectorsPerBlock = 4096; if(!SupportedMediaTypes.Contains(mediaType)) { ErrorMessage = $"Unsupport media format {mediaType}"; return false; } shift = 0; uint oldSectorsPerBlock = sectorsPerBlock; while(sectorsPerBlock > 1) { sectorsPerBlock >>= 1; shift++; } DicConsole.DebugWriteLine("DiscImageChef format plugin", "Got a shift of {0} for {1} sectors per block", shift, oldSectorsPerBlock); imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; try { imageStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } catch(IOException e) { ErrorMessage = $"Could not create new image file, exception {e.Message}"; return false; } index = new List(); // TODO: Set correct version header = new DicHeader { identifier = DIC_MAGIC, application = "DiscImageChef", imageMajorVersion = DICF_VERSION, imageMinorVersion = 0, applicationMajorVersion = 4, applicationMinorVersion = 0, mediaType = mediaType, creationTime = DateTime.UtcNow.ToFileTimeUtc() }; // TODO: Settable inMemoryDdt = sectors <= 256 * 1024 * 1024 / sizeof(ulong); DicConsole.DebugWriteLine("DiscImageChef format plugin", "In memory DDT?: {0}", inMemoryDdt); imageStream.Write(new byte[Marshal.SizeOf(typeof(DicHeader))], 0, Marshal.SizeOf(typeof(DicHeader))); if(inMemoryDdt) userDataDdt = new ulong[sectors]; else { outMemoryDdtPosition = imageStream.Position; index.Add(new IndexEntry { blockType = BlockType.DeDuplicationTable, dataType = DataType.UserData, offset = (ulong)outMemoryDdtPosition }); // CRC64 will be calculated later DdtHeader ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.UserData, compression = CompressionType.None, shift = shift, entries = sectors, cmpLength = sectors * sizeof(ulong), length = sectors * sizeof(ulong) }; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; Marshal.StructureToPtr(ddtHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); structureBytes = null; // TODO: Can be changed to a seek? imageStream.Write(new byte[sectors * sizeof(ulong)], 0, (int)(sectors * sizeof(ulong))); } mediaTags = new Dictionary(); checksumProvider = SHA256.Create(); deduplicationTable = new Dictionary(); IsWriting = true; ErrorMessage = null; return true; } public bool WriteMediaTag(byte[] data, MediaTagType tag) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); mediaTags.Add(tag, data); ErrorMessage = ""; return true; } // TODO: Resume public bool WriteSector(byte[] data, ulong sectorAddress) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(sectorAddress >= Info.Sectors) { ErrorMessage = "Tried to write past image size"; return false; } byte[] hash = checksumProvider.ComputeHash(data); if(deduplicationTable.TryGetValue(hash, out ulong pointer)) { SetDdtEntry(sectorAddress, pointer); ErrorMessage = ""; return true; } // Close current block first if(blockStream != null && (currentBlockHeader.sectorSize != data.Length || currentBlockOffset == 1 << shift)) { currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); byte[] lzmaProperties = compressedBlockStream.Properties; compressedBlockStream.Close(); currentBlockHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; Crc64Context.Data(blockStream.ToArray(), out byte[] cmpCrc64); currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64, 0); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); structureBytes = null; imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream = null; currentBlockOffset = 0; } // No block set if(blockStream == null) { blockStream = new MemoryStream(); compressedBlockStream = new LzmaStream(new LzmaEncoderProperties(), false, blockStream); currentBlockHeader = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.UserData, compression = CompressionType.Lzma, sectorSize = (uint)data.Length }; crc64 = new Crc64Context(); crc64.Init(); } ulong ddtEntry = (ulong)((imageStream.Position << shift) + currentBlockOffset); deduplicationTable.Add(hash, ddtEntry); compressedBlockStream.Write(data, 0, data.Length); SetDdtEntry(sectorAddress, ddtEntry); crc64.Update(data); currentBlockOffset++; ErrorMessage = ""; return true; } // TODO: Optimize this public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(sectorAddress + length > Info.Sectors) { ErrorMessage = "Tried to write past image size"; return false; } uint sectorSize = (uint)(data.Length / length); for(uint i = 0; i < length; i++) { byte[] tmp = new byte[sectorSize]; Array.Copy(data, i * sectorSize, tmp, 0, sectorSize); if(!WriteSector(tmp, sectorAddress + i)) return false; } ErrorMessage = ""; return true; } // TODO: Implement public bool WriteSectorLong(byte[] data, ulong sectorAddress) { ErrorMessage = "Writing sectors with tags is not yet implemented."; return false; } // TODO: Implement public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) { ErrorMessage = "Writing sectors with tags is not yet implemented."; return false; } public bool SetTracks(List tracks) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } Tracks = tracks; ErrorMessage = ""; return true; } public bool Close() { if(!IsWriting) { ErrorMessage = "Image is not opened for writing"; return false; } // Close current block first if(blockStream != null) { currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); byte[] lzmaProperties = compressedBlockStream.Properties; compressedBlockStream.Close(); currentBlockHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; Crc64Context.Data(blockStream.ToArray(), out byte[] cmpCrc64); currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64, 0); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); structureBytes = null; imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream = null; } IndexEntry idxEntry; foreach(KeyValuePair mediaTag in mediaTags) { DataType dataType = GetDataTypeForMediaTag(mediaTag.Key); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = dataType, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tag type {0} to position {1}", mediaTag.Key, idxEntry.offset); Crc64Context.Data(mediaTag.Value, out byte[] tagCrc); BlockHeader tagBlock = new BlockHeader { identifier = BlockType.DataBlock, type = dataType, length = (uint)mediaTag.Value.Length, crc64 = BitConverter.ToUInt64(tagCrc, 0) }; blockStream = new MemoryStream(); compressedBlockStream = new LzmaStream(new LzmaEncoderProperties(), false, blockStream); compressedBlockStream.Write(mediaTag.Value, 0, mediaTag.Value.Length); byte[] lzmaProperties = compressedBlockStream.Properties; compressedBlockStream.Close(); byte[] tagData; // Not compressible if(blockStream.Length + LZMA_PROPERTIES_LENGTH >= mediaTag.Value.Length) { tagBlock.cmpLength = tagBlock.length; tagBlock.cmpCrc64 = tagBlock.crc64; tagData = mediaTag.Value; tagBlock.compression = CompressionType.None; } else { tagData = blockStream.ToArray(); Crc64Context.Data(tagData, out tagCrc); tagBlock.cmpLength = (uint)tagData.Length + LZMA_PROPERTIES_LENGTH; tagBlock.cmpCrc64 = BitConverter.ToUInt64(tagCrc, 0); tagBlock.compression = CompressionType.Lzma; } compressedBlockStream = null; blockStream = null; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tagBlock)); structureBytes = new byte[Marshal.SizeOf(tagBlock)]; Marshal.StructureToPtr(tagBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(tagBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(tagData, 0, tagData.Length); index.Add(idxEntry); } if(geometryBlock.identifier == BlockType.GeometryBlock) { idxEntry = new IndexEntry { blockType = BlockType.GeometryBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing geometry block to position {0}", idxEntry.offset); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(geometryBlock)); structureBytes = new byte[Marshal.SizeOf(geometryBlock)]; Marshal.StructureToPtr(geometryBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); index.Add(idxEntry); } if(inMemoryDdt) { idxEntry = new IndexEntry { blockType = BlockType.DeDuplicationTable, dataType = DataType.UserData, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing user data DDT to position {0}", idxEntry.offset); DdtHeader ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.UserData, compression = CompressionType.Lzma, shift = shift, entries = (ulong)userDataDdt.LongLength, length = (ulong)(userDataDdt.LongLength * sizeof(ulong)) }; blockStream = new MemoryStream(); compressedBlockStream = new LzmaStream(new LzmaEncoderProperties(), false, blockStream); crc64 = new Crc64Context(); crc64.Init(); for(ulong i = 0; i < (ulong)userDataDdt.LongLength; i++) { byte[] ddtEntry = BitConverter.GetBytes(userDataDdt[i]); crc64.Update(ddtEntry); compressedBlockStream.Write(ddtEntry, 0, ddtEntry.Length); } byte[] lzmaProperties = compressedBlockStream.Properties; compressedBlockStream.Close(); ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; Crc64Context.Data(blockStream.ToArray(), out byte[] cmpCrc64); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64, 0); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(ddtHeader)); structureBytes = new byte[Marshal.SizeOf(ddtHeader)]; Marshal.StructureToPtr(ddtHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); structureBytes = null; imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream = null; compressedBlockStream = null; index.Add(idxEntry); } MetadataBlock metadataBlock = new MetadataBlock(); blockStream = new MemoryStream(); blockStream.Write(new byte[Marshal.SizeOf(metadataBlock)], 0, Marshal.SizeOf(metadataBlock)); byte[] tmpUtf16Le; if(imageInfo.MediaSequence > 0 && imageInfo.LastMediaSequence > 0) { metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaSequence = imageInfo.MediaSequence; metadataBlock.lastMediaSequence = imageInfo.LastMediaSequence; } if(!string.IsNullOrWhiteSpace(imageInfo.Creator)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Creator); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.creatorOffset = (uint)blockStream.Position; metadataBlock.creatorLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.Comments)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.Comments); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.commentsOffset = (uint)blockStream.Position; metadataBlock.commentsLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaTitle)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaTitle); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaTitleOffset = (uint)blockStream.Position; metadataBlock.mediaTitleLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaManufacturer)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaManufacturer); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaManufacturerOffset = (uint)blockStream.Position; metadataBlock.mediaManufacturerLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaModel)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaModel); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaModelOffset = (uint)blockStream.Position; metadataBlock.mediaModelLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaSerialNumber)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaSerialNumber); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaSerialNumberOffset = (uint)blockStream.Position; metadataBlock.mediaSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaBarcode)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaBarcode); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaBarcodeOffset = (uint)blockStream.Position; metadataBlock.mediaBarcodeLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.MediaPartNumber)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.MediaPartNumber); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.mediaPartNumberOffset = (uint)blockStream.Position; metadataBlock.mediaPartNumberLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.DriveManufacturer)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveManufacturer); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.driveManufacturerOffset = (uint)blockStream.Position; metadataBlock.driveManufacturerLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.DriveModel)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveModel); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.driveModelOffset = (uint)blockStream.Position; metadataBlock.driveModelLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.DriveSerialNumber)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveSerialNumber); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.driveSerialNumberOffset = (uint)blockStream.Position; metadataBlock.driveSerialNumberLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(!string.IsNullOrWhiteSpace(imageInfo.DriveFirmwareRevision)) { tmpUtf16Le = Encoding.Unicode.GetBytes(imageInfo.DriveFirmwareRevision); metadataBlock.identifier = BlockType.MetadataBlock; metadataBlock.driveFirmwareRevisionOffset = (uint)blockStream.Position; metadataBlock.driveFirmwareRevisionLength = (uint)(tmpUtf16Le.Length + 2); blockStream.Write(tmpUtf16Le, 0, tmpUtf16Le.Length); blockStream.Write(new byte[] {0, 0}, 0, 2); } if(metadataBlock.identifier == BlockType.MetadataBlock) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing metadata to position {0}", imageStream.Position); metadataBlock.blockSize = (uint)blockStream.Length; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(metadataBlock)); structureBytes = new byte[Marshal.SizeOf(metadataBlock)]; Marshal.StructureToPtr(metadataBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); blockStream.Position = 0; blockStream.Write(structureBytes, 0, structureBytes.Length); index.Add(new IndexEntry { blockType = BlockType.MetadataBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); } header.indexOffset = (ulong)imageStream.Position; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing index to position {0}", header.indexOffset); blockStream = new MemoryStream(); foreach(IndexEntry entry in index) { structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); structureBytes = new byte[Marshal.SizeOf(entry)]; Marshal.StructureToPtr(entry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); blockStream.Write(structureBytes, 0, structureBytes.Length); } Crc64Context.Data(blockStream.ToArray(), out byte[] idxCrc); IndexHeader idxHeader = new IndexHeader { identifier = BlockType.Index, entries = (ushort)index.Count, crc64 = BitConverter.ToUInt64(idxCrc, 0) }; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(idxHeader)); structureBytes = new byte[Marshal.SizeOf(idxHeader)]; Marshal.StructureToPtr(idxHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing header"); header.lastWrittenTime = DateTime.UtcNow.ToFileTimeUtc(); imageStream.Position = 0; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(header)); structureBytes = new byte[Marshal.SizeOf(header)]; Marshal.StructureToPtr(header, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); imageStream.Flush(); imageStream.Close(); IsWriting = false; ErrorMessage = ""; return true; } // TODO: Implement public bool SetMetadata(ImageInfo metadata) { imageInfo.Creator = metadata.Creator; imageInfo.Comments = metadata.Comments; imageInfo.MediaManufacturer = metadata.MediaManufacturer; imageInfo.MediaModel = metadata.MediaModel; imageInfo.MediaSerialNumber = metadata.MediaSerialNumber; imageInfo.MediaBarcode = metadata.MediaBarcode; imageInfo.MediaPartNumber = metadata.MediaPartNumber; imageInfo.MediaSequence = metadata.MediaSequence; imageInfo.LastMediaSequence = metadata.LastMediaSequence; imageInfo.DriveManufacturer = metadata.DriveManufacturer; imageInfo.DriveModel = metadata.DriveModel; imageInfo.DriveSerialNumber = metadata.DriveSerialNumber; imageInfo.DriveFirmwareRevision = metadata.DriveFirmwareRevision; return true; } public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } geometryBlock = new GeometryBlock { identifier = BlockType.GeometryBlock, cylinders = cylinders, heads = heads, sectorsPerTrack = sectorsPerTrack }; ErrorMessage = ""; return true; } // TODO: Implement public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) { ErrorMessage = "Writing sectors with tags is not yet implemented."; return false; } // TODO: Implement public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) { ErrorMessage = "Writing sectors with tags is not yet implemented."; return false; } static XmlMediaType GetXmlMediaType(MediaType type) { switch(type) { case MediaType.CD: case MediaType.CDDA: case MediaType.CDG: case MediaType.CDEG: case MediaType.CDI: case MediaType.CDROM: case MediaType.CDROMXA: case MediaType.CDPLUS: case MediaType.CDMO: case MediaType.CDR: case MediaType.CDRW: case MediaType.CDMRW: case MediaType.VCD: case MediaType.SVCD: case MediaType.PCD: case MediaType.SACD: case MediaType.DDCD: case MediaType.DDCDR: case MediaType.DDCDRW: case MediaType.DTSCD: case MediaType.CDMIDI: case MediaType.CDV: case MediaType.DVDROM: case MediaType.DVDR: case MediaType.DVDRW: case MediaType.DVDPR: case MediaType.DVDPRW: case MediaType.DVDPRWDL: case MediaType.DVDRDL: case MediaType.DVDPRDL: case MediaType.DVDRAM: case MediaType.DVDRWDL: case MediaType.DVDDownload: case MediaType.HDDVDROM: case MediaType.HDDVDRAM: case MediaType.HDDVDR: case MediaType.HDDVDRW: case MediaType.HDDVDRDL: case MediaType.HDDVDRWDL: case MediaType.BDROM: case MediaType.BDR: case MediaType.BDRE: case MediaType.BDRXL: case MediaType.BDREXL: case MediaType.EVD: case MediaType.FVD: case MediaType.HVD: case MediaType.CBHD: case MediaType.HDVMD: case MediaType.VCDHD: case MediaType.SVOD: case MediaType.FDDVD: case MediaType.LD: case MediaType.LDROM: case MediaType.LDROM2: case MediaType.LVROM: case MediaType.MegaLD: case MediaType.PS1CD: case MediaType.PS2CD: case MediaType.PS2DVD: case MediaType.PS3DVD: case MediaType.PS3BD: case MediaType.PS4BD: case MediaType.UMD: case MediaType.XGD: case MediaType.XGD2: case MediaType.XGD3: case MediaType.XGD4: case MediaType.MEGACD: case MediaType.SATURNCD: case MediaType.GDROM: case MediaType.GDR: case MediaType.SuperCDROM2: case MediaType.JaguarCD: case MediaType.ThreeDO: case MediaType.GOD: case MediaType.WOD: case MediaType.WUOD: return XmlMediaType.OpticalDisc; default: return XmlMediaType.BlockMedia; } } ulong GetDdtEntry(ulong sectorAddress) { if(inMemoryDdt) return userDataDdt[sectorAddress]; if(ddtEntryCache.TryGetValue(sectorAddress, out ulong entry)) return entry; long oldPosition = imageStream.Position; imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); imageStream.Position += (long)(sectorAddress * sizeof(ulong)); byte[] temp = new byte[sizeof(ulong)]; imageStream.Read(temp, 0, sizeof(ulong)); imageStream.Position = oldPosition; entry = BitConverter.ToUInt64(temp, 0); if(ddtEntryCache.Count >= MAX_DDT_ENTRY_CACHE) ddtEntryCache.Clear(); ddtEntryCache.Add(sectorAddress, entry); return entry; } void SetDdtEntry(ulong sectorAddress, ulong pointer) { if(inMemoryDdt) { userDataDdt[sectorAddress] = pointer; return; } long oldPosition = imageStream.Position; imageStream.Position = outMemoryDdtPosition + Marshal.SizeOf(typeof(DdtHeader)); imageStream.Position += (long)(sectorAddress * sizeof(ulong)); imageStream.Write(BitConverter.GetBytes(pointer), 0, sizeof(ulong)); imageStream.Position = oldPosition; } static MediaTagType GetMediaTagTypeForDataType(DataType type) { switch(type) { case DataType.CompactDiscPartialToc: return MediaTagType.CD_TOC; case DataType.CompactDiscSessionInfo: return MediaTagType.CD_SessionInfo; case DataType.CompactDiscToc: return MediaTagType.CD_FullTOC; case DataType.CompactDiscPma: return MediaTagType.CD_PMA; case DataType.CompactDiscAtip: return MediaTagType.CD_ATIP; case DataType.CompactDiscLeadInCdText: return MediaTagType.CD_TEXT; case DataType.DvdPfi: return MediaTagType.DVD_PFI; case DataType.DvdLeadInCmi: return MediaTagType.DVD_CMI; case DataType.DvdDiscKey: return MediaTagType.DVD_DiscKey; case DataType.DvdBca: return MediaTagType.DVD_BCA; case DataType.DvdDmi: return MediaTagType.DVD_DMI; case DataType.DvdMediaIdentifier: return MediaTagType.DVD_MediaIdentifier; case DataType.DvdMediaKeyBlock: return MediaTagType.DVD_MKB; case DataType.DvdRamDds: return MediaTagType.DVDRAM_DDS; case DataType.DvdRamMediumStatus: return MediaTagType.DVDRAM_MediumStatus; case DataType.DvdRamSpareArea: return MediaTagType.DVDRAM_SpareArea; case DataType.DvdRRmd: return MediaTagType.DVDR_RMD; case DataType.DvdRPrerecordedInfo: return MediaTagType.DVDR_PreRecordedInfo; case DataType.DvdRMediaIdentifier: return MediaTagType.DVDR_MediaIdentifier; case DataType.DvdRPfi: return MediaTagType.DVDR_PFI; case DataType.DvdAdip: return MediaTagType.DVD_ADIP; case DataType.HdDvdCpi: return MediaTagType.HDDVD_CPI; case DataType.HdDvdMediumStatus: return MediaTagType.HDDVD_MediumStatus; case DataType.DvdDlLayerCapacity: return MediaTagType.DVDDL_LayerCapacity; case DataType.DvdDlMiddleZoneAddress: return MediaTagType.DVDDL_MiddleZoneAddress; case DataType.DvdDlJumpIntervalSize: return MediaTagType.DVDDL_JumpIntervalSize; case DataType.DvdDlManualLayerJumpLba: return MediaTagType.DVDDL_ManualLayerJumpLBA; case DataType.BlurayDi: return MediaTagType.BD_DI; case DataType.BlurayBca: return MediaTagType.BD_BCA; case DataType.BlurayDds: return MediaTagType.BD_DDS; case DataType.BlurayCartridgeStatus: return MediaTagType.BD_CartridgeStatus; case DataType.BluraySpareArea: return MediaTagType.BD_SpareArea; case DataType.AacsVolumeIdentifier: return MediaTagType.AACS_VolumeIdentifier; case DataType.AacsSerialNumber: return MediaTagType.AACS_SerialNumber; case DataType.AacsMediaIdentifier: return MediaTagType.AACS_MediaIdentifier; case DataType.AacsMediaKeyBlock: return MediaTagType.AACS_MKB; case DataType.AacsDataKeys: return MediaTagType.AACS_DataKeys; case DataType.AacsLbaExtents: return MediaTagType.AACS_LBAExtents; case DataType.CprmMediaKeyBlock: return MediaTagType.AACS_CPRM_MKB; case DataType.HybridRecognizedLayers: return MediaTagType.Hybrid_RecognizedLayers; case DataType.ScsiMmcWriteProtection: return MediaTagType.MMC_WriteProtection; case DataType.ScsiMmcDiscInformation: return MediaTagType.MMC_DiscInformation; case DataType.ScsiMmcTrackResourcesInformation: return MediaTagType.MMC_TrackResourcesInformation; case DataType.ScsiMmcPowResourcesInformation: return MediaTagType.MMC_POWResourcesInformation; case DataType.ScsiInquiry: return MediaTagType.SCSI_INQUIRY; case DataType.ScsiModePage2A: return MediaTagType.SCSI_MODEPAGE_2A; case DataType.AtaIdentify: return MediaTagType.ATA_IDENTIFY; case DataType.AtapiIdentify: return MediaTagType.ATAPI_IDENTIFY; case DataType.PcmciaCis: return MediaTagType.PCMCIA_CIS; case DataType.SecureDigitalCid: return MediaTagType.SD_CID; case DataType.SecureDigitalCsd: return MediaTagType.SD_CSD; case DataType.SecureDigitalScr: return MediaTagType.SD_SCR; case DataType.SecureDigitalOcr: return MediaTagType.SD_OCR; case DataType.MultiMediaCardCid: return MediaTagType.MMC_CID; case DataType.MultiMediaCardCsd: return MediaTagType.MMC_CSD; case DataType.MultiMediaCardOcr: return MediaTagType.MMC_OCR; case DataType.MultiMediaCardExtendedCsd: return MediaTagType.MMC_ExtendedCSD; case DataType.XboxSecuritySector: return MediaTagType.Xbox_SecuritySector; case DataType.FloppyLeadOut: return MediaTagType.Floppy_LeadOut; case DataType.DvdDiscControlBlock: return MediaTagType.DCB; case DataType.CompactDiscLeadIn: return MediaTagType.CD_LeadIn; case DataType.CompactDiscLeadOut: return MediaTagType.CD_LeadOut; case DataType.ScsiModeSense6: return MediaTagType.SCSI_MODESENSE_6; case DataType.ScsiModeSense10: return MediaTagType.SCSI_MODESENSE_10; case DataType.UsbDescriptors: return MediaTagType.USB_Descriptors; case DataType.XboxDmi: return MediaTagType.Xbox_DMI; case DataType.XboxPfi: return MediaTagType.Xbox_PFI; default: throw new ArgumentOutOfRangeException(); } } static DataType GetDataTypeForMediaTag(MediaTagType tag) { switch(tag) { case MediaTagType.CD_TOC: return DataType.CompactDiscPartialToc; case MediaTagType.CD_SessionInfo: return DataType.CompactDiscSessionInfo; case MediaTagType.CD_FullTOC: return DataType.CompactDiscToc; case MediaTagType.CD_PMA: return DataType.CompactDiscPma; case MediaTagType.CD_ATIP: return DataType.CompactDiscAtip; case MediaTagType.CD_TEXT: return DataType.CompactDiscLeadInCdText; case MediaTagType.DVD_PFI: return DataType.DvdPfi; case MediaTagType.DVD_CMI: return DataType.DvdLeadInCmi; case MediaTagType.DVD_DiscKey: return DataType.DvdDiscKey; case MediaTagType.DVD_BCA: return DataType.DvdBca; case MediaTagType.DVD_DMI: return DataType.DvdDmi; case MediaTagType.DVD_MediaIdentifier: return DataType.DvdMediaIdentifier; case MediaTagType.DVD_MKB: return DataType.DvdMediaKeyBlock; case MediaTagType.DVDRAM_DDS: return DataType.DvdRamDds; case MediaTagType.DVDRAM_MediumStatus: return DataType.DvdRamMediumStatus; case MediaTagType.DVDRAM_SpareArea: return DataType.DvdRamSpareArea; case MediaTagType.DVDR_RMD: return DataType.DvdRRmd; case MediaTagType.DVDR_PreRecordedInfo: return DataType.DvdRPrerecordedInfo; case MediaTagType.DVDR_MediaIdentifier: return DataType.DvdRMediaIdentifier; case MediaTagType.DVDR_PFI: return DataType.DvdRPfi; case MediaTagType.DVD_ADIP: return DataType.DvdAdip; case MediaTagType.HDDVD_CPI: return DataType.HdDvdCpi; case MediaTagType.HDDVD_MediumStatus: return DataType.HdDvdMediumStatus; case MediaTagType.DVDDL_LayerCapacity: return DataType.DvdDlLayerCapacity; case MediaTagType.DVDDL_MiddleZoneAddress: return DataType.DvdDlMiddleZoneAddress; case MediaTagType.DVDDL_JumpIntervalSize: return DataType.DvdDlJumpIntervalSize; case MediaTagType.DVDDL_ManualLayerJumpLBA: return DataType.DvdDlManualLayerJumpLba; case MediaTagType.BD_DI: return DataType.BlurayDi; case MediaTagType.BD_BCA: return DataType.BlurayBca; case MediaTagType.BD_DDS: return DataType.BlurayDds; case MediaTagType.BD_CartridgeStatus: return DataType.BlurayCartridgeStatus; case MediaTagType.BD_SpareArea: return DataType.BluraySpareArea; case MediaTagType.AACS_VolumeIdentifier: return DataType.AacsVolumeIdentifier; case MediaTagType.AACS_SerialNumber: return DataType.AacsSerialNumber; case MediaTagType.AACS_MediaIdentifier: return DataType.AacsMediaIdentifier; case MediaTagType.AACS_MKB: return DataType.AacsMediaKeyBlock; case MediaTagType.AACS_DataKeys: return DataType.AacsDataKeys; case MediaTagType.AACS_LBAExtents: return DataType.AacsLbaExtents; case MediaTagType.AACS_CPRM_MKB: return DataType.CprmMediaKeyBlock; case MediaTagType.Hybrid_RecognizedLayers: return DataType.HybridRecognizedLayers; case MediaTagType.MMC_WriteProtection: return DataType.ScsiMmcWriteProtection; case MediaTagType.MMC_DiscInformation: return DataType.ScsiMmcDiscInformation; case MediaTagType.MMC_TrackResourcesInformation: return DataType.ScsiMmcTrackResourcesInformation; case MediaTagType.MMC_POWResourcesInformation: return DataType.ScsiMmcPowResourcesInformation; case MediaTagType.SCSI_INQUIRY: return DataType.ScsiInquiry; case MediaTagType.SCSI_MODEPAGE_2A: return DataType.ScsiModePage2A; case MediaTagType.ATA_IDENTIFY: return DataType.AtaIdentify; case MediaTagType.ATAPI_IDENTIFY: return DataType.AtapiIdentify; case MediaTagType.PCMCIA_CIS: return DataType.PcmciaCis; case MediaTagType.SD_CID: return DataType.SecureDigitalCid; case MediaTagType.SD_CSD: return DataType.SecureDigitalCsd; case MediaTagType.SD_SCR: return DataType.SecureDigitalScr; case MediaTagType.SD_OCR: return DataType.SecureDigitalOcr; case MediaTagType.MMC_CID: return DataType.MultiMediaCardCid; case MediaTagType.MMC_CSD: return DataType.MultiMediaCardCsd; case MediaTagType.MMC_OCR: return DataType.MultiMediaCardOcr; case MediaTagType.MMC_ExtendedCSD: return DataType.MultiMediaCardExtendedCsd; case MediaTagType.Xbox_SecuritySector: return DataType.XboxSecuritySector; case MediaTagType.Floppy_LeadOut: return DataType.FloppyLeadOut; case MediaTagType.DCB: return DataType.DvdDiscControlBlock; case MediaTagType.CD_LeadIn: return DataType.CompactDiscLeadIn; case MediaTagType.CD_LeadOut: return DataType.CompactDiscLeadOut; case MediaTagType.SCSI_MODESENSE_6: return DataType.ScsiModeSense6; case MediaTagType.SCSI_MODESENSE_10: return DataType.ScsiModeSense10; case MediaTagType.USB_Descriptors: return DataType.UsbDescriptors; case MediaTagType.Xbox_DMI: return DataType.XboxDmi; case MediaTagType.Xbox_PFI: return DataType.XboxPfi; default: throw new ArgumentOutOfRangeException(nameof(tag), tag, null); } } enum CompressionType : ushort { None = 0, Lzma = 1 } enum DataType : ushort { NoData = 0, UserData = 1, CompactDiscPartialToc = 2, CompactDiscSessionInfo = 3, CompactDiscToc = 4, CompactDiscPma = 5, CompactDiscAtip = 6, CompactDiscLeadInCdText = 7, DvdPfi = 8, DvdLeadInCmi = 9, DvdDiscKey = 10, DvdBca = 11, DvdDmi = 12, DvdMediaIdentifier = 13, DvdMediaKeyBlock = 14, DvdRamDds = 15, DvdRamMediumStatus = 16, DvdRamSpareArea = 17, DvdRRmd = 18, DvdRPrerecordedInfo = 19, DvdRMediaIdentifier = 20, DvdRPfi = 21, DvdAdip = 22, HdDvdCpi = 23, HdDvdMediumStatus = 24, DvdDlLayerCapacity = 25, DvdDlMiddleZoneAddress = 26, DvdDlJumpIntervalSize = 27, DvdDlManualLayerJumpLba = 28, BlurayDi = 29, BlurayBca = 30, BlurayDds = 31, BlurayCartridgeStatus = 32, BluraySpareArea = 33, AacsVolumeIdentifier = 34, AacsSerialNumber = 35, AacsMediaIdentifier = 36, AacsMediaKeyBlock = 37, AacsDataKeys = 38, AacsLbaExtents = 39, CprmMediaKeyBlock = 40, HybridRecognizedLayers = 41, ScsiMmcWriteProtection = 42, ScsiMmcDiscInformation = 43, ScsiMmcTrackResourcesInformation = 44, ScsiMmcPowResourcesInformation = 45, ScsiInquiry = 46, ScsiModePage2A = 47, AtaIdentify = 48, AtapiIdentify = 49, PcmciaCis = 50, SecureDigitalCid = 51, SecureDigitalCsd = 52, SecureDigitalScr = 53, SecureDigitalOcr = 54, MultiMediaCardCid = 55, MultiMediaCardCsd = 56, MultiMediaCardOcr = 57, MultiMediaCardExtendedCsd = 58, XboxSecuritySector = 59, FloppyLeadOut = 60, DvdDiscControlBlock = 61, CompactDiscLeadIn = 62, CompactDiscLeadOut = 63, ScsiModeSense6 = 64, ScsiModeSense10 = 65, UsbDescriptors = 66, XboxDmi = 67, XboxPfi = 68 } enum BlockType : uint { DataBlock = 0x484B4C42, DeDuplicationTable = 0x48544444, Index = 0x48584449, GeometryBlock = 0x4D4F4547, MetadataBlock = 0x5444545D } /// Header, at start of file [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] struct DicHeader { /// Header identifier, public ulong identifier; /// UTF-16LE name of the application that created the image [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string application; /// Image format major version. A new major version means a possibly incompatible change of format public byte imageMajorVersion; /// Image format minor version. A new minor version indicates a compatible change of format public byte imageMinorVersion; /// Major version of the application that created the image public byte applicationMajorVersion; /// Minor version of the application that created the image public byte applicationMinorVersion; /// Type of media contained on image public MediaType mediaType; /// Offset to index public ulong indexOffset; /// /// Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image creation time /// public long creationTime; /// /// Windows filetime (100 nanoseconds since 1601/01/01 00:00:00 UTC) of image last written time /// public long lastWrittenTime; } /// Header for a deduplication table. Table follows it [StructLayout(LayoutKind.Sequential, Pack = 1)] struct DdtHeader { /// Identifier, public BlockType identifier; /// Type of data pointed by this DDT public DataType type; /// Compression algorithm used to compress the DDT public CompressionType compression; /// Each entry is ((byte offset in file) << shift) + (sector offset in block) public byte shift; /// How many entries are in the table public ulong entries; /// Compressed length for the DDT public ulong cmpLength; /// Uncompressed length for the DDT public ulong length; /// CRC64-ECMA of the compressed DDT public ulong cmpCrc64; /// CRC64-ECMA of the uncompressed DDT public ulong crc64; } /// Header for the index, followed by entries [StructLayout(LayoutKind.Sequential, Pack = 1)] struct IndexHeader { /// Identifier, public BlockType identifier; /// How many entries follow this header public ushort entries; /// CRC64-ECMA of the index public ulong crc64; } /// Index entry [StructLayout(LayoutKind.Sequential, Pack = 1)] struct IndexEntry { /// Type of item pointed by this entry public BlockType blockType; /// Type of data contained by the block pointed by this entry public DataType dataType; /// Offset in file where item is stored public ulong offset; } /// Block header, precedes block data [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BlockHeader { /// Identifier, public BlockType identifier; /// Type of data contained by this block public DataType type; /// Compression algorithm used to compress the block public CompressionType compression; /// Size in bytes of each sector contained in this block public uint sectorSize; /// Compressed length for the block public uint cmpLength; /// Uncompressed length for the block public uint length; /// CRC64-ECMA of the compressed block public ulong cmpCrc64; /// CRC64-ECMA of the uncompressed block public ulong crc64; } /// Geometry block, contains physical geometry information [StructLayout(LayoutKind.Sequential, Pack = 1)] struct GeometryBlock { /// Identifier, public BlockType identifier; public uint cylinders; public uint heads; public uint sectorsPerTrack; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct MetadataBlock { /// Identifier, public BlockType identifier; /// Size in bytes of this whole metadata block public uint blockSize; /// Sequence of media set this media belongs to public int mediaSequence; /// Total number of media on the media set this media belongs to public int lastMediaSequence; /// Offset to start of creator string from start of this block public uint creatorOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint creatorLength; /// Offset to start of creator string from start of this block public uint commentsOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint commentsLength; /// Offset to start of creator string from start of this block public uint mediaTitleOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaTitleLength; /// Offset to start of creator string from start of this block public uint mediaManufacturerOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaManufacturerLength; /// Offset to start of creator string from start of this block public uint mediaModelOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaModelLength; /// Offset to start of creator string from start of this block public uint mediaSerialNumberOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaSerialNumberLength; /// Offset to start of creator string from start of this block public uint mediaBarcodeOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaBarcodeLength; /// Offset to start of creator string from start of this block public uint mediaPartNumberOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint mediaPartNumberLength; /// Offset to start of creator string from start of this block public uint driveManufacturerOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint driveManufacturerLength; /// Offset to start of creator string from start of this block public uint driveModelOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint driveModelLength; /// Offset to start of creator string from start of this block public uint driveSerialNumberOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint driveSerialNumberLength; /// Offset to start of creator string from start of this block public uint driveFirmwareRevisionOffset; /// Length in bytes of the null-terminated UTF-16LE creator string public uint driveFirmwareRevisionLength; } } }