// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Write.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Writes Aaru 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-2021 Natalia Portillo // Copyright © 2020-2021 Rebecca Wallander // ****************************************************************************/ 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 Aaru.Checksums; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Exceptions; using Aaru.CommonTypes.Structs; using Aaru.Console; using Aaru.Decoders; using Aaru.Helpers; using CUETools.Codecs; using CUETools.Codecs.Flake; using Schemas; using SharpCompress.Compressors.LZMA; using Marshal = Aaru.Helpers.Marshal; using TrackType = Aaru.CommonTypes.Enums.TrackType; namespace Aaru.DiscImages { public sealed partial class AaruFormat { 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 = true; if(options.TryGetValue("sha1", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha1)) { ErrorMessage = "Invalid value for sha1 option"; return false; } } else doSha1 = true; if(options.TryGetValue("sha256", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha256)) { ErrorMessage = "Invalid value for sha256 option"; return false; } } else doSha256 = true; 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("compress", out tmpValue)) { if(!bool.TryParse(tmpValue, out _compress)) { ErrorMessage = "Invalid value for compress option"; return false; } } else _compress = true; } else { sectorsPerBlock = 4096; dictionary = 1 << 25; maxDdtSize = 256; doMd5 = true; doSha1 = true; doSha256 = true; doSpamsum = false; _deduplicate = true; _compress = true; } // This really, cannot happen if(!SupportedMediaTypes.Contains(mediaType)) { ErrorMessage = $"Unsupported media format {mediaType}"; return false; } // Calculate shift _shift = 0; uint oldSectorsPerBlock = sectorsPerBlock; while(sectorsPerBlock > 1) { sectorsPerBlock >>= 1; _shift++; } AaruConsole.DebugWriteLine("Aaru 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()) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); _header = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); if(_header.identifier != DIC_MAGIC && _header.identifier != AARU_MAGIC) { ErrorMessage = "Cannot append to a non Aaru Format image"; return false; } if(_header.imageMajorVersion > AARUFMT_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 AaruHeader { identifier = AARU_MAGIC, mediaType = mediaType, creationTime = DateTime.UtcNow.ToFileTimeUtc() }; _imageStream.Write(new byte[Marshal.SizeOf()], 0, Marshal.SizeOf()); } _header.application = "Aaru"; _header.imageMajorVersion = AARUFMT_VERSION_V1; _header.imageMinorVersion = 0; _header.applicationMajorVersion = (byte)typeof(AaruFormat).Assembly.GetName().Version.Major; _header.applicationMinorVersion = (byte)typeof(AaruFormat).Assembly.GetName().Version.Minor; // Initialize tables _index = new List(); _mediaTags = new Dictionary(); _checksumProvider = SHA256.Create(); _deduplicationTable = new Dictionary(); _trackIsrcs = new Dictionary(); _trackFlags = new Dictionary(); _imageInfo.ReadableSectorTags = new List(); // If there exists an index, we are appending, so read index if(_header.indexOffset > 0) { List compactDiscIndexes = null; // Initialize caches _blockCache = new Dictionary(); _blockHeaderCache = new Dictionary(); _currentCacheSize = 0; // Can't calculate checksum of an appended image _md5Provider = null; _sha1Provider = null; _sha256Provider = null; _spamsumProvider = null; _imageStream.Position = (long)_header.indexOffset; _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); IndexHeader idxHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(idxHeader.identifier != BlockType.Index) { ErrorMessage = "Index not found in existing image, cannot continue"; return false; } AaruConsole.DebugWriteLine("Aaru Format plugin", "Index at {0} contains {1} entries", _header.indexOffset, idxHeader.entries); for(ushort i = 0; i < idxHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); IndexEntry entry = Marshal.SpanToStructureLittleEndian(_structureBytes); AaruConsole.DebugWriteLine("Aaru 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: // NOP block, skip if(entry.dataType == DataType.NoData) break; _imageStream.Position = (long)entry.offset; _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); BlockHeader blockHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); _imageInfo.ImageSize += blockHeader.cmpLength; // Unused, skip if(entry.dataType == DataType.UserData) break; if(blockHeader.identifier != entry.blockType) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect identifier for data block at position {0}", entry.offset); break; } if(blockHeader.type != entry.dataType) { AaruConsole.DebugWriteLine("Aaru 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Found data block type {0} at position {1}", entry.dataType, entry.offset); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); // Decompress media tag if(blockHeader.compression == CompressionType.Lzma || blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform) { if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform && entry.dataType != DataType.CdSectorSubchannel) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Invalid compression type {0} for block with data type {1}, continuing...", blockHeader.compression, entry.dataType); break; } DateTime startDecompress = DateTime.Now; 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); var compressedTagMs = new MemoryStream(compressedTag); var lzmaBlock = new LzmaStream(lzmaProperties, compressedTagMs); data = new byte[blockHeader.length]; lzmaBlock.Read(data, 0, (int)blockHeader.length); lzmaBlock.Close(); compressedTagMs.Close(); if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform) data = ClauniaSubchannelUntransform(data); DateTime endDecompress = DateTime.Now; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to decompress block", (endDecompress - startDecompress).TotalSeconds); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); } else if(blockHeader.compression == CompressionType.None) { data = new byte[blockHeader.length]; _imageStream.Read(data, 0, (int)blockHeader.length); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); } else { AaruConsole.DebugWriteLine("Aaru Format plugin", "Found unknown compression type {0}, continuing...", (ushort)blockHeader.compression); break; } // Check CRC, if not correct, skip it Crc64Context.Data(data, out byte[] blockCrc); if(BitConverter.ToUInt64(blockCrc, 0) != blockHeader.crc64) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", BitConverter.ToUInt64(blockCrc, 0), blockHeader.crc64); break; } // Check if it's not a media tag, but a sector tag, and fill the appropriate table then switch(entry.dataType) { case DataType.CdSectorPrefix: case DataType.CdSectorPrefixCorrected: if(entry.dataType == DataType.CdSectorPrefixCorrected) { _sectorPrefixMs = new NonClosableStream(); _sectorPrefixMs.Write(data, 0, data.Length); } else _sectorPrefix = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.CdSectorSuffix: case DataType.CdSectorSuffixCorrected: if(entry.dataType == DataType.CdSectorSuffixCorrected) { _sectorSuffixMs = new NonClosableStream(); _sectorSuffixMs.Write(data, 0, data.Length); } else _sectorSuffix = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubHeader); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEcc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEcc); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccP)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccP); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccQ)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccQ); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEdc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEdc); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.CdSectorSubchannel: _sectorSubchannel = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.AppleProfileTag: case DataType.AppleSonyTag: case DataType.PriamDataTowerTag: _sectorSubchannel = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.AppleSectorTag)) _imageInfo.ReadableSectorTags.Add(SectorTagType.AppleSectorTag); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.CompactDiscMode2Subheader: _mode2Subheaders = data; AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.DvdSectorCpiMai: _sectorCpiMai = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdCmi)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdCmi); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdTitleKey)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdTitleKey); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; case DataType.DvdSectorTitleKeyDecrypted: _sectorDecryptedTitleKey = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdTitleKeyDecrypted)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdTitleKeyDecrypted); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; default: MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); if(_mediaTags.ContainsKey(mediaTagType)) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Media tag type {0} duplicated, removing previous entry...", mediaTagType); _mediaTags.Remove(mediaTagType); } _mediaTags.Add(mediaTagType, data); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; } break; case BlockType.DeDuplicationTable: // Only user data deduplication tables are used right now if(entry.dataType == DataType.UserData) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); DdtHeader ddtHeader = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; if(ddtHeader.entries != _imageInfo.Sectors && !IsTape) { 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: AaruConsole.DebugWriteLine("Aaru 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); var compressedDdtMs = new MemoryStream(compressedDdt); var lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); byte[] decompressedDdt = new byte[ddtHeader.length]; lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); lzmaDdt.Close(); compressedDdtMs.Close(); _userDataDdt = MemoryMarshal.Cast(decompressedDdt).ToArray(); DateTime ddtEnd = DateTime.UtcNow; _inMemoryDdt = true; AaruConsole.DebugWriteLine("Aaru 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}"); } if(IsTape) { _tapeDdt = new Dictionary(); for(long i = 0; i < _userDataDdt.LongLength; i++) _tapeDdt.Add((ulong)i, _userDataDdt[i]); _userDataDdt = null; } foundUserDataDdt = true; } else if(entry.dataType == DataType.CdSectorPrefixCorrected || entry.dataType == DataType.CdSectorSuffixCorrected) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); DdtHeader ddtHeader = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); 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]; switch(ddtHeader.compression) { case CompressionType.Lzma: AaruConsole.DebugWriteLine("Aaru 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); var compressedDdtMs = new MemoryStream(compressedDdt); var lzmaDdt = new LzmaStream(lzmaProperties, compressedDdtMs); lzmaDdt.Read(decompressedDdt, 0, (int)ddtHeader.length); lzmaDdt.Close(); compressedDdtMs.Close(); DateTime ddtEnd = DateTime.UtcNow; AaruConsole.DebugWriteLine("Aaru 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}"); } uint[] cdDdt = MemoryMarshal.Cast(decompressedDdt).ToArray(); switch(entry.dataType) { case DataType.CdSectorPrefixCorrected: _sectorPrefixDdt = cdDdt; break; case DataType.CdSectorSuffixCorrected: _sectorSuffixDdt = cdDdt; break; } } break; // Logical geometry block. It doesn't have a CRC coz, well, it's not so important case BlockType.GeometryBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); _geometryBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(_geometryBlock.identifier == BlockType.GeometryBlock) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Geometry set to {0} cylinders {1} heads {2} sectors per track", _geometryBlock.cylinders, _geometryBlock.heads, _geometryBlock.sectorsPerTrack); _imageInfo.Cylinders = _geometryBlock.cylinders; _imageInfo.Heads = _geometryBlock.heads; _imageInfo.SectorsPerTrack = _geometryBlock.sectorsPerTrack; AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); } break; // Metadata block case BlockType.MetadataBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); MetadataBlock metadataBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(metadataBlock.identifier != entry.blockType) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect identifier for data block at position {0}", entry.offset); break; } AaruConsole.DebugWriteLine("Aaru Format plugin", "Found metadata block at position {0}", entry.offset); byte[] metadata = new byte[metadataBlock.blockSize]; _imageStream.Position = (long)entry.offset; _imageStream.Read(metadata, 0, metadata.Length); if(metadataBlock.mediaSequence > 0 && metadataBlock.lastMediaSequence > 0) { _imageInfo.MediaSequence = metadataBlock.mediaSequence; _imageInfo.LastMediaSequence = metadataBlock.lastMediaSequence; AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media sequence as {0} of {1}", _imageInfo.MediaSequence, _imageInfo.LastMediaSequence); } if(metadataBlock.creatorLength > 0 && metadataBlock.creatorLength + metadataBlock.creatorOffset <= metadata.Length) { _imageInfo.Creator = Encoding.Unicode.GetString(metadata, (int)metadataBlock.creatorOffset, (int)(metadataBlock.creatorLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting creator: {0}", _imageInfo.Creator); } if(metadataBlock.commentsOffset > 0 && metadataBlock.commentsLength + metadataBlock.commentsOffset <= metadata.Length) { _imageInfo.Comments = Encoding.Unicode.GetString(metadata, (int)metadataBlock.commentsOffset, (int)(metadataBlock.commentsLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting comments: {0}", _imageInfo.Comments); } if(metadataBlock.mediaTitleOffset > 0 && metadataBlock.mediaTitleLength + metadataBlock.mediaTitleOffset <= metadata.Length) { _imageInfo.MediaTitle = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaTitleOffset, (int)(metadataBlock.mediaTitleLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media title: {0}", _imageInfo.MediaTitle); } if(metadataBlock.mediaManufacturerOffset > 0 && metadataBlock.mediaManufacturerLength + metadataBlock.mediaManufacturerOffset <= metadata.Length) { _imageInfo.MediaManufacturer = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaManufacturerOffset, (int)(metadataBlock.mediaManufacturerLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media manufacturer: {0}", _imageInfo.MediaManufacturer); } if(metadataBlock.mediaModelOffset > 0 && metadataBlock.mediaModelLength + metadataBlock.mediaModelOffset <= metadata.Length) { _imageInfo.MediaModel = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaModelOffset, (int)(metadataBlock.mediaModelLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media model: {0}", _imageInfo.MediaModel); } if(metadataBlock.mediaSerialNumberOffset > 0 && metadataBlock.mediaSerialNumberLength + metadataBlock.mediaSerialNumberOffset <= metadata.Length) { _imageInfo.MediaSerialNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaSerialNumberOffset, (int)(metadataBlock.mediaSerialNumberLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media serial number: {0}", _imageInfo.MediaSerialNumber); } if(metadataBlock.mediaBarcodeOffset > 0 && metadataBlock.mediaBarcodeLength + metadataBlock.mediaBarcodeOffset <= metadata.Length) { _imageInfo.MediaBarcode = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaBarcodeOffset, (int)(metadataBlock.mediaBarcodeLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media barcode: {0}", _imageInfo.MediaBarcode); } if(metadataBlock.mediaPartNumberOffset > 0 && metadataBlock.mediaPartNumberLength + metadataBlock.mediaPartNumberOffset <= metadata.Length) { _imageInfo.MediaPartNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.mediaPartNumberOffset, (int)(metadataBlock.mediaPartNumberLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting media part number: {0}", _imageInfo.MediaPartNumber); } if(metadataBlock.driveManufacturerOffset > 0 && metadataBlock.driveManufacturerLength + metadataBlock.driveManufacturerOffset <= metadata.Length) { _imageInfo.DriveManufacturer = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveManufacturerOffset, (int)(metadataBlock.driveManufacturerLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting drive manufacturer: {0}", _imageInfo.DriveManufacturer); } if(metadataBlock.driveModelOffset > 0 && metadataBlock.driveModelLength + metadataBlock.driveModelOffset <= metadata.Length) { _imageInfo.DriveModel = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveModelOffset, (int)(metadataBlock.driveModelLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting drive model: {0}", _imageInfo.DriveModel); } if(metadataBlock.driveSerialNumberOffset > 0 && metadataBlock.driveSerialNumberLength + metadataBlock.driveSerialNumberOffset <= metadata.Length) { _imageInfo.DriveSerialNumber = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveSerialNumberOffset, (int)(metadataBlock.driveSerialNumberLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting drive serial number: {0}", _imageInfo.DriveSerialNumber); } if(metadataBlock.driveFirmwareRevisionOffset > 0 && metadataBlock.driveFirmwareRevisionLength + metadataBlock.driveFirmwareRevisionOffset <= metadata.Length) { _imageInfo.DriveFirmwareRevision = Encoding.Unicode.GetString(metadata, (int)metadataBlock.driveFirmwareRevisionOffset, (int)(metadataBlock.driveFirmwareRevisionLength - 2)); AaruConsole.DebugWriteLine("Aaru Format plugin", "Setting drive firmware revision: {0}", _imageInfo.DriveFirmwareRevision); } AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; // Optical disc tracks block case BlockType.TracksBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); TracksHeader tracksHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(tracksHeader.identifier != BlockType.TracksBlock) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect identifier for tracks block at position {0}", entry.offset); break; } _structureBytes = new byte[Marshal.SizeOf() * tracksHeader.entries]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); Crc64Context.Data(_structureBytes, out byte[] trksCrc); if(BitConverter.ToUInt64(trksCrc, 0) != tracksHeader.crc64) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", BitConverter.ToUInt64(trksCrc, 0), tracksHeader.crc64); break; } _imageStream.Position -= _structureBytes.Length; Tracks = new List(); _trackFlags = new Dictionary(); _trackIsrcs = new Dictionary(); AaruConsole.DebugWriteLine("Aaru Format plugin", "Found {0} tracks at position {0}", tracksHeader.entries, entry.offset); for(ushort i = 0; i < tracksHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); TrackEntry trackEntry = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); Tracks.Add(new Track { TrackSequence = trackEntry.sequence, TrackType = trackEntry.type, TrackStartSector = (ulong)trackEntry.start, TrackEndSector = (ulong)trackEntry.end, TrackPregap = (ulong)trackEntry.pregap, TrackSession = trackEntry.session, TrackFileType = "BINARY" }); if(trackEntry.type == TrackType.Data) continue; _trackFlags.Add(trackEntry.sequence, trackEntry.flags); if(!string.IsNullOrEmpty(trackEntry.isrc)) _trackIsrcs.Add(trackEntry.sequence, trackEntry.isrc); } if(_trackFlags.Count > 0 && !_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdTrackFlags)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdTrackFlags); if(_trackIsrcs.Count > 0 && !_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdTrackIsrc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdTrackIsrc); _imageInfo.HasPartitions = true; _imageInfo.HasSessions = true; AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); break; // CICM XML metadata block case BlockType.CicmBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); CicmMetadataBlock cicmBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(cicmBlock.identifier != BlockType.CicmBlock) break; AaruConsole.DebugWriteLine("Aaru Format plugin", "Found CICM XML metadata block at position {0}", entry.offset); byte[] cicmBytes = new byte[cicmBlock.length]; _imageStream.Read(cicmBytes, 0, cicmBytes.Length); var cicmMs = new MemoryStream(cicmBytes); var cicmXs = new XmlSerializer(typeof(CICMMetadataType)); try { var sr = new StreamReader(cicmMs); CicmMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); sr.Close(); } catch(XmlException ex) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Exception {0} processing CICM XML metadata block", ex.Message); CicmMetadata = null; } break; // Dump hardware block case BlockType.DumpHardwareBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); DumpHardwareHeader dumpBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; AaruConsole.DebugWriteLine("Aaru 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) { AaruConsole.DebugWriteLine("Aaru 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++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); DumpHardwareEntry dumpEntry = Marshal.SpanToStructureLittleEndian(_structureBytes); var 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; // Tape partition block case BlockType.TapePartitionBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); TapePartitionHeader partitionHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(partitionHeader.identifier != BlockType.TapePartitionBlock) break; AaruConsole.DebugWriteLine("Aaru Format plugin", "Found tape partition block at position {0}", entry.offset); byte[] tapePartitionBytes = new byte[partitionHeader.length]; _imageStream.Read(tapePartitionBytes, 0, tapePartitionBytes.Length); Span tapePartitions = MemoryMarshal.Cast(tapePartitionBytes); TapePartitions = new List(); foreach(TapePartitionEntry tapePartition in tapePartitions) TapePartitions.Add(new TapePartition { FirstBlock = tapePartition.FirstBlock, LastBlock = tapePartition.LastBlock, Number = tapePartition.Number }); IsTape = true; break; // Tape file block case BlockType.TapeFileBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); TapeFileHeader fileHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(fileHeader.identifier != BlockType.TapeFileBlock) break; AaruConsole.DebugWriteLine("Aaru Format plugin", "Found tape file block at position {0}", entry.offset); byte[] tapeFileBytes = new byte[fileHeader.length]; _imageStream.Read(tapeFileBytes, 0, tapeFileBytes.Length); Span tapeFiles = MemoryMarshal.Cast(tapeFileBytes); Files = new List(); foreach(TapeFileEntry file in tapeFiles) Files.Add(new TapeFile { FirstBlock = file.FirstBlock, LastBlock = file.LastBlock, Partition = file.Partition, File = file.File }); IsTape = true; break; // Optical disc tracks block case BlockType.CompactDiscIndexesBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); CompactDiscIndexesHeader indexesHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(indexesHeader.identifier != BlockType.CompactDiscIndexesBlock) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect identifier for compact disc indexes block at position {0}", entry.offset); break; } _structureBytes = new byte[Marshal.SizeOf() * indexesHeader.entries]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); Crc64Context.Data(_structureBytes, out byte[] idsxCrc); if(BitConverter.ToUInt64(idsxCrc, 0) != indexesHeader.crc64) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Incorrect CRC found: 0x{0:X16} found, expected 0x{1:X16}, continuing...", BitConverter.ToUInt64(idsxCrc, 0), indexesHeader.crc64); break; } _imageStream.Position -= _structureBytes.Length; compactDiscIndexes = new List(); AaruConsole.DebugWriteLine("Aaru Format plugin", "Found {0} compact disc indexes at position {0}", indexesHeader.entries, entry.offset); for(ushort i = 0; i < indexesHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.Read(_structureBytes, 0, _structureBytes.Length); compactDiscIndexes.Add(Marshal. ByteArrayToStructureLittleEndian(_structureBytes)); } AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); 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; } if(!_inMemoryDdt) _ddtEntryCache = new Dictionary(); // Initialize tracks, sessions and partitions if(_imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); if(Tracks == null || Tracks.Count == 0) { Tracks = new List { new Track { TrackBytesPerSector = (int)_imageInfo.SectorSize, TrackEndSector = _imageInfo.Sectors - 1, TrackFileType = "BINARY", TrackRawBytesPerSector = (int)_imageInfo.SectorSize, TrackSession = 1, TrackSequence = 1, TrackType = TrackType.Data } }; _trackFlags = new Dictionary { { 1, (byte)CdFlags.DataTrack } }; _trackIsrcs = new Dictionary(); } AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); Sessions = new List(); for(int i = 1; i <= Tracks.Max(t => t.TrackSession); i++) Sessions.Add(new Session { SessionSequence = (ushort)i, StartTrack = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackSequence), EndTrack = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackSequence), StartSector = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackStartSector), EndSector = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackEndSector) }); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); foreach(Track track in Tracks.OrderBy(t => t.TrackStartSector)) { if(track.TrackSequence == 1) { track.TrackPregap = 150; track.Indexes[0] = -150; track.Indexes[1] = (int)track.TrackStartSector; continue; } if(track.TrackPregap > 0) { track.Indexes[0] = (int)track.TrackStartSector; track.Indexes[1] = (int)(track.TrackStartSector + track.TrackPregap); } else track.Indexes[1] = (int)track.TrackStartSector; } ulong currentTrackOffset = 0; Partitions = new List(); foreach(Track track in Tracks.OrderBy(t => t.TrackStartSector)) { Partitions.Add(new Partition { Sequence = track.TrackSequence, Type = track.TrackType.ToString(), Name = $"Track {track.TrackSequence}", Offset = currentTrackOffset, Start = (ulong)track.Indexes[1], Size = ((track.TrackEndSector - (ulong)track.Indexes[1]) + 1) * (ulong)track.TrackBytesPerSector, Length = (track.TrackEndSector - (ulong)track.Indexes[1]) + 1, Scheme = "Optical disc track" }); currentTrackOffset += ((track.TrackEndSector - track.TrackStartSector) + 1) * (ulong)track.TrackBytesPerSector; } AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); Track[] tracks = Tracks.ToArray(); foreach(Track trk in tracks) { byte[] sector = ReadSector(trk.TrackStartSector); trk.TrackBytesPerSector = sector.Length; trk.TrackRawBytesPerSector = (_sectorPrefix != null && _sectorSuffix != null) || (_sectorPrefixDdt != null && _sectorSuffixDdt != null) ? 2352 : sector.Length; if(_sectorSubchannel == null) continue; trk.TrackSubchannelFile = trk.TrackFile; trk.TrackSubchannelFilter = trk.TrackFilter; trk.TrackSubchannelType = TrackSubchannelType.Raw; } AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); Tracks = tracks.ToList(); AaruConsole.DebugWriteLine("Aaru Format plugin", "Memory snapshot: {0} bytes", GC.GetTotalMemory(false)); if(compactDiscIndexes != null) { foreach(CompactDiscIndexEntry compactDiscIndex in compactDiscIndexes.OrderBy(i => i.Track). ThenBy(i => i.Index)) { Track track = Tracks.FirstOrDefault(t => t.TrackSequence == compactDiscIndex.Track); if(track is null) continue; track.Indexes[compactDiscIndex.Index] = compactDiscIndex.Lba; } } } else { Tracks = null; Sessions = null; Partitions = 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) { if(IsTape) _tapeDdt = new Dictionary(); else _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 var ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.UserData, compression = CompressionType.None, shift = _shift, entries = sectors, cmpLength = sectors * sizeof(ulong), length = sectors * sizeof(ulong) }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref ddtHeader); _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(); } AaruConsole.DebugWriteLine("Aaru Format plugin", "In memory DDT?: {0}", _inMemoryDdt); _imageStream.Seek(0, SeekOrigin.End); // Initialize compressors properties (all maxed) _lzmaEncoderProperties = new LzmaEncoderProperties(true, (int)dictionary, 273); _flakeWriterSettings = new EncoderSettings { 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; AudioEncoder.Vendor = "Aaru"; 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 && !IsTape) { 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) { var 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; } } var 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 is null) trk.TrackType = TrackType.Data; // TODO: Check intersession data type if(trk.TrackSequence == 0 && trk.TrackStartSector == 0 && trk.TrackEndSector == 0) trk.TrackType = TrackType.Data; // TODO: Check intersession data type } // 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); var cmpCrc64Context = new Crc64Context(); byte[] lzmaProperties = new byte[0]; switch(_currentBlockHeader.compression) { case CompressionType.Flac: { long remaining = (_currentBlockOffset * SAMPLES_PER_SECTOR) % _flakeWriter.Settings.BlockSize; // Fill FLAC block if(remaining != 0) { var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); _flakeWriter.Write(audioBuffer); } _flakeWriter.Close(); break; } case CompressionType.Lzma: { lzmaProperties = _lzmaBlockStream.Properties; _lzmaBlockStream.Close(); _lzmaBlockStream = null; cmpCrc64Context.Update(lzmaProperties); if(_blockStream.Length > _decompressedStream.Length) _currentBlockHeader.compression = CompressionType.None; break; } } 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 }); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref _currentBlockHeader); _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 = _compress ? CompressionType.Lzma : CompressionType.None, sectorSize = (uint)data.Length }; if(_imageInfo.XmlMediaType == XmlMediaType.OpticalDisc && trk.TrackType == TrackType.Audio && _compress) _currentBlockHeader.compression = CompressionType.Flac; // JaguarCD stores data in audio tracks. FLAC is too inefficient, use LZMA there. // VideoNow stores video in audio tracks, and LZMA works better too. if(((_imageInfo.MediaType == MediaType.JaguarCD && trk.TrackSession > 1) || _imageInfo.MediaType == MediaType.VideoNow || _imageInfo.MediaType == MediaType.VideoNowColor || _imageInfo.MediaType == MediaType.VideoNowXp) && trk.TrackType == TrackType.Audio && _compress && _currentBlockHeader.compression == CompressionType.Flac) _currentBlockHeader.compression = CompressionType.Lzma; _blockStream = new NonClosableStream(); _decompressedStream = new NonClosableStream(); switch(_currentBlockHeader.compression) { case CompressionType.Flac: _flakeWriter = new AudioEncoder(_flakeWriterSettings, "", _blockStream) { DoSeekTable = false }; break; case CompressionType.Lzma: _lzmaBlockStream = new LzmaStream(_lzmaEncoderProperties, false, _blockStream); break; default: _lzmaBlockStream = null; break; } _crc64 = new Crc64Context(); } ulong ddtEntry = (ulong)((_imageStream.Position << _shift) + _currentBlockOffset); if(hash != null) _deduplicationTable.Add(hashString, ddtEntry); if(_currentBlockHeader.compression == CompressionType.Flac) { var 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 is null) { ErrorMessage = "Track not found"; return false; } if(track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0) track.TrackType = TrackType.Data; 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); } _sectorSuffixMs ??= new NonClosableStream(); _sectorPrefixMs ??= new NonClosableStream(); if(_sectorSuffixDdt == null) { _sectorSuffixDdt = new uint[_imageInfo.Sectors]; EccInit(); } _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); } _sectorSuffixMs ??= new NonClosableStream(); _sectorPrefixMs ??= new NonClosableStream(); if(_sectorSuffixDdt == null) { _sectorSuffixDdt = new uint[_imageInfo.Sectors]; EccInit(); } _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); } _mode2Subheaders ??= new byte[_imageInfo.Sectors * 8]; bool form2 = (data[18] & 0x20) == 0x20 || (data[22] & 0x20) == 0x20; if(form2) { uint computedEdc = ComputeEdc(0, data, 0x91C, 0x10); uint edc = BitConverter.ToUInt32(data, 0x92C); bool correctEdc = computedEdc == edc; sector = new byte[2324]; _sectorSuffixDdt ??= new uint[_imageInfo.Sectors]; Array.Copy(data, 24, sector, 0, 2324); if(correctEdc) { _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2Ok; } else if(BitConverter.ToUInt32(data, 0x92C) == 0) { _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2NoCrc; } 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, 2348, 4); } } else { bool correctEcc = SuffixIsCorrectMode2(data); uint computedEdc = ComputeEdc(0, data, 0x808, 0x10); uint edc = BitConverter.ToUInt32(data, 0x818); bool correctEdc = computedEdc == edc; sector = new byte[2048]; Array.Copy(data, 24, sector, 0, 2048); if(correctEcc && correctEdc) { _sectorSuffixDdt ??= new uint[_imageInfo.Sectors]; _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form1Ok; } 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, 2072, 280); } } 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); _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: if(data.Length % 2352 != 0) { ErrorMessage = "Incorrect data size"; 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); var 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) { var audioBuffer = new AudioBuffer(AudioPCMConfig.RedBook, new byte[remaining * 4], (int)remaining); _flakeWriter.Write(audioBuffer); } _flakeWriter.Close(); } 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 }); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref _currentBlockHeader); _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.ReallyClose(); _blockStream = null; } if(_deduplicate) AaruConsole.DebugWriteLine("Aaru 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); if(mediaTag.Value is null) { AaruConsole.ErrorWriteLine("Tag type {0} is null, skipping...", dataType); continue; } idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = dataType, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing tag type {0} to position {1}", mediaTag.Key, idxEntry.offset); Crc64Context.Data(mediaTag.Value, out byte[] tagCrc); var tagBlock = new BlockHeader { identifier = BlockType.DataBlock, type = dataType, length = (uint)mediaTag.Value.Length, crc64 = BitConverter.ToUInt64(tagCrc, 0) }; _blockStream = new NonClosableStream(); byte[] lzmaProperties = CompressDataToStreamWithLZMA(mediaTag.Value, _lzmaEncoderProperties, _blockStream); 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(); var 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.ReallyClose(); _blockStream = null; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref tagBlock); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing geometry block to position {0}", idxEntry.offset); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref _geometryBlock); _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) { var 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(!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); var 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 }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref dumpEntry); 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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing dump hardware block to position {0}", idxEntry.offset); Crc64Context.Data(dumpMs.ToArray(), out byte[] dumpCrc); var dumpBlock = new DumpHardwareHeader { identifier = BlockType.DumpHardwareBlock, entries = (ushort)DumpHardware.Count, crc64 = BitConverter.ToUInt64(dumpCrc, 0), length = (uint)dumpMs.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref dumpBlock); _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) { var cicmMs = new MemoryStream(); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(cicmMs, CicmMetadata); idxEntry = new IndexEntry { blockType = BlockType.CicmBlock, dataType = DataType.NoData, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CICM XML block to position {0}", idxEntry.offset); var cicmBlock = new CicmMetadataBlock { identifier = BlockType.CicmBlock, length = (uint)cicmMs.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref cicmBlock); _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) { var chkMs = new MemoryStream(); var chkHeader = new ChecksumHeader { identifier = BlockType.ChecksumBlock }; if(_md5Provider != null) { byte[] md5 = _md5Provider.Final(); var md5Entry = new ChecksumEntry { type = ChecksumAlgorithm.Md5, length = (uint)md5.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref md5Entry); chkMs.Write(_structureBytes, 0, _structureBytes.Length); chkMs.Write(md5, 0, md5.Length); chkHeader.entries++; } if(_sha1Provider != null) { byte[] sha1 = _sha1Provider.Final(); var sha1Entry = new ChecksumEntry { type = ChecksumAlgorithm.Sha1, length = (uint)sha1.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref sha1Entry); chkMs.Write(_structureBytes, 0, _structureBytes.Length); chkMs.Write(sha1, 0, sha1.Length); chkHeader.entries++; } if(_sha256Provider != null) { byte[] sha256 = _sha256Provider.Final(); var sha256Entry = new ChecksumEntry { type = ChecksumAlgorithm.Sha256, length = (uint)sha256.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref sha256Entry); chkMs.Write(_structureBytes, 0, _structureBytes.Length); chkMs.Write(sha256, 0, sha256.Length); chkHeader.entries++; } if(_spamsumProvider != null) { byte[] spamsum = Encoding.ASCII.GetBytes(_spamsumProvider.End()); var spamsumEntry = new ChecksumEntry { type = ChecksumAlgorithm.SpamSum, length = (uint)spamsum.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref spamsumEntry); 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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing checksum block to position {0}", idxEntry.offset); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref chkHeader); _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(IsTape) { ulong latestBlock = _tapeDdt.Max(b => b.Key); _userDataDdt = new ulong[latestBlock + 1]; foreach(KeyValuePair block in _tapeDdt) _userDataDdt[block.Key] = block.Value; _inMemoryDdt = true; _tapeDdt.Clear(); idxEntry = new IndexEntry { blockType = BlockType.TapePartitionBlock, dataType = DataType.UserData, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing tape partitions to position {0}", idxEntry.offset); TapePartitionEntry[] tapePartitionEntries = new TapePartitionEntry[TapePartitions.Count]; for(int t = 0; t < TapePartitions.Count; t++) { tapePartitionEntries[t] = new TapePartitionEntry { Number = TapePartitions[t].Number, FirstBlock = TapePartitions[t].FirstBlock, LastBlock = TapePartitions[t].LastBlock }; } byte[] tapePartitionEntriesData = MemoryMarshal.Cast(tapePartitionEntries).ToArray(); var tapePartitionHeader = new TapePartitionHeader { identifier = BlockType.TapePartitionBlock, entries = (byte)tapePartitionEntries.Length, length = (ulong)tapePartitionEntriesData.Length }; _crc64 = new Crc64Context(); _crc64.Update(tapePartitionEntriesData); tapePartitionHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref tapePartitionHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(tapePartitionEntriesData, 0, tapePartitionEntriesData.Length); _index.RemoveAll(t => t.blockType == BlockType.TapePartitionBlock && t.dataType == DataType.UserData); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.TapeFileBlock, dataType = DataType.UserData, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing tape files to position {0}", idxEntry.offset); TapeFileEntry[] tapeFileEntries = new TapeFileEntry[Files.Count]; for(int t = 0; t < Files.Count; t++) tapeFileEntries[t] = new TapeFileEntry { File = Files[t].File, FirstBlock = Files[t].FirstBlock, LastBlock = Files[t].LastBlock }; byte[] tapeFileEntriesData = MemoryMarshal.Cast(tapeFileEntries).ToArray(); var tapeFileHeader = new TapeFileHeader { identifier = BlockType.TapeFileBlock, entries = (uint)tapeFileEntries.Length, length = (ulong)tapeFileEntriesData.Length }; _crc64 = new Crc64Context(); _crc64.Update(tapeFileEntriesData); tapeFileHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref tapeFileHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(tapeFileEntriesData, 0, tapeFileEntriesData.Length); _index.RemoveAll(t => t.blockType == BlockType.TapeFileBlock && t.dataType == DataType.UserData); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing user data DDT to position {0}", idxEntry.offset); var 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 NonClosableStream(); var userDataDdtStream = new MemoryStream(); _crc64 = new Crc64Context(); byte[] ddtEntries = MemoryMarshal.Cast(_userDataDdt).ToArray(); _crc64.Update(ddtEntries); userDataDdtStream.Write(ddtEntries, 0, ddtEntries.Length); byte[] lzmaProperties = CompressDataToStreamWithLZMA(userDataDdtStream.ToArray(), _lzmaEncoderProperties, _blockStream); userDataDdtStream.Close(); ddtHeader.cmpLength = (uint)_blockStream.Length + LZMA_PROPERTIES_LENGTH; var cmpCrc64Context = new Crc64Context(); cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(_blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CD sector prefix block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorPrefix, out byte[] blockCrc); var prefixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorPrefix, length = (uint)_sectorPrefix.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 16 }; byte[] lzmaProperties = null; if(!_compress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; _blockStream = new NonClosableStream(_sectorPrefix); } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorPrefix, _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress prefix", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref prefixBlock); _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.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru 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(!_compress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; _blockStream = new NonClosableStream(_sectorSuffix); } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorSuffix, _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress suffix", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref prefixBlock); _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.ReallyClose(); _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++; AaruConsole.DebugWriteLine("Aaru 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); AaruConsole.DebugWriteLine("Aaru 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); AaruConsole.DebugWriteLine("Aaru 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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CompactDisc sector prefix DDT to position {0}", idxEntry.offset); var ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.CdSectorPrefixCorrected, compression = CompressionType.Lzma, entries = (ulong)_sectorPrefixDdt.LongLength, length = (ulong)(_sectorPrefixDdt.LongLength * sizeof(uint)) }; _blockStream = new NonClosableStream(); var sectorPrefixDdtStream = new MemoryStream(); _crc64 = new Crc64Context(); byte[] ddtEntries = MemoryMarshal.Cast(_sectorPrefixDdt).ToArray(); _crc64.Update(ddtEntries); sectorPrefixDdtStream.Write(ddtEntries, 0, ddtEntries.Length); byte[] lzmaProperties = CompressDataToStreamWithLZMA(sectorPrefixDdtStream.ToArray(), _lzmaEncoderProperties, _blockStream); sectorPrefixDdtStream.Close(); ddtHeader.cmpLength = (uint)_blockStream.Length + LZMA_PROPERTIES_LENGTH; var cmpCrc64Context = new Crc64Context(); cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(_blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru 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 NonClosableStream(); var sectorSuffixDdtStream = new MemoryStream(); _crc64 = new Crc64Context(); ddtEntries = MemoryMarshal.Cast(_sectorSuffixDdt).ToArray(); _crc64.Update(ddtEntries); sectorSuffixDdtStream.Write(ddtEntries, 0, ddtEntries.Length); lzmaProperties = CompressDataToStreamWithLZMA(sectorSuffixDdtStream.ToArray(), _lzmaEncoderProperties, _blockStream); 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); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CD sector corrected prefix block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorPrefixMs.GetBuffer(), (uint)_sectorPrefixMs.Length, out byte[] blockCrc); var prefixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorPrefixCorrected, length = (uint)_sectorPrefixMs.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 16 }; lzmaProperties = null; if(!_compress) { prefixBlock.compression = CompressionType.None; prefixBlock.cmpCrc64 = prefixBlock.crc64; prefixBlock.cmpLength = prefixBlock.length; _blockStream = _sectorPrefixMs; } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorPrefixMs.ToArray(), _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress prefix", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref prefixBlock); _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.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CD sector corrected suffix block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorSuffixMs.GetBuffer(), (uint)_sectorSuffixMs.Length, out blockCrc); var suffixBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorSuffixCorrected, length = (uint)_sectorSuffixMs.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 288 }; lzmaProperties = null; if(!_compress) { suffixBlock.compression = CompressionType.None; suffixBlock.cmpCrc64 = suffixBlock.crc64; suffixBlock.cmpLength = suffixBlock.length; _blockStream = _sectorSuffixMs; } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorSuffixMs.ToArray(), _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress suffix", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref suffixBlock); _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.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CD MODE2 subheaders block to position {0}", idxEntry.offset); Crc64Context.Data(_mode2Subheaders, out byte[] blockCrc); var subheaderBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CompactDiscMode2Subheader, length = (uint)_mode2Subheaders.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 8 }; byte[] lzmaProperties = null; if(!_compress) { subheaderBlock.compression = CompressionType.None; subheaderBlock.cmpCrc64 = subheaderBlock.crc64; subheaderBlock.cmpLength = subheaderBlock.length; _blockStream = new NonClosableStream(_mode2Subheaders); } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_mode2Subheaders, _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress MODE2 subheaders", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref subheaderBlock); _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.ReallyClose(); _blockStream = null; } if(_sectorSubchannel != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSubchannel, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing CD subchannel block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorSubchannel, out byte[] blockCrc); var subchannelBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.CdSectorSubchannel, length = (uint)_sectorSubchannel.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 96 }; byte[] lzmaProperties = null; if(!_compress) { subchannelBlock.compression = CompressionType.None; subchannelBlock.cmpCrc64 = subchannelBlock.crc64; subchannelBlock.cmpLength = subchannelBlock.length; _blockStream = new NonClosableStream(_sectorSubchannel); } else { startCompress = DateTime.Now; byte[] transformedSubchannel = ClauniaSubchannelTransform(_sectorSubchannel); _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(transformedSubchannel, _lzmaEncoderProperties, _blockStream); var 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; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress subchannel", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref subchannelBlock); _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.ReallyClose(); _blockStream = null; } if(_sectorCpiMai != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorCpiMai, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing DVD CPI_MAI block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorCpiMai, out byte[] blockCrc); var cpiMaiBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorCpiMai, length = (uint)_sectorCpiMai.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 6 }; byte[] lzmaProperties = null; if(!_compress) { cpiMaiBlock.compression = CompressionType.None; cpiMaiBlock.cmpCrc64 = cpiMaiBlock.crc64; cpiMaiBlock.cmpLength = cpiMaiBlock.length; _blockStream = new NonClosableStream(_sectorCpiMai); } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorCpiMai, _lzmaEncoderProperties, _blockStream); var cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(_blockStream.ToArray()); blockCrc = cmpCrc.Final(); cpiMaiBlock.cmpLength = (uint)_blockStream.Length + LZMA_PROPERTIES_LENGTH; cpiMaiBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); cpiMaiBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress CPI_MAI", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref cpiMaiBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(cpiMaiBlock.compression == CompressionType.Lzma || cpiMaiBlock.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.DvdSectorCpiMai); _index.Add(idxEntry); _blockStream.ReallyClose(); _blockStream = null; } if(_sectorDecryptedTitleKey != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorTitleKeyDecrypted, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing decrypted DVD title key block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorDecryptedTitleKey, out byte[] blockCrc); var titleKeyBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorTitleKeyDecrypted, length = (uint)_sectorDecryptedTitleKey.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 5 }; byte[] lzmaProperties = null; if(!_compress) { titleKeyBlock.compression = CompressionType.None; titleKeyBlock.cmpCrc64 = titleKeyBlock.crc64; titleKeyBlock.cmpLength = titleKeyBlock.length; _blockStream = new NonClosableStream(_sectorDecryptedTitleKey); } else { startCompress = DateTime.Now; _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorDecryptedTitleKey, _lzmaEncoderProperties, _blockStream); var cmpCrc = new Crc64Context(); cmpCrc.Update(lzmaProperties); cmpCrc.Update(_blockStream.ToArray()); blockCrc = cmpCrc.Final(); titleKeyBlock.cmpLength = (uint)_blockStream.Length + LZMA_PROPERTIES_LENGTH; titleKeyBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); titleKeyBlock.compression = CompressionType.Lzma; endCompress = DateTime.Now; AaruConsole.DebugWriteLine("Aaru Format plugin", "Took {0} seconds to compress decrypted DVD title keys", (endCompress - startCompress).TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref titleKeyBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(titleKeyBlock.compression == CompressionType.Lzma || titleKeyBlock.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.DvdSectorTitleKeyDecrypted); _index.Add(idxEntry); _blockStream.ReallyClose(); _blockStream = null; } List trackEntries = new List(); List compactDiscIndexEntries = 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(!track.Indexes.ContainsKey(0) && track.TrackPregap > 0) { track.Indexes[0] = (int)track.TrackStartSector; track.Indexes[1] = (int)(track.TrackStartSector + track.TrackPregap); } else if(!track.Indexes.ContainsKey(0) && !track.Indexes.ContainsKey(1)) track.Indexes[0] = (int)track.TrackStartSector; compactDiscIndexEntries.AddRange(track.Indexes.Select(trackIndex => new CompactDiscIndexEntry { Index = trackIndex.Key, Lba = trackIndex.Value, Track = (ushort)track.TrackSequence })); } // If there are tracks build the tracks block if(trackEntries.Count > 0) { _blockStream = new NonClosableStream(); foreach(TrackEntry entry in trackEntries) { _structurePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(Marshal.SizeOf()); _structureBytes = new byte[Marshal.SizeOf()]; System.Runtime.InteropServices.Marshal.StructureToPtr(entry, _structurePointer, true); System.Runtime.InteropServices.Marshal.Copy(_structurePointer, _structureBytes, 0, _structureBytes.Length); System.Runtime.InteropServices.Marshal.FreeHGlobal(_structurePointer); _blockStream.Write(_structureBytes, 0, _structureBytes.Length); } Crc64Context.Data(_blockStream.ToArray(), out byte[] trksCrc); var trkHeader = new TracksHeader { identifier = BlockType.TracksBlock, entries = (ushort)trackEntries.Count, crc64 = BitConverter.ToUInt64(trksCrc, 0) }; AaruConsole.DebugWriteLine("Aaru 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 }); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref trkHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _blockStream = null; } // If there are track indexes bigger than 1 if(compactDiscIndexEntries.Any(i => i.Index > 1)) { _blockStream = new NonClosableStream(); foreach(CompactDiscIndexEntry entry in compactDiscIndexEntries) { _structurePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(Marshal. SizeOf()); _structureBytes = new byte[Marshal.SizeOf()]; System.Runtime.InteropServices.Marshal.StructureToPtr(entry, _structurePointer, true); System.Runtime.InteropServices.Marshal.Copy(_structurePointer, _structureBytes, 0, _structureBytes.Length); System.Runtime.InteropServices.Marshal.FreeHGlobal(_structurePointer); _blockStream.Write(_structureBytes, 0, _structureBytes.Length); } Crc64Context.Data(_blockStream.ToArray(), out byte[] cdixCrc); var cdixHeader = new CompactDiscIndexesHeader { identifier = BlockType.CompactDiscIndexesBlock, entries = (ushort)compactDiscIndexEntries.Count, crc64 = BitConverter.ToUInt64(cdixCrc, 0) }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing compact disc indexes to position {0}", _imageStream.Position); _index.RemoveAll(t => t.blockType == BlockType.CompactDiscIndexesBlock && t.dataType == DataType.NoData); _index.Add(new IndexEntry { blockType = BlockType.CompactDiscIndexesBlock, dataType = DataType.NoData, offset = (ulong)_imageStream.Position }); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref cdixHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _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 }; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing apple sector tag block to position {0}", idxEntry.offset); Crc64Context.Data(_sectorSubchannel, out byte[] blockCrc); var 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(!_compress) { subchannelBlock.compression = CompressionType.None; subchannelBlock.cmpCrc64 = subchannelBlock.crc64; subchannelBlock.cmpLength = subchannelBlock.length; _blockStream = new NonClosableStream(_sectorSubchannel); } else { _blockStream = new NonClosableStream(); lzmaProperties = CompressDataToStreamWithLZMA(_sectorSubchannel, _lzmaEncoderProperties, _blockStream); var 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; } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref subchannelBlock); _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.ReallyClose(); _blockStream = null; } break; } // Write metadata if present SetMetadataFromTags(); var metadataBlock = new MetadataBlock(); _blockStream = new NonClosableStream(); _blockStream.Write(new byte[Marshal.SizeOf()], 0, Marshal.SizeOf()); 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) { AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing metadata to position {0}", _imageStream.Position); metadataBlock.blockSize = (uint)_blockStream.Length; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref metadataBlock); _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.ReallyClose(); _blockStream = null; } _header.indexOffset = (ulong)_imageStream.Position; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing index to position {0}", _header.indexOffset); _blockStream = new NonClosableStream(); // Write index to memory foreach(IndexEntry entry in _index) { _structureBytes = new byte[Marshal.SizeOf()]; IndexEntry indexEntry = entry; MemoryMarshal.Write(_structureBytes, ref indexEntry); _blockStream.Write(_structureBytes, 0, _structureBytes.Length); } Crc64Context.Data(_blockStream.ToArray(), out byte[] idxCrc); if(_index.Count > ushort.MaxValue) { _header.imageMajorVersion = AARUFMT_VERSION; var idxHeader = new IndexHeader2 { identifier = BlockType.Index2, entries = (ulong)_index.Count, crc64 = BitConverter.ToUInt64(idxCrc, 0) }; // Write index header to disk _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref idxHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); } else { var idxHeader = new IndexHeader { identifier = BlockType.Index, entries = (ushort)_index.Count, crc64 = BitConverter.ToUInt64(idxCrc, 0) }; // Write index header to disk _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, ref idxHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); } // Write index to disk _imageStream.Write(_blockStream.ToArray(), 0, (int)_blockStream.Length); _blockStream.ReallyClose(); _blockStream = null; AaruConsole.DebugWriteLine("Aaru Format plugin", "Writing header"); _header.lastWrittenTime = DateTime.UtcNow.ToFileTimeUtc(); _imageStream.Position = 0; _structurePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(Marshal.SizeOf()); _structureBytes = new byte[Marshal.SizeOf()]; System.Runtime.InteropServices.Marshal.StructureToPtr(_header, _structurePointer, true); System.Runtime.InteropServices.Marshal.Copy(_structurePointer, _structureBytes, 0, _structureBytes.Length); System.Runtime.InteropServices.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 support 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; switch(tag) { case SectorTagType.CdTrackFlags: case SectorTagType.CdTrackIsrc: if(_imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) { ErrorMessage = "Incorrect tag for disk type"; return false; } track = Tracks.FirstOrDefault(trk => sectorAddress == trk.TrackSequence); if(track is null || (track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0)) { ErrorMessage = $"Can't find track {sectorAddress}"; return false; } break; 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 != null && track.TrackSequence == 0 && track.TrackStartSector == 0 && track.TrackEndSector == 0) track.TrackType = TrackType.Data; break; } switch(tag) { case SectorTagType.CdTrackFlags: { if(data.Length != 1) { ErrorMessage = "Incorrect data size for track flags"; return false; } _trackFlags[(byte)sectorAddress] = data[0]; return true; } case SectorTagType.CdTrackIsrc: { if(data != null) _trackIsrcs[(byte)sectorAddress] = Encoding.UTF8.GetString(data); return true; } case SectorTagType.CdSectorSubchannel: { if(data.Length != 96) { ErrorMessage = "Incorrect data size for subchannel"; return false; } _sectorSubchannel ??= new byte[_imageInfo.Sectors * 96]; Array.Copy(data, 0, _sectorSubchannel, (int)(96 * sectorAddress), 96); return true; } case SectorTagType.DvdCmi: { if(data.Length != 1) { ErrorMessage = "Incorrect data size for CMI"; return false; } _sectorCpiMai ??= new byte[_imageInfo.Sectors * 6]; Array.Copy(data, 0, _sectorCpiMai, (int)(6 * sectorAddress), 1); return true; } case SectorTagType.DvdTitleKey: { if(data.Length != 5) { ErrorMessage = "Incorrect data size for title key"; return false; } _sectorCpiMai ??= new byte[_imageInfo.Sectors * 6]; Array.Copy(data, 0, _sectorCpiMai, (int)(1 + (6 * sectorAddress)), 5); return true; } case SectorTagType.DvdTitleKeyDecrypted: { if(data.Length != 5) { ErrorMessage = "Incorrect data size for decrypted title key"; return false; } _sectorDecryptedTitleKey ??= new byte[_imageInfo.Sectors * 5]; Array.Copy(data, 0, _sectorDecryptedTitleKey, (int)(5 * sectorAddress), 5); 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; } _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) { using var lzmaStream = new LzmaStream(properties, false, stream); lzmaStream.Write(data, 0, data.Length); byte[] propertiesArray = new byte[lzmaStream.Properties.Length]; lzmaStream.Properties.CopyTo(propertiesArray, 0); return propertiesArray; } } }