// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : Write.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Writes 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-2019 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Xml; using System.Xml.Serialization; using CUETools.Codecs; using CUETools.Codecs.FLAKE; using DiscImageChef.Checksums; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Enums; using DiscImageChef.CommonTypes.Exceptions; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Decoders; using Schemas; using SharpCompress.Compressors.LZMA; using TrackType = DiscImageChef.CommonTypes.Enums.TrackType; namespace DiscImageChef.DiscImages { public partial class DiscImageChef { public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, uint sectorSize) { uint sectorsPerBlock; uint dictionary; uint maxDdtSize; bool doMd5; bool doSha1; bool doSha256; bool doSpamsum; 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; if(options.TryGetValue("dictionary", out tmpValue)) { if(!uint.TryParse(tmpValue, out dictionary)) { ErrorMessage = "Invalid value for dictionary option"; return false; } } else dictionary = 1 << 25; if(options.TryGetValue("max_ddt_size", out tmpValue)) { if(!uint.TryParse(tmpValue, out maxDdtSize)) { ErrorMessage = "Invalid value for max_ddt_size option"; return false; } } else maxDdtSize = 256; if(options.TryGetValue("md5", out tmpValue)) { if(!bool.TryParse(tmpValue, out doMd5)) { ErrorMessage = "Invalid value for md5 option"; return false; } } else doMd5 = false; if(options.TryGetValue("sha1", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha1)) { ErrorMessage = "Invalid value for sha1 option"; return false; } } else doSha1 = false; if(options.TryGetValue("sha256", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha256)) { ErrorMessage = "Invalid value for sha256 option"; return false; } } else doSha256 = false; if(options.TryGetValue("spamsum", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSpamsum)) { ErrorMessage = "Invalid value for spamsum option"; return false; } } else doSpamsum = false; if(options.TryGetValue("deduplicate", out tmpValue)) { if(!bool.TryParse(tmpValue, out deduplicate)) { ErrorMessage = "Invalid value for deduplicate option"; return false; } } else deduplicate = true; if(options.TryGetValue("nocompress", out tmpValue)) { if(!bool.TryParse(tmpValue, out nocompress)) { ErrorMessage = "Invalid value for nocompress option"; return false; } } else nocompress = false; } else { sectorsPerBlock = 4096; dictionary = 1 << 25; maxDdtSize = 256; doMd5 = false; doSha1 = false; doSha256 = false; doSpamsum = false; deduplicate = true; nocompress = false; } // This really, cannot happen if(!SupportedMediaTypes.Contains(mediaType)) { ErrorMessage = $"Unsupport media format {mediaType}"; return false; } // Calculate shift 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, XmlMediaType = GetXmlMediaType(mediaType) }; 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; } // Check if appending to an existing image if(imageStream.Length > Marshal.SizeOf(typeof(DicHeader))) { 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.identifier != DIC_MAGIC) { ErrorMessage = "Cannot append to a non DiscImageChef format image"; return false; } if(header.imageMajorVersion > DICF_VERSION) { ErrorMessage = $"Cannot append to an unknown image version {header.imageMajorVersion}"; return false; } if(header.mediaType != mediaType) { ErrorMessage = $"Cannot write a media with type {mediaType} to an image with type {header.mediaType}"; return false; } } else { header = new DicHeader { identifier = DIC_MAGIC, mediaType = mediaType, creationTime = DateTime.UtcNow.ToFileTimeUtc() }; imageStream.Write(new byte[Marshal.SizeOf(typeof(DicHeader))], 0, Marshal.SizeOf(typeof(DicHeader))); } header.application = "DiscImageChef"; header.imageMajorVersion = DICF_VERSION; header.imageMinorVersion = 0; header.applicationMajorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Major; header.applicationMinorVersion = (byte)typeof(DiscImageChef).Assembly.GetName().Version.Minor; index = new List(); // If there exists an index, we are appending, so read index if(header.indexOffset > 0) { // Can't calculate checksum of an appended image md5Provider = null; sha1Provider = null; sha256Provider = null; spamsumProvider = null; 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) { ErrorMessage = "Index not found in existing image, cannot continue"; return false; } DicConsole.DebugWriteLine("DiscImageChef format plugin", "Index at {0} contains {1} entries", header.indexOffset, idxHeader.entries); 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); } // Invalidate previous checksum block index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); bool foundUserDataDdt = false; foreach(IndexEntry entry in index) { imageStream.Position = (long)entry.offset; switch(entry.blockType) { case BlockType.DataBlock: switch(entry.dataType) { case DataType.CdSectorPrefix: case DataType.CdSectorSuffix: case DataType.CdSectorPrefixCorrected: case DataType.CdSectorSuffixCorrected: case DataType.CdSectorSubchannel: case DataType.AppleProfileTag: case DataType.AppleSonyTag: case DataType.PriamDataTowerTag: break; default: continue; } 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; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found data block type {0} at position {1}", entry.dataType, 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(); } 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); blockCrc = blockCrc.ToArray(); 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; } switch(entry.dataType) { case DataType.CdSectorPrefix: sectorPrefix = data; break; case DataType.CdSectorSuffix: sectorSuffix = data; break; case DataType.CdSectorPrefixCorrected: sectorPrefixMs = new MemoryStream(); sectorPrefixMs.Write(data, 0, data.Length); break; case DataType.CdSectorSuffixCorrected: sectorSuffixMs = new MemoryStream(); sectorSuffixMs.Write(data, 0, data.Length); break; case DataType.CdSectorSubchannel: case DataType.AppleProfileTag: case DataType.AppleSonyTag: case DataType.PriamDataTowerTag: sectorSubchannel = data; break; } break; case BlockType.DeDuplicationTable: // Only user data deduplication tables are used right now if(entry.dataType == DataType.UserData) { 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); if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; if(ddtHeader.entries != imageInfo.Sectors) { ErrorMessage = $"Trying to write a media with {imageInfo.Sectors} sectors to an image with {ddtHeader.entries} sectors, not continuing..."; return false; } 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(); userDataDdt = new ulong[ddtHeader.entries]; for(ulong i = 0; i < ddtHeader.entries; i++) userDataDdt[i] = BitConverter.ToUInt64(decompressedDdt, (int)(i * sizeof(ulong))); 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; } else if(entry.dataType == DataType.CdSectorPrefixCorrected || entry.dataType == DataType.CdSectorSuffixCorrected) { 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); if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; if(ddtHeader.entries != imageInfo.Sectors) { ErrorMessage = $"Trying to write a media with {imageInfo.Sectors} sectors to an image with {ddtHeader.entries} sectors, not continuing..."; return false; } byte[] decompressedDdt = new byte[ddtHeader.length]; uint[] cdDdt = new uint[ddtHeader.entries]; 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); lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); lzmaDdt.Close(); compressedDdtMs.Close(); DateTime ddtEnd = DateTime.UtcNow; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to decompress DDT", (ddtEnd - ddtStart).TotalSeconds); break; case CompressionType.None: imageStream.Read(decompressedDdt, 0, decompressedDdt.Length); break; default: throw new ImageNotSupportedException($"Found unsupported compression algorithm {(ushort)ddtHeader.compression}"); } for(ulong i = 0; i < ddtHeader.entries; i++) cdDdt[i] = BitConverter.ToUInt32(decompressedDdt, (int)(i * sizeof(uint))); switch(entry.dataType) { case DataType.CdSectorPrefixCorrected: sectorPrefixDdt = cdDdt; break; case DataType.CdSectorSuffixCorrected: sectorSuffixDdt = cdDdt; break; } } break; // CICM XML metadata block case BlockType.CicmBlock: CicmMetadataBlock cicmBlock = new CicmMetadataBlock(); structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(cicmBlock)); cicmBlock = (CicmMetadataBlock)Marshal.PtrToStructure(structurePointer, typeof(CicmMetadataBlock)); Marshal.FreeHGlobal(structurePointer); if(cicmBlock.identifier != BlockType.CicmBlock) break; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found CICM XML metadata block at position {0}", entry.offset); byte[] cicmBytes = new byte[cicmBlock.length]; imageStream.Read(cicmBytes, 0, cicmBytes.Length); MemoryStream cicmMs = new MemoryStream(cicmBytes); XmlSerializer cicmXs = new XmlSerializer(typeof(CICMMetadataType)); try { StreamReader sr = new StreamReader(cicmMs); CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); sr.Close(); } catch(XmlException ex) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Exception {0} processing CICM XML metadata block", ex.Message); CicmMetadata = null; } break; // Dump hardware block case BlockType.DumpHardwareBlock: DumpHardwareHeader dumpBlock = new DumpHardwareHeader(); structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpBlock)); dumpBlock = (DumpHardwareHeader)Marshal.PtrToStructure(structurePointer, typeof(DumpHardwareHeader)); Marshal.FreeHGlobal(structurePointer); if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found dump hardware block at position {0}", entry.offset); structureBytes = new byte[dumpBlock.length]; imageStream.Read(structureBytes, 0, structureBytes.Length); Crc64Context.Data(structureBytes, out byte[] dumpCrc); if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) { DicConsole.DebugWriteLine("DiscImageChef format plugin", "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", BitConverter.ToUInt64(dumpCrc, 0), dumpBlock.crc64); break; } imageStream.Position -= structureBytes.Length; DumpHardware = new List(); for(ushort i = 0; i < dumpBlock.entries; i++) { DumpHardwareEntry dumpEntry = new DumpHardwareEntry(); structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; imageStream.Read(structureBytes, 0, structureBytes.Length); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(dumpEntry)); dumpEntry = (DumpHardwareEntry)Marshal.PtrToStructure(structurePointer, typeof(DumpHardwareEntry)); Marshal.FreeHGlobal(structurePointer); DumpHardwareType dump = new DumpHardwareType { Software = new SoftwareType(), Extents = new ExtentType[dumpEntry.extents] }; byte[] tmp; if(dumpEntry.manufacturerLength > 0) { tmp = new byte[dumpEntry.manufacturerLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Manufacturer = Encoding.UTF8.GetString(tmp); } if(dumpEntry.modelLength > 0) { tmp = new byte[dumpEntry.modelLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Model = Encoding.UTF8.GetString(tmp); } if(dumpEntry.revisionLength > 0) { tmp = new byte[dumpEntry.revisionLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Revision = Encoding.UTF8.GetString(tmp); } if(dumpEntry.firmwareLength > 0) { tmp = new byte[dumpEntry.firmwareLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Firmware = Encoding.UTF8.GetString(tmp); } if(dumpEntry.serialLength > 0) { tmp = new byte[dumpEntry.serialLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Serial = Encoding.UTF8.GetString(tmp); } if(dumpEntry.softwareNameLength > 0) { tmp = new byte[dumpEntry.softwareNameLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Software.Name = Encoding.UTF8.GetString(tmp); } if(dumpEntry.softwareVersionLength > 0) { tmp = new byte[dumpEntry.softwareVersionLength - 1]; imageStream.Read(tmp, 0, tmp.Length); imageStream.Position += 1; dump.Software.Version = Encoding.UTF8.GetString(tmp); } if(dumpEntry.softwareOperatingSystemLength > 0) { tmp = new byte[dumpEntry.softwareOperatingSystemLength - 1]; imageStream.Position += 1; imageStream.Read(tmp, 0, tmp.Length); dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); } tmp = new byte[16]; for(uint j = 0; j < dumpEntry.extents; j++) { imageStream.Read(tmp, 0, tmp.Length); dump.Extents[j] = new ExtentType { Start = BitConverter.ToUInt64(tmp, 0), End = BitConverter.ToUInt64(tmp, 8) }; } dump.Extents = dump.Extents.OrderBy(t => t.Start).ToArray(); if(dump.Extents.Length > 0) DumpHardware.Add(dump); } if(DumpHardware.Count == 0) DumpHardware = null; break; } } if(!foundUserDataDdt) { ErrorMessage = "Could not find user data deduplication table."; return false; } if(sectorSuffixMs == null || sectorSuffixDdt == null || sectorPrefixMs == null || sectorPrefixDdt == null) { sectorSuffixMs = null; sectorSuffixDdt = null; sectorPrefixMs = null; sectorPrefixDdt = null; } } // Creating new else { // Checking that DDT is smaller than requested size inMemoryDdt = sectors <= maxDdtSize * 1024 * 1024 / sizeof(ulong); // If in memory, easy if(inMemoryDdt) userDataDdt = new ulong[sectors]; // If not, create the block, add to index, and enlarge the file to allow the DDT to exist on-disk 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; imageStream.Position += (long)(sectors * sizeof(ulong)) - 1; imageStream.WriteByte(0); } if(doMd5) md5Provider = new Md5Context(); if(doSha1) sha1Provider = new Sha1Context(); if(doSha256) sha256Provider = new Sha256Context(); if(doSpamsum) spamsumProvider = new SpamSumContext(); } DicConsole.DebugWriteLine("DiscImageChef format plugin", "In memory DDT?: {0}", inMemoryDdt); // Initialize tables imageStream.Seek(0, SeekOrigin.End); mediaTags = new Dictionary(); checksumProvider = SHA256.Create(); deduplicationTable = new Dictionary(); trackIsrcs = new Dictionary(); trackFlags = new Dictionary(); // Initialize compressors properties (all maxed) lzmaEncoderProperties = new LzmaEncoderProperties(true, (int)dictionary, 273); flakeWriterSettings = new FlakeWriterSettings { PCM = AudioPCMConfig.RedBook, DoMD5 = false, BlockSize = (1 << shift) * SAMPLES_PER_SECTOR, MinFixedOrder = 0, MaxFixedOrder = 4, MinLPCOrder = 1, MaxLPCOrder = 32, MaxPartitionOrder = 8, StereoMethod = StereoMethod.Evaluate, PredictionType = PredictionType.Search, WindowMethod = WindowMethod.EvaluateN, EstimationDepth = 5, MinPrecisionSearch = 1, MaxPrecisionSearch = 1, TukeyParts = 0, TukeyOverlap = 1.0, TukeyP = 1.0, AllowNonSubset = true }; // Check if FLAKE's block size is bigger than what we want if(flakeWriterSettings.BlockSize > MAX_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MAX_FLAKE_BLOCK; if(flakeWriterSettings.BlockSize < MIN_FLAKE_BLOCK) flakeWriterSettings.BlockSize = MIN_FLAKE_BLOCK; FlakeWriter.Vendor = "DiscImageChef"; 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; } 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; } if((imageInfo.XmlMediaType != XmlMediaType.OpticalDisc || !writingLong) && !rewinded) { if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) { rewinded = true; md5Provider = null; sha1Provider = null; sha256Provider = null; spamsumProvider = null; } md5Provider?.Update(data); sha1Provider?.Update(data); sha256Provider?.Update(data); spamsumProvider?.Update(data); lastWrittenBlock = sectorAddress; } if(sectorAddress == 0) alreadyWrittenZero = true; byte[] hash = null; writtenSectors++; // Compute hash only if asked to deduplicate, or the sector is empty (those will always be deduplicated) if(deduplicate || ArrayHelpers.ArrayIsNullOrEmpty(data)) hash = checksumProvider.ComputeHash(data); string hashString = null; if(hash != null) { StringBuilder hashSb = new StringBuilder(); foreach(byte h in hash) hashSb.Append(h.ToString("x2")); hashString = hashSb.ToString(); if(deduplicationTable.TryGetValue(hashString, out ulong pointer)) { SetDdtEntry(sectorAddress, pointer); ErrorMessage = ""; return true; } } Track trk = new Track(); // If optical disc check track if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { trk = Tracks.FirstOrDefault(t => sectorAddress >= t.TrackStartSector && sectorAddress <= t.TrackEndSector); if(trk.TrackSequence == 0 && trk.TrackStartSector == 0 && trk.TrackEndSector == 0) throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Can't found track containing requested sector"); } // Close current block first if(blockStream != null && // When sector siz changes (currentBlockHeader.sectorSize != data.Length || // When block if filled currentBlockOffset == 1 << shift || // When we change to/from CompactDisc audio currentBlockHeader.compression == CompressionType.Flac && trk.TrackType != TrackType.Audio)) { currentBlockHeader.length = currentBlockOffset * currentBlockHeader.sectorSize; currentBlockHeader.crc64 = BitConverter.ToUInt64(crc64.Final(), 0); Crc64Context cmpCrc64Context = new Crc64Context(); byte[] lzmaProperties = new byte[0]; if(currentBlockHeader.compression == CompressionType.Flac) { long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; // Fill FLAC block if(remaining != 0) { AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); flakeWriter.Write(audioBuffer); } // This trick because CUETools.Codecs.Flake closes the underlying stream long realLength = blockStream.Length; byte[] buffer = new byte[realLength]; flakeWriter.Close(); Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); blockStream = new MemoryStream(buffer); } else if(currentBlockHeader.compression == CompressionType.Lzma) { lzmaProperties = lzmaBlockStream.Properties; lzmaBlockStream.Close(); lzmaBlockStream = null; cmpCrc64Context.Update(lzmaProperties); if(blockStream.Length > decompressedStream.Length) currentBlockHeader.compression = CompressionType.None; } if(currentBlockHeader.compression == CompressionType.None) { blockStream = decompressedStream; currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; } else { cmpCrc64Context.Update(blockStream.ToArray()); currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); } currentBlockHeader.cmpLength = (uint)blockStream.Length; if(currentBlockHeader.compression == CompressionType.Lzma) currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; index.Add(new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.UserData, offset = (ulong)imageStream.Position }); 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; if(currentBlockHeader.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, false); currentBlockOffset = 0; } // No block set if(blockStream == null) { currentBlockHeader = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.UserData, compression = nocompress ? CompressionType.None : CompressionType.Lzma, sectorSize = (uint)data.Length }; if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc && trk.TrackType == TrackType.Audio && !nocompress ) currentBlockHeader.compression = CompressionType.Flac; // JaguarCD stores data in audio tracks. FLAC is too inefficient, use LZMA there. if(imageInfo.MediaType == MediaType.JaguarCD && trk.TrackType == TrackType.Audio && !nocompress && currentBlockHeader.compression == CompressionType.Flac && trk.TrackSession > 1) currentBlockHeader.compression = CompressionType.Lzma; blockStream = new MemoryStream(); decompressedStream = new MemoryStream(); if(currentBlockHeader.compression == CompressionType.Flac) flakeWriter = new FlakeWriter("", blockStream, flakeWriterSettings) {DoSeekTable = false}; else lzmaBlockStream = new LzmaStream(lzmaEncoderProperties, false, blockStream); crc64 = new Crc64Context(); } ulong ddtEntry = (ulong)((imageStream.Position << shift) + currentBlockOffset); if(hash != null) deduplicationTable.Add(hashString, ddtEntry); if(currentBlockHeader.compression == CompressionType.Flac) { AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, data, SAMPLES_PER_SECTOR); flakeWriter.Write(audioBuffer); } else { decompressedStream.Write(data, 0, data.Length); if(currentBlockHeader.compression == CompressionType.Lzma) lzmaBlockStream.Write(data, 0, data.Length); } SetDdtEntry(sectorAddress, ddtEntry); crc64.Update(data); currentBlockOffset++; ErrorMessage = ""; return true; } 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; } public bool WriteSectorLong(byte[] data, ulong sectorAddress) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } byte[] sector; switch(imageInfo.XmlMediaType) { case XmlMediaType.OpticalDisc: Track track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && sectorAddress <= trk.TrackEndSector); if(track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0) { ErrorMessage = $"Can't found track containing {sectorAddress}"; return false; } if(data.Length != 2352) { ErrorMessage = "Incorrect data size"; return false; } writingLong = true; if(!rewinded) { if(sectorAddress <= lastWrittenBlock && alreadyWrittenZero) { rewinded = true; md5Provider = null; sha1Provider = null; sha256Provider = null; spamsumProvider = null; } md5Provider?.Update(data); sha1Provider?.Update(data); sha256Provider?.Update(data); spamsumProvider?.Update(data); lastWrittenBlock = sectorAddress; } bool prefixCorrect; int minute; int second; int frame; int storedLba; // Split raw cd sector data in prefix (sync, header), user data and suffix (edc, ecc p, ecc q) switch(track.TrackType) { case TrackType.Audio: case TrackType.Data: return WriteSector(data, sectorAddress); case TrackType.CdMode1: if(sectorPrefix != null && sectorSuffix != null) { sector = new byte[2048]; Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); Array.Copy(data, 16, sector, 0, 2048); Array.Copy(data, 2064, sectorSuffix, (int)sectorAddress * 288, 288); return WriteSector(sector, sectorAddress); } if(sectorSuffixMs == null) sectorSuffixMs = new MemoryStream(); if(sectorPrefixMs == null) sectorPrefixMs = new MemoryStream(); if(sectorSuffixDdt == null) { sectorSuffixDdt = new uint[imageInfo.Sectors]; EccInit(); } if(sectorPrefixDdt == null) sectorPrefixDdt = new uint[imageInfo.Sectors]; sector = new byte[2048]; if(ArrayHelpers.ArrayIsNullOrEmpty(data)) { sectorPrefixDdt[sectorAddress] = (uint)CdFixFlags.NotDumped; sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.NotDumped; return WriteSector(sector, sectorAddress); } prefixCorrect = true; if(data[0x00] != 0x00 || data[0x01] != 0xFF || data[0x02] != 0xFF || data[0x03] != 0xFF || data[0x04] != 0xFF || data[0x05] != 0xFF || data[0x06] != 0xFF || data[0x07] != 0xFF || data[0x08] != 0xFF || data[0x09] != 0xFF || data[0x0A] != 0xFF || data[0x0B] != 0x00 || data[0x0F] != 0x01) prefixCorrect = false; if(prefixCorrect) { minute = (data[0x0C] >> 4) * 10 + (data[0x0C] & 0x0F); second = (data[0x0D] >> 4) * 10 + (data[0x0D] & 0x0F); frame = (data[0x0E] >> 4) * 10 + (data[0x0E] & 0x0F); storedLba = minute * 60 * 75 + second * 75 + frame - 150; prefixCorrect = storedLba == (int)sectorAddress; } if(prefixCorrect) sectorPrefixDdt[sectorAddress] = (uint)CdFixFlags.Correct; else { if((sectorPrefixDdt[sectorAddress] & CD_DFIX_MASK) > 0) sectorPrefixMs.Position = ((sectorPrefixDdt[sectorAddress] & CD_DFIX_MASK) - 1) * 16; else sectorPrefixMs.Seek(0, SeekOrigin.End); sectorPrefixDdt[sectorAddress] = (uint)(sectorPrefixMs.Position / 16 + 1); sectorPrefixMs.Write(data, 0, 16); } bool correct = SuffixIsCorrect(data); if(correct) sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Correct; else { if((sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) > 0) sectorSuffixMs.Position = ((sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) - 1) * 288; else sectorSuffixMs.Seek(0, SeekOrigin.End); sectorSuffixDdt[sectorAddress] = (uint)(sectorSuffixMs.Position / 288 + 1); sectorSuffixMs.Write(data, 2064, 288); } Array.Copy(data, 16, sector, 0, 2048); return WriteSector(sector, sectorAddress); case TrackType.CdMode2Formless: case TrackType.CdMode2Form1: case TrackType.CdMode2Form2: if(sectorPrefix != null && sectorSuffix != null) { sector = new byte[2336]; Array.Copy(data, 0, sectorPrefix, (int)sectorAddress * 16, 16); Array.Copy(data, 16, sector, 0, 2336); return WriteSector(sector, sectorAddress); } if(sectorSuffixMs == null) sectorSuffixMs = new MemoryStream(); if(sectorPrefixMs == null) sectorPrefixMs = new MemoryStream(); if(sectorSuffixDdt == null) { sectorSuffixDdt = new uint[imageInfo.Sectors]; EccInit(); } if(sectorPrefixDdt == null) sectorPrefixDdt = new uint[imageInfo.Sectors]; sector = new byte[2328]; if(ArrayHelpers.ArrayIsNullOrEmpty(data)) { sectorPrefixDdt[sectorAddress] = (uint)CdFixFlags.NotDumped; return WriteSector(sector, sectorAddress); } prefixCorrect = true; if(data[0x00] != 0x00 || data[0x01] != 0xFF || data[0x02] != 0xFF || data[0x03] != 0xFF || data[0x04] != 0xFF || data[0x05] != 0xFF || data[0x06] != 0xFF || data[0x07] != 0xFF || data[0x08] != 0xFF || data[0x09] != 0xFF || data[0x0A] != 0xFF || data[0x0B] != 0x00 || data[0x0F] != 0x02) prefixCorrect = false; if(prefixCorrect) { minute = (data[0x0C] >> 4) * 10 + (data[0x0C] & 0x0F); second = (data[0x0D] >> 4) * 10 + (data[0x0D] & 0x0F); frame = (data[0x0E] >> 4) * 10 + (data[0x0E] & 0x0F); storedLba = minute * 60 * 75 + second * 75 + frame - 150; prefixCorrect = storedLba == (int)sectorAddress; } if(prefixCorrect) sectorPrefixDdt[sectorAddress] = (uint)CdFixFlags.Correct; else { if((sectorPrefixDdt[sectorAddress] & CD_DFIX_MASK) > 0) sectorPrefixMs.Position = ((sectorPrefixDdt[sectorAddress] & CD_DFIX_MASK) - 1) * 16; else sectorPrefixMs.Seek(0, SeekOrigin.End); sectorPrefixDdt[sectorAddress] = (uint)(sectorPrefixMs.Position / 16 + 1); sectorPrefixMs.Write(data, 0, 16); } if(mode2Subheaders == null) mode2Subheaders = new byte[imageInfo.Sectors * 8]; bool correctEcc = SuffixIsCorrectMode2(data); bool correctEdc = false; if(correctEcc) { uint computedEdc = ComputeEdc(0, data, 0x808, 0x10); uint edc = BitConverter.ToUInt32(data, 0x818); correctEdc = computedEdc == edc; } else { uint computedEdc = ComputeEdc(0, data, 0x91C, 0x10); uint edc = BitConverter.ToUInt32(data, 0x92C); correctEdc = computedEdc == edc; } if(correctEcc && correctEdc) { sector = new byte[2048]; if(sectorSuffixDdt == null) sectorSuffixDdt = new uint[imageInfo.Sectors]; sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form1Ok; Array.Copy(data, 24, sector, 0, 2048); } else if(correctEdc) { sector = new byte[2324]; if(sectorSuffixDdt == null) sectorSuffixDdt = new uint[imageInfo.Sectors]; sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2Ok; Array.Copy(data, 24, sector, 0, 2324); } else if(BitConverter.ToUInt32(data, 0x92C) == 0) { sector = new byte[2324]; if(sectorSuffixDdt == null) sectorSuffixDdt = new uint[imageInfo.Sectors]; sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2NoCrc; Array.Copy(data, 24, sector, 0, 2324); } else Array.Copy(data, 24, sector, 0, 2328); Array.Copy(data, 16, mode2Subheaders, (int)sectorAddress * 8, 8); return WriteSector(sector, sectorAddress); } break; case XmlMediaType.BlockMedia: switch(imageInfo.MediaType) { // Split user data from Apple tags case MediaType.AppleFileWare: case MediaType.AppleProfile: case MediaType.AppleSonyDS: case MediaType.AppleSonySS: case MediaType.AppleWidget: case MediaType.PriamDataTower: byte[] oldTag; byte[] newTag; switch(data.Length - 512) { // Sony tag, convert to Profile case 12 when imageInfo.MediaType == MediaType.AppleProfile || imageInfo.MediaType == MediaType.AppleFileWare: oldTag = new byte[12]; Array.Copy(data, 512, oldTag, 0, 12); newTag = LisaTag.DecodeSonyTag(oldTag)?.ToProfile().GetBytes(); break; // Sony tag, convert to Priam case 12 when imageInfo.MediaType == MediaType.PriamDataTower: oldTag = new byte[12]; Array.Copy(data, 512, oldTag, 0, 12); newTag = LisaTag.DecodeSonyTag(oldTag)?.ToPriam().GetBytes(); break; // Sony tag, copy to Sony case 12 when imageInfo.MediaType == MediaType.AppleSonySS || imageInfo.MediaType == MediaType.AppleSonySS: newTag = new byte[12]; Array.Copy(data, 512, newTag, 0, 12); break; // Profile tag, copy to Profile case 20 when imageInfo.MediaType == MediaType.AppleProfile || imageInfo.MediaType == MediaType.AppleFileWare: newTag = new byte[20]; Array.Copy(data, 512, newTag, 0, 20); break; // Profile tag, convert to Priam case 20 when imageInfo.MediaType == MediaType.PriamDataTower: oldTag = new byte[20]; Array.Copy(data, 512, oldTag, 0, 20); newTag = LisaTag.DecodeProfileTag(oldTag)?.ToPriam().GetBytes(); break; // Profile tag, convert to Sony case 20 when imageInfo.MediaType == MediaType.AppleSonySS || imageInfo.MediaType == MediaType.AppleSonySS: oldTag = new byte[20]; Array.Copy(data, 512, oldTag, 0, 20); newTag = LisaTag.DecodeProfileTag(oldTag)?.ToSony().GetBytes(); break; // Priam tag, convert to Profile case 24 when imageInfo.MediaType == MediaType.AppleProfile || imageInfo.MediaType == MediaType.AppleFileWare: oldTag = new byte[24]; Array.Copy(data, 512, oldTag, 0, 24); newTag = LisaTag.DecodePriamTag(oldTag)?.ToProfile().GetBytes(); break; // Priam tag, copy to Priam case 12 when imageInfo.MediaType == MediaType.PriamDataTower: newTag = new byte[24]; Array.Copy(data, 512, newTag, 0, 24); break; // Priam tag, convert to Sony case 24 when imageInfo.MediaType == MediaType.AppleSonySS || imageInfo.MediaType == MediaType.AppleSonySS: oldTag = new byte[24]; Array.Copy(data, 512, oldTag, 0, 24); newTag = LisaTag.DecodePriamTag(oldTag)?.ToSony().GetBytes(); break; case 0: newTag = null; break; default: ErrorMessage = "Incorrect data size"; return false; } sector = new byte[512]; Array.Copy(data, 0, sector, 0, 512); if(newTag == null) return WriteSector(sector, sectorAddress); if(sectorSubchannel == null) sectorSubchannel = new byte[newTag.Length * (int)imageInfo.Sectors]; Array.Copy(newTag, 0, sectorSubchannel, newTag.Length * (int)sectorAddress, newTag.Length); return WriteSector(sector, sectorAddress); } break; } ErrorMessage = "Unknown long sector type, cannot write."; return false; } public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) { byte[] sector; switch(imageInfo.XmlMediaType) { case XmlMediaType.OpticalDisc: Track track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && sectorAddress <= trk.TrackEndSector); if(track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0) { ErrorMessage = $"Can't found track containing {sectorAddress}"; return false; } if(data.Length % 2352 != 0) { ErrorMessage = "Incorrect data size"; return false; } if(sectorAddress + length > track.TrackEndSector + 1) { ErrorMessage = "Can't cross tracks"; return false; } sector = new byte[2352]; for(uint i = 0; i < length; i++) { Array.Copy(data, 2352 * i, sector, 0, 2352); if(!WriteSectorLong(sector, sectorAddress + i)) return false; } ErrorMessage = ""; return true; case XmlMediaType.BlockMedia: switch(imageInfo.MediaType) { case MediaType.AppleFileWare: case MediaType.AppleProfile: case MediaType.AppleSonyDS: case MediaType.AppleSonySS: case MediaType.AppleWidget: case MediaType.PriamDataTower: int sectorSize = 0; if(data.Length % 524 == 0) sectorSize = 524; else if(data.Length % 532 == 0) sectorSize = 532; else if(data.Length % 536 == 0) sectorSize = 536; if(sectorSize == 0) { ErrorMessage = "Incorrect data size"; return false; } sector = new byte[sectorSize]; for(uint i = 0; i < length; i++) { Array.Copy(data, sectorSize * i, sector, 0, sectorSize); if(!WriteSectorLong(sector, sectorAddress + i)) return false; } ErrorMessage = ""; return true; } break; } ErrorMessage = "Unknown long sector type, cannot write."; return false; } public bool SetTracks(List tracks) { if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) { ErrorMessage = "Unsupported feature"; return false; } 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); Crc64Context cmpCrc64Context = new Crc64Context(); byte[] lzmaProperties = new byte[0]; if(currentBlockHeader.compression == CompressionType.Flac) { long remaining = currentBlockOffset * SAMPLES_PER_SECTOR % flakeWriter.Settings.BlockSize; // Fill FLAC block if(remaining != 0) { AudioBuffer audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); flakeWriter.Write(audioBuffer); } // This trick because CUETools.Codecs.Flake closes the underlying stream long realLength = blockStream.Length; byte[] buffer = new byte[realLength]; flakeWriter.Close(); Array.Copy(blockStream.GetBuffer(), 0, buffer, 0, realLength); blockStream = new MemoryStream(buffer); } else if(currentBlockHeader.compression == CompressionType.Lzma) { lzmaProperties = lzmaBlockStream.Properties; lzmaBlockStream.Close(); lzmaBlockStream = null; cmpCrc64Context.Update(lzmaProperties); if(blockStream.Length > decompressedStream.Length) currentBlockHeader.compression = CompressionType.None; } if(currentBlockHeader.compression == CompressionType.None) { blockStream = decompressedStream; currentBlockHeader.cmpCrc64 = currentBlockHeader.crc64; } else { cmpCrc64Context.Update(blockStream.ToArray()); currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); } currentBlockHeader.cmpLength = (uint)blockStream.Length; if(currentBlockHeader.compression == CompressionType.Lzma) currentBlockHeader.cmpLength += LZMA_PROPERTIES_LENGTH; index.Add(new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.UserData, offset = (ulong)imageStream.Position }); 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; if(currentBlockHeader.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; } if(deduplicate) DicConsole.DebugWriteLine("DiscImageChef format plugin", "Of {0} sectors written, {1} are unique ({2:P})", writtenSectors, deduplicationTable.Count, (double)deduplicationTable.Count / writtenSectors); IndexEntry idxEntry; // Write media tag blocks 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(); byte[] lzmaProperties = CompressDataToStreamWithLZMA(mediaTag.Value, lzmaEncoderProperties, blockStream, false); 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 crc64Ctx = new Crc64Context(); crc64Ctx.Update(lzmaProperties); crc64Ctx.Update(tagData); tagCrc = crc64Ctx.Final(); tagBlock.cmpLength = (uint)tagData.Length + LZMA_PROPERTIES_LENGTH; tagBlock.cmpCrc64 = BitConverter.ToUInt64(tagCrc, 0); tagBlock.compression = CompressionType.Lzma; } blockStream.Close(); 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.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == dataType); index.Add(idxEntry); } // If we have set the geometry block, write it 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.RemoveAll(t => t.blockType == BlockType.GeometryBlock && t.dataType == DataType.NoData); index.Add(idxEntry); } // If we have dump hardware, write it if(DumpHardware != null) { MemoryStream dumpMs = new MemoryStream(); foreach(DumpHardwareType dump in DumpHardware) { byte[] dumpManufacturer = null; byte[] dumpModel = null; byte[] dumpRevision = null; byte[] dumpFirmware = null; byte[] dumpSerial = null; byte[] dumpSoftwareName = null; byte[] dumpSoftwareVersion = null; byte[] dumpSoftwareOperatingSystem = null; if(!string.IsNullOrWhiteSpace(dump.Manufacturer)) dumpManufacturer = Encoding.UTF8.GetBytes(dump.Manufacturer); if(!string.IsNullOrWhiteSpace(dump.Model)) dumpModel = Encoding.UTF8.GetBytes(dump.Model); if(!string.IsNullOrWhiteSpace(dump.Revision)) dumpRevision = Encoding.UTF8.GetBytes(dump.Revision); if(!string.IsNullOrWhiteSpace(dump.Firmware)) dumpFirmware = Encoding.UTF8.GetBytes(dump.Firmware); if(!string.IsNullOrWhiteSpace(dump.Serial)) dumpSerial = Encoding.UTF8.GetBytes(dump.Serial); if(dump.Software != null) { if(!string.IsNullOrWhiteSpace(dump.Software.Name)) dumpSoftwareName = Encoding.UTF8.GetBytes(dump.Software.Name); if(!string.IsNullOrWhiteSpace(dump.Software.Version)) dumpSoftwareVersion = Encoding.UTF8.GetBytes(dump.Software.Version); if(!string.IsNullOrWhiteSpace(dump.Software.OperatingSystem)) dumpSoftwareOperatingSystem = Encoding.UTF8.GetBytes(dump.Software.OperatingSystem); } DumpHardwareEntry dumpEntry = new DumpHardwareEntry { manufacturerLength = (uint)(dumpManufacturer?.Length + 1 ?? 0), modelLength = (uint)(dumpModel?.Length + 1 ?? 0), revisionLength = (uint)(dumpRevision?.Length + 1 ?? 0), firmwareLength = (uint)(dumpFirmware?.Length + 1 ?? 0), serialLength = (uint)(dumpSerial?.Length + 1 ?? 0), softwareNameLength = (uint)(dumpSoftwareName?.Length + 1 ?? 0), softwareVersionLength = (uint)(dumpSoftwareVersion?.Length + 1 ?? 0), softwareOperatingSystemLength = (uint)(dumpSoftwareOperatingSystem?.Length + 1 ?? 0), extents = (uint)dump.Extents.Length }; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpEntry)); structureBytes = new byte[Marshal.SizeOf(dumpEntry)]; Marshal.StructureToPtr(dumpEntry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); dumpMs.Write(structureBytes, 0, structureBytes.Length); if(dumpManufacturer != null) { dumpMs.Write(dumpManufacturer, 0, dumpManufacturer.Length); dumpMs.WriteByte(0); } if(dumpModel != null) { dumpMs.Write(dumpModel, 0, dumpModel.Length); dumpMs.WriteByte(0); } if(dumpRevision != null) { dumpMs.Write(dumpRevision, 0, dumpRevision.Length); dumpMs.WriteByte(0); } if(dumpFirmware != null) { dumpMs.Write(dumpFirmware, 0, dumpFirmware.Length); dumpMs.WriteByte(0); } if(dumpSerial != null) { dumpMs.Write(dumpSerial, 0, dumpSerial.Length); dumpMs.WriteByte(0); } if(dumpSoftwareName != null) { dumpMs.Write(dumpSoftwareName, 0, dumpSoftwareName.Length); dumpMs.WriteByte(0); } if(dumpSoftwareVersion != null) { dumpMs.Write(dumpSoftwareVersion, 0, dumpSoftwareVersion.Length); dumpMs.WriteByte(0); } if(dumpSoftwareOperatingSystem != null) { dumpMs.Write(dumpSoftwareOperatingSystem, 0, dumpSoftwareOperatingSystem.Length); dumpMs.WriteByte(0); } foreach(ExtentType extent in dump.Extents) { dumpMs.Write(BitConverter.GetBytes(extent.Start), 0, sizeof(ulong)); dumpMs.Write(BitConverter.GetBytes(extent.End), 0, sizeof(ulong)); } } idxEntry = new IndexEntry { blockType = BlockType.DumpHardwareBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing dump hardware block to position {0}", idxEntry.offset); Crc64Context.Data(dumpMs.ToArray(), out byte[] dumpCrc); DumpHardwareHeader dumpBlock = new DumpHardwareHeader { identifier = BlockType.DumpHardwareBlock, entries = (ushort)DumpHardware.Count, crc64 = BitConverter.ToUInt64(dumpCrc, 0), length = (uint)dumpMs.Length }; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(dumpBlock)); structureBytes = new byte[Marshal.SizeOf(dumpBlock)]; Marshal.StructureToPtr(dumpBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); imageStream.Write(dumpMs.ToArray(), 0, (int)dumpMs.Length); index.RemoveAll(t => t.blockType == BlockType.DumpHardwareBlock && t.dataType == DataType.NoData); index.Add(idxEntry); } // If we have CICM XML metadata, write it if(CicmMetadata != null) { MemoryStream cicmMs = new MemoryStream(); XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(cicmMs, CicmMetadata); idxEntry = new IndexEntry { blockType = BlockType.CicmBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CICM XML block to position {0}", idxEntry.offset); CicmMetadataBlock cicmBlock = new CicmMetadataBlock {identifier = BlockType.CicmBlock, length = (uint)cicmMs.Length}; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(cicmBlock)); structureBytes = new byte[Marshal.SizeOf(cicmBlock)]; Marshal.StructureToPtr(cicmBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); imageStream.Write(cicmMs.ToArray(), 0, (int)cicmMs.Length); index.RemoveAll(t => t.blockType == BlockType.CicmBlock && t.dataType == DataType.NoData); index.Add(idxEntry); } // If we have checksums, write it to disk if(md5Provider != null || sha1Provider != null || sha256Provider != null || spamsumProvider != null) { MemoryStream chkMs = new MemoryStream(); ChecksumHeader chkHeader = new ChecksumHeader {identifier = BlockType.ChecksumBlock}; if(md5Provider != null) { byte[] md5 = md5Provider.Final(); ChecksumEntry md5Entry = new ChecksumEntry {type = ChecksumAlgorithm.Md5, length = (uint)md5.Length}; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(md5Entry)); structureBytes = new byte[Marshal.SizeOf(md5Entry)]; Marshal.StructureToPtr(md5Entry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); chkMs.Write(structureBytes, 0, structureBytes.Length); chkMs.Write(md5, 0, md5.Length); chkHeader.entries++; } if(sha1Provider != null) { byte[] sha1 = sha1Provider.Final(); ChecksumEntry sha1Entry = new ChecksumEntry {type = ChecksumAlgorithm.Sha1, length = (uint)sha1.Length}; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha1Entry)); structureBytes = new byte[Marshal.SizeOf(sha1Entry)]; Marshal.StructureToPtr(sha1Entry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); chkMs.Write(structureBytes, 0, structureBytes.Length); chkMs.Write(sha1, 0, sha1.Length); chkHeader.entries++; } if(sha256Provider != null) { byte[] sha256 = sha256Provider.Final(); ChecksumEntry sha256Entry = new ChecksumEntry {type = ChecksumAlgorithm.Sha256, length = (uint)sha256.Length}; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(sha256Entry)); structureBytes = new byte[Marshal.SizeOf(sha256Entry)]; Marshal.StructureToPtr(sha256Entry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); chkMs.Write(structureBytes, 0, structureBytes.Length); chkMs.Write(sha256, 0, sha256.Length); chkHeader.entries++; } if(spamsumProvider != null) { byte[] spamsum = Encoding.ASCII.GetBytes(spamsumProvider.End()); ChecksumEntry spamsumEntry = new ChecksumEntry {type = ChecksumAlgorithm.SpamSum, length = (uint)spamsum.Length}; structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(spamsumEntry)); structureBytes = new byte[Marshal.SizeOf(spamsumEntry)]; Marshal.StructureToPtr(spamsumEntry, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); chkMs.Write(structureBytes, 0, structureBytes.Length); chkMs.Write(spamsum, 0, spamsum.Length); chkHeader.entries++; } if(chkHeader.entries > 0) { chkHeader.length = (uint)chkMs.Length; idxEntry = new IndexEntry { blockType = BlockType.ChecksumBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing checksum block to position {0}", idxEntry.offset); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(chkHeader)); structureBytes = new byte[Marshal.SizeOf(chkHeader)]; Marshal.StructureToPtr(chkHeader, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); imageStream.Write(chkMs.ToArray(), 0, (int)chkMs.Length); index.RemoveAll(t => t.blockType == BlockType.ChecksumBlock && t.dataType == DataType.NoData); index.Add(idxEntry); } } // If the DDT is in-memory, write it to disk 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(); MemoryStream userDataDdtStream = new MemoryStream(); crc64 = new Crc64Context(); for(ulong i = 0; i < (ulong)userDataDdt.LongLength; i++) { byte[] ddtEntry = BitConverter.GetBytes(userDataDdt[i]); crc64.Update(ddtEntry); userDataDdtStream.Write(ddtEntry, 0, ddtEntry.Length); } byte[] lzmaProperties = CompressDataToStreamWithLZMA(userDataDdtStream.ToArray(), lzmaEncoderProperties, blockStream, false); userDataDdtStream.Close(); ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; Crc64Context cmpCrc64Context = new Crc64Context(); cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 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.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DeDuplicationTable && t.dataType == DataType.UserData); index.Add(idxEntry); } // Write the sector prefix, suffix and subchannels if present switch(imageInfo.XmlMediaType) { case XmlMediaType.OpticalDisc when Tracks != null && Tracks.Count > 0: DateTime startCompress; DateTime endCompress; // Old format if(sectorPrefix != null && sectorSuffix != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorPrefix, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD sector prefix block to position {0}", idxEntry.offset); Crc64Context.Data(sectorPrefix, out byte[] blockCrc); BlockHeader prefixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorPrefix, length = (uint)sectorPrefix.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 16 }; byte[] lzmaProperties = null; if(nocompress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; blockStream = new MemoryStream(sectorPrefix); } else { startCompress = DateTime.Now; blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(sectorPrefix, lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress prefix", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; Marshal.StructureToPtr(prefixBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(prefixBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CdSectorPrefix); index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSuffix, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD sector suffix block to position {0}", idxEntry.offset); Crc64Context.Data(sectorSuffix, out blockCrc); prefixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorSuffix, length = (uint)sectorSuffix.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 288 }; if(nocompress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; blockStream = new MemoryStream(sectorSuffix); } else { startCompress = DateTime.Now; blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(sectorSuffix, lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress suffix", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; Marshal.StructureToPtr(prefixBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(prefixBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CdSectorSuffix); index.Add(idxEntry); blockStream.Close(); blockStream = null; } else if(sectorSuffixMs != null && sectorSuffixDdt != null && sectorPrefixMs != null && sectorPrefixDdt != null) { #if DEBUG uint notDumpedPrefixes = 0; uint correctPrefixes = 0; uint writtenPrefixes = 0; uint notDumpedSuffixes = 0; uint correctSuffixes = 0; uint writtenSuffixes = 0; uint correctMode2Form1 = 0; uint correctMode2Form2 = 0; uint emptyMode2Form1 = 0; for(long i = 0; i < sectorPrefixDdt.LongLength; i++) if((sectorPrefixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.NotDumped) notDumpedPrefixes++; else if((sectorPrefixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.Correct) correctPrefixes++; else if((sectorPrefixDdt[i] & CD_DFIX_MASK) > 0) writtenPrefixes++; for(long i = 0; i < sectorPrefixDdt.LongLength; i++) if((sectorSuffixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.NotDumped) notDumpedSuffixes++; else if((sectorSuffixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.Correct) correctSuffixes++; else if((sectorSuffixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.Mode2Form1Ok) correctMode2Form1++; else if((sectorSuffixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.Mode2Form2Ok) correctMode2Form2++; else if((sectorSuffixDdt[i] & CD_XFIX_MASK) == (uint)CdFixFlags.Mode2Form2NoCrc) emptyMode2Form1++; else if((sectorSuffixDdt[i] & CD_DFIX_MASK) > 0) writtenSuffixes++; DicConsole.DebugWriteLine("DiscImageChef format plugin", "{0} ({1:P}% prefixes are correct, {2} ({3:P}%) prefixes have not been dumped, {4} ({5:P}%) prefixes have been written to image", correctPrefixes, correctPrefixes / imageInfo.Sectors, notDumpedPrefixes, notDumpedPrefixes / imageInfo.Sectors, writtenPrefixes, writtenPrefixes / imageInfo.Sectors); DicConsole.DebugWriteLine("DiscImageChef format plugin", "{0} ({1:P}% suffixes are correct, {2} ({3:P}%) suffixes have not been dumped, {4} ({5:P}%) suffixes have been written to image", correctSuffixes, correctSuffixes / imageInfo.Sectors, notDumpedSuffixes, notDumpedSuffixes / imageInfo.Sectors, writtenSuffixes, writtenSuffixes / imageInfo.Sectors); DicConsole.DebugWriteLine("DiscImageChef format plugin", "{0} ({1:P}% MODE 2 Form 1 are correct, {2} ({3:P}%) MODE 2 Form 2 are correct, {4} ({5:P}%) MODE 2 Form 2 have empty CRC", correctMode2Form1, correctMode2Form1 / imageInfo.Sectors, correctMode2Form2, correctMode2Form2 / imageInfo.Sectors, emptyMode2Form1, emptyMode2Form1 / imageInfo.Sectors); #endif idxEntry = new IndexEntry { blockType = BlockType.DeDuplicationTable, dataType = DataType.CdSectorPrefixCorrected, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CompactDisc sector prefix DDT to position {0}", idxEntry.offset); DdtHeader ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.CdSectorPrefixCorrected, compression = CompressionType.Lzma, entries = (ulong)sectorPrefixDdt.LongLength, length = (ulong)(sectorPrefixDdt.LongLength * sizeof(uint)) }; blockStream = new MemoryStream(); MemoryStream sectorPrefixDdtStream = new MemoryStream(); crc64 = new Crc64Context(); for(ulong i = 0; i < (ulong)sectorPrefixDdt.LongLength; i++) { byte[] ddtEntry = BitConverter.GetBytes(sectorPrefixDdt[i]); crc64.Update(ddtEntry); sectorPrefixDdtStream.Write(ddtEntry, 0, ddtEntry.Length); } byte[] lzmaProperties = CompressDataToStreamWithLZMA(sectorPrefixDdtStream.ToArray(), lzmaEncoderProperties, blockStream, false); sectorPrefixDdtStream.Close(); ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; Crc64Context cmpCrc64Context = new Crc64Context(); cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 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.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DeDuplicationTable && t.dataType == DataType.CdSectorPrefixCorrected); index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DeDuplicationTable, dataType = DataType.CdSectorSuffixCorrected, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CompactDisc sector suffix DDT to position {0}", idxEntry.offset); ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.CdSectorSuffixCorrected, compression = CompressionType.Lzma, entries = (ulong)sectorSuffixDdt.LongLength, length = (ulong)(sectorSuffixDdt.LongLength * sizeof(uint)) }; blockStream = new MemoryStream(); MemoryStream sectorSuffixDdtStream = new MemoryStream(); crc64 = new Crc64Context(); for(ulong i = 0; i < (ulong)sectorSuffixDdt.LongLength; i++) { byte[] ddtEntry = BitConverter.GetBytes(sectorSuffixDdt[i]); crc64.Update(ddtEntry); sectorSuffixDdtStream.Write(ddtEntry, 0, ddtEntry.Length); } lzmaProperties = CompressDataToStreamWithLZMA(sectorSuffixDdtStream.ToArray(), lzmaEncoderProperties, blockStream, false); ddtHeader.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; cmpCrc64Context = new Crc64Context(); cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 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.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DeDuplicationTable && t.dataType == DataType.CdSectorSuffixCorrected); index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorPrefixCorrected, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD sector corrected prefix block to position {0}", idxEntry.offset); Crc64Context.Data(sectorPrefixMs.GetBuffer(), (uint)sectorPrefixMs.Length, out byte[] blockCrc); BlockHeader prefixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorPrefixCorrected, length = (uint)sectorPrefixMs.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 16 }; lzmaProperties = null; if(nocompress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; blockStream = sectorPrefixMs; } else { startCompress = DateTime.Now; blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(sectorPrefixMs.ToArray(), lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress prefix", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(prefixBlock)); structureBytes = new byte[Marshal.SizeOf(prefixBlock)]; Marshal.StructureToPtr(prefixBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(prefixBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CdSectorPrefixCorrected); index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSuffixCorrected, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD sector corrected suffix block to position {0}", idxEntry.offset); Crc64Context.Data(sectorSuffixMs.GetBuffer(), (uint)sectorSuffixMs.Length, out blockCrc); BlockHeader suffixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorSuffixCorrected, length = (uint)sectorSuffixMs.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 288 }; lzmaProperties = null; if(nocompress) { suffixBlock.compression = CompressionType.None; suffixBlock.cmpCrc64 = suffixBlock.crc64; suffixBlock.cmpLength = suffixBlock.length; blockStream = sectorSuffixMs; } else { startCompress = DateTime.Now; blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(sectorSuffixMs.ToArray(), lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); suffixBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; suffixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); suffixBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress suffix", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(suffixBlock)); structureBytes = new byte[Marshal.SizeOf(suffixBlock)]; Marshal.StructureToPtr(suffixBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(suffixBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CdSectorSuffixCorrected); index.Add(idxEntry); } if(mode2Subheaders != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CompactDiscMode2Subheader, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD MODE2 subheaders block to position {0}", idxEntry.offset); Crc64Context.Data(mode2Subheaders, out byte[] blockCrc); BlockHeader subheaderBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CompactDiscMode2Subheader, length = (uint)mode2Subheaders.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 8 }; byte[] lzmaProperties = null; if(nocompress) { subheaderBlock.compression = CompressionType.None; subheaderBlock.cmpCrc64 = subheaderBlock.crc64; subheaderBlock.cmpLength = subheaderBlock.length; blockStream = new MemoryStream(mode2Subheaders); } else { startCompress = DateTime.Now; blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(mode2Subheaders, lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subheaderBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; subheaderBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subheaderBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress MODE2 subheaders", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subheaderBlock)); structureBytes = new byte[Marshal.SizeOf(subheaderBlock)]; Marshal.StructureToPtr(subheaderBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(subheaderBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CompactDiscMode2Subheader); index.Add(idxEntry); blockStream.Close(); blockStream = null; } if(sectorSubchannel != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSubchannel, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing CD subchannel block to position {0}", idxEntry.offset); Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); BlockHeader subchannelBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorSubchannel, length = (uint)sectorSubchannel.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 96 }; byte[] lzmaProperties = null; if(nocompress) { subchannelBlock.compression = CompressionType.None; subchannelBlock.cmpCrc64 = subchannelBlock.crc64; subchannelBlock.cmpLength = subchannelBlock.length; blockStream = new MemoryStream(sectorSubchannel); } else { startCompress = DateTime.Now; byte[] transformedSubchannel = ClauniaSubchannelTransform(sectorSubchannel); blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(transformedSubchannel, lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subchannelBlock.compression = CompressionType.LzmaClauniaSubchannelTransform; endCompress = DateTime.Now; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Took {0} seconds to compress subchannel", (endCompress - startCompress).TotalSeconds); } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; Marshal.StructureToPtr(subchannelBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(subchannelBlock.compression == CompressionType.Lzma || subchannelBlock.compression == CompressionType.LzmaClauniaSubchannelTransform) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == DataType.CdSectorSubchannel); index.Add(idxEntry); blockStream.Close(); blockStream = null; } List trackEntries = new List(); foreach(Track track in Tracks) { trackFlags.TryGetValue((byte)track.TrackSequence, out byte flags); trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc); if((flags & (int)CdFlags.DataTrack) == 0 && track.TrackType != TrackType.Audio) flags += (byte)CdFlags.DataTrack; trackEntries.Add(new TrackEntry { sequence = (byte)track.TrackSequence, type = track.TrackType, start = (long)track.TrackStartSector, end = (long)track.TrackEndSector, pregap = (long)track.TrackPregap, session = (byte)track.TrackSession, isrc = isrc, flags = flags }); } // If there are tracks build the tracks block if(trackEntries.Count > 0) { blockStream = new MemoryStream(); foreach(TrackEntry entry in trackEntries) { 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[] trksCrc); TracksHeader trkHeader = new TracksHeader { identifier = BlockType.TracksBlock, entries = (ushort)trackEntries.Count, crc64 = BitConverter.ToUInt64(trksCrc, 0) }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tracks to position {0}", imageStream.Position); index.RemoveAll(t => t.blockType == BlockType.TracksBlock && t.dataType == DataType.NoData); index.Add(new IndexEntry { blockType = BlockType.TracksBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }); structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); structureBytes = new byte[Marshal.SizeOf(trkHeader)]; Marshal.StructureToPtr(trkHeader, 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); blockStream.Close(); blockStream = null; } break; case XmlMediaType.BlockMedia: if(sectorSubchannel != null && (imageInfo.MediaType == MediaType.AppleFileWare || imageInfo.MediaType == MediaType.AppleSonySS || imageInfo.MediaType == MediaType.AppleSonyDS || imageInfo.MediaType == MediaType.AppleProfile || imageInfo.MediaType == MediaType.AppleWidget || imageInfo.MediaType == MediaType.PriamDataTower)) { DataType tagType = DataType.NoData; switch(imageInfo.MediaType) { case MediaType.AppleSonySS: case MediaType.AppleSonyDS: tagType = DataType.AppleSonyTag; break; case MediaType.AppleFileWare: case MediaType.AppleProfile: case MediaType.AppleWidget: tagType = DataType.AppleProfileTag; break; case MediaType.PriamDataTower: tagType = DataType.PriamDataTowerTag; break; } idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = tagType, offset = (ulong)imageStream.Position }; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing apple sector tag block to position {0}", idxEntry.offset); Crc64Context.Data(sectorSubchannel, out byte[] blockCrc); BlockHeader subchannelBlock = new BlockHeader { identifier = BlockType.DataBlock, type = tagType, length = (uint)sectorSubchannel.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0) }; switch(imageInfo.MediaType) { case MediaType.AppleSonySS: case MediaType.AppleSonyDS: subchannelBlock.sectorSize = 12; break; case MediaType.AppleFileWare: case MediaType.AppleProfile: case MediaType.AppleWidget: subchannelBlock.sectorSize = 20; break; case MediaType.PriamDataTower: subchannelBlock.sectorSize = 24; break; } byte[] lzmaProperties = null; if(nocompress) { subchannelBlock.compression = CompressionType.None; subchannelBlock.cmpCrc64 = subchannelBlock.crc64; subchannelBlock.cmpLength = subchannelBlock.length; blockStream = new MemoryStream(sectorSubchannel); } else { blockStream = new MemoryStream(); lzmaProperties = CompressDataToStreamWithLZMA(sectorSubchannel, lzmaEncoderProperties, blockStream, false); Crc64Context cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subchannelBlock.cmpLength = (uint)blockStream.Length + LZMA_PROPERTIES_LENGTH; subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subchannelBlock.compression = CompressionType.Lzma; } structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(subchannelBlock)); structureBytes = new byte[Marshal.SizeOf(subchannelBlock)]; Marshal.StructureToPtr(subchannelBlock, structurePointer, true); Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); Marshal.FreeHGlobal(structurePointer); imageStream.Write(structureBytes, 0, structureBytes.Length); if(subchannelBlock.compression == CompressionType.Lzma) imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); index.RemoveAll(t => t.blockType == BlockType.DataBlock && t.dataType == tagType); index.Add(idxEntry); blockStream.Close(); blockStream = null; } break; } // Write metadata if present SetMetadataFromTags(); 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); } // Check if we set up any metadata earlier, then write its block 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.RemoveAll(t => t.blockType == BlockType.MetadataBlock && t.dataType == DataType.NoData); index.Add(new IndexEntry { blockType = BlockType.MetadataBlock, dataType = DataType.NoData, offset = (ulong)imageStream.Position }); imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); blockStream = null; } header.indexOffset = (ulong)imageStream.Position; DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing index to position {0}", header.indexOffset); blockStream = new MemoryStream(); // Write index to memory 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) }; // Write index to disk 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); blockStream.Close(); blockStream = null; 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(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); IsWriting = false; ErrorMessage = ""; return true; } 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; imageInfo.MediaTitle = metadata.MediaTitle; return true; } public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(imageInfo.XmlMediaType != XmlMediaType.BlockMedia) { ErrorMessage = "Tried to set geometry on a media that doesn't suppport it"; return false; } geometryBlock = new GeometryBlock { identifier = BlockType.GeometryBlock, cylinders = cylinders, heads = heads, sectorsPerTrack = sectorsPerTrack }; ErrorMessage = ""; return true; } public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(sectorAddress >= imageInfo.Sectors) { ErrorMessage = "Tried to write past image size"; return false; } Track track = new Track(); switch(tag) { case SectorTagType.CdTrackFlags: case SectorTagType.CdTrackIsrc: case SectorTagType.CdSectorSubchannel: if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) { ErrorMessage = "Incorrect tag for disk type"; return false; } track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && sectorAddress <= trk.TrackEndSector); if(track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0) { ErrorMessage = $"Can't found track containing {sectorAddress}"; return false; } break; } switch(tag) { case SectorTagType.CdTrackFlags: { if(data.Length != 1) { ErrorMessage = "Incorrect data size for track flags"; return false; } trackFlags.Add((byte)track.TrackSequence, data[0]); return true; } case SectorTagType.CdTrackIsrc: { if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); return true; } case SectorTagType.CdSectorSubchannel: { if(data.Length != 96) { ErrorMessage = "Incorrect data size for subchannel"; return false; } if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96); return true; } default: ErrorMessage = $"Don't know how to write sector tag type {tag}"; return false; } } public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) { if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; return false; } if(sectorAddress + length > imageInfo.Sectors) { ErrorMessage = "Tried to write past image size"; return false; } switch(tag) { case SectorTagType.CdTrackFlags: case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); case SectorTagType.CdSectorSubchannel: { if(data.Length % 96 != 0) { ErrorMessage = "Incorrect data size for subchannel"; return false; } if(sectorSubchannel == null) sectorSubchannel = new byte[imageInfo.Sectors * 96]; if(sectorAddress * 96 + length * 96 > (ulong)sectorSubchannel.LongLength) { ErrorMessage = "Tried to write more data than possible"; return false; } Array.Copy(data, 0, sectorSubchannel, (int)(96 * sectorAddress), 96 * length); return true; } default: ErrorMessage = $"Don't know how to write sector tag type {tag}"; return false; } } public bool SetDumpHardware(List dumpHardware) { DumpHardware = dumpHardware; return true; } public bool SetCicmMetadata(CICMMetadataType metadata) { CicmMetadata = metadata; return true; } /// /// This method exists to ensure the .NET memory allocator frees the LZ tree on each call /// /// Data to compress /// LZMA properties /// Stream where to write the compressed data to /// The properties as a byte array static byte[] CompressDataToStreamWithLZMA(byte[] data, LzmaEncoderProperties properties, Stream stream, bool foo) { byte[] propertiesArray; using(LzmaStream lzmaStream = new LzmaStream(properties, false, stream)) { lzmaStream.Write(data, 0, data.Length); propertiesArray = new byte[lzmaStream.Properties.Length]; lzmaStream.Properties.CopyTo(propertiesArray, 0); } return propertiesArray; } } }