// /*************************************************************************** // 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-2023 Natalia Portillo // Copyright © 2020-2023 Rebecca Wallander // ****************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Xml; using System.Xml.Serialization; using Aaru.Checksums; using Aaru.CommonTypes; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Structs; using Aaru.Compression; using Aaru.Console; using Aaru.Decoders; using Aaru.Decoders.CD; using Aaru.Helpers; using Schemas; using Marshal = Aaru.Helpers.Marshal; using Partition = Aaru.CommonTypes.Partition; using Session = Aaru.CommonTypes.Structs.Session; using TapeFile = Aaru.CommonTypes.Structs.TapeFile; using TapePartition = Aaru.CommonTypes.Structs.TapePartition; using Track = Aaru.CommonTypes.Structs.Track; using TrackType = Aaru.CommonTypes.Enums.TrackType; namespace Aaru.Images; public sealed partial class AaruFormat { #region IWritableOpticalImage Members /// public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, uint sectorSize) { uint sectorsPerBlock; 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 = Localization.Invalid_value_for_sectors_per_block_option; return false; } } else sectorsPerBlock = 4096; if(options.TryGetValue("dictionary", out tmpValue)) { if(!uint.TryParse(tmpValue, out _dictionarySize)) { ErrorMessage = Localization.Invalid_value_for_dictionary_option; return false; } } else _dictionarySize = 1 << 25; if(options.TryGetValue("max_ddt_size", out tmpValue)) { if(!uint.TryParse(tmpValue, out maxDdtSize)) { ErrorMessage = Localization.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 = Localization.Invalid_value_for_md5_option; return false; } } else doMd5 = true; if(options.TryGetValue("sha1", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha1)) { ErrorMessage = Localization.Invalid_value_for_sha1_option; return false; } } else doSha1 = true; if(options.TryGetValue("sha256", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSha256)) { ErrorMessage = Localization.Invalid_value_for_sha256_option; return false; } } else doSha256 = true; if(options.TryGetValue("spamsum", out tmpValue)) { if(!bool.TryParse(tmpValue, out doSpamsum)) { ErrorMessage = Localization.Invalid_value_for_spamsum_option; return false; } } else doSpamsum = false; if(options.TryGetValue("deduplicate", out tmpValue)) { if(!bool.TryParse(tmpValue, out _deduplicate)) { ErrorMessage = Localization.Invalid_value_for_deduplicate_option; return false; } } else _deduplicate = true; if(options.TryGetValue("compress", out tmpValue)) { if(!bool.TryParse(tmpValue, out _compress)) { ErrorMessage = Localization.Invalid_value_for_compress_option; return false; } } else _compress = true; } else { sectorsPerBlock = 4096; _dictionarySize = 1 << 25; maxDdtSize = 256; doMd5 = true; doSha1 = true; doSha256 = true; doSpamsum = false; _deduplicate = true; _compress = true; } _compressionAlgorithm = _compress ? CompressionType.Lzma : CompressionType.None; // This really, cannot happen if(!SupportedMediaTypes.Contains(mediaType)) { ErrorMessage = string.Format(Localization.Unsupported_media_format_0, mediaType); return false; } // Calculate shift _shift = 0; uint oldSectorsPerBlock = sectorsPerBlock; while(sectorsPerBlock > 1) { sectorsPerBlock >>= 1; _shift++; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Got_a_shift_of_0_for_1_sectors_per_block, _shift, oldSectorsPerBlock); _imageInfo = new ImageInfo { MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors, MetadataMediaType = GetMetadataMediaType(mediaType) }; try { _imageStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } catch(IOException e) { ErrorMessage = string.Format(Localization.Could_not_create_new_image_file_exception_0, e.Message); return false; } // Check if appending to an existing image if(_imageStream.Length > Marshal.SizeOf()) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); _header = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); if(_header.identifier != DIC_MAGIC && _header.identifier != AARU_MAGIC) { ErrorMessage = Localization.Cannot_append_to_a_non_Aaru_Format_image; return false; } if(_header.imageMajorVersion > AARUFMT_VERSION) { ErrorMessage = string.Format(Localization.Cannot_append_to_an_unknown_image_version_0, _header.imageMajorVersion); return false; } if(_header.mediaType != mediaType) { ErrorMessage = string.Format(Localization.Cannot_write_a_media_with_type_0_to_an_image_with_type_1, mediaType, _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.EnsureRead(_structureBytes, 0, _structureBytes.Length); IndexHeader idxHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(idxHeader.identifier != BlockType.Index) { ErrorMessage = Localization.Index_not_found_in_existing_image_cannot_continue; return false; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Index_at_0_contains_1_entries, _header.indexOffset, idxHeader.entries); for(ushort i = 0; i < idxHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); IndexEntry entry = Marshal.SpanToStructureLittleEndian(_structureBytes); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 is { blockType: BlockType.ChecksumBlock, dataType: DataType.NoData }); var 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.EnsureRead(_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(MODULE_NAME, Localization.Incorrect_identifier_for_data_block_at_position_0, entry.offset); break; } if(blockHeader.type != entry.dataType) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. 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(MODULE_NAME, Localization.Found_data_block_type_0_at_position_1, entry.dataType, entry.offset); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); // Decompress media tag if(blockHeader.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) { if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform && entry.dataType != DataType.CdSectorSubchannel) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Invalid_compression_type_0_for_block_with_data_type_1_continuing, blockHeader.compression, entry.dataType); break; } var decompressStopwatch = new Stopwatch(); decompressStopwatch.Restart(); var compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; var lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; _imageStream.EnsureRead(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); _imageStream.EnsureRead(compressedTag, 0, compressedTag.Length); data = new byte[blockHeader.length]; int decompressedLength = LZMA.DecodeBuffer(compressedTag, data, lzmaProperties); if(decompressedLength != blockHeader.length) { ErrorMessage = string. Format(Localization.Error_decompressing_block_should_be_0_bytes_but_got_1_bytes, blockHeader.length, decompressedLength); return false; } if(blockHeader.compression == CompressionType.LzmaClauniaSubchannelTransform) data = ClauniaSubchannelUntransform(data); decompressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_decompress_block, decompressStopwatch.Elapsed.TotalSeconds); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); } else if(blockHeader.compression == CompressionType.None) { data = new byte[blockHeader.length]; _imageStream.EnsureRead(data, 0, (int)blockHeader.length); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); } else { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 && blockHeader.crc64 != 0) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Incorrect_CRC_found_0_X16_found_expected_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 MemoryStream(); _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(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.CdSectorSuffix: case DataType.CdSectorSuffixCorrected: if(entry.dataType == DataType.CdSectorSuffixCorrected) { _sectorSuffixMs = new MemoryStream(); _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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.CompactDiscMode2Subheader: _mode2Subheaders = data; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.DvdSectorCprMai: _sectorCprMai = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorCmi)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorCmi); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorTitleKey)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorTitleKey); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; default: MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); if(_mediaTags.ContainsKey(mediaTagType)) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Media_tag_type_0_duplicated_removing_previous_entry, mediaTagType); _mediaTags.Remove(mediaTagType); } _mediaTags.Add(mediaTagType, data); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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.EnsureRead(_structureBytes, 0, _structureBytes.Length); DdtHeader ddtHeader = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; if(ddtHeader.entries != _imageInfo.Sectors && !IsTape) { ErrorMessage = string. Format(Localization.Trying_to_write_a_media_with_0_sectors_to_an_image_with_1_sectors_not_continuing, _imageInfo.Sectors, ddtHeader.entries); return false; } _shift = ddtHeader.shift; switch(ddtHeader.compression) { case CompressionType.Lzma: AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Decompressing_DDT); var ddtStopwatch = new Stopwatch(); ddtStopwatch.Start(); var compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; var lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; _imageStream.EnsureRead(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); _imageStream.EnsureRead(compressedDdt, 0, compressedDdt.Length); var decompressedDdt = new byte[ddtHeader.length]; var decompressedLength = (ulong)LZMA.DecodeBuffer(compressedDdt, decompressedDdt, lzmaProperties); if(decompressedLength != ddtHeader.length) { ErrorMessage = string. Format(Localization.Error_decompressing_DDT_should_be_0_bytes_but_got_1_bytes, ddtHeader.length, decompressedLength); return false; } _userDataDdt = MemoryMarshal.Cast(decompressedDdt).ToArray(); ddtStopwatch.Stop(); _inMemoryDdt = true; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_decompress_DDT, ddtStopwatch.Elapsed.TotalSeconds); break; case CompressionType.None: _inMemoryDdt = false; _outMemoryDdtPosition = (long)entry.offset; break; default: ErrorMessage = string.Format(Localization.Found_unsupported_compression_algorithm_0, (ushort)ddtHeader.compression); return false; } 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 is DataType.CdSectorPrefixCorrected or DataType.CdSectorSuffixCorrected) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); DdtHeader ddtHeader = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); if(ddtHeader.identifier != BlockType.DeDuplicationTable) break; if(ddtHeader.entries != _imageInfo.Sectors) { ErrorMessage = string. Format(Localization.Trying_to_write_a_media_with_0_sectors_to_an_image_with_1_sectors_not_continuing, _imageInfo.Sectors, ddtHeader.entries); return false; } var decompressedDdt = new byte[ddtHeader.length]; switch(ddtHeader.compression) { case CompressionType.Lzma: AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Decompressing_DDT); var ddtStopwatch = new Stopwatch(); ddtStopwatch.Start(); var compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; var lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; _imageStream.EnsureRead(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); _imageStream.EnsureRead(compressedDdt, 0, compressedDdt.Length); var decompressedLength = (ulong)LZMA.DecodeBuffer(compressedDdt, decompressedDdt, lzmaProperties); if(decompressedLength != ddtHeader.length) { ErrorMessage = string. Format(Localization.Error_decompressing_DDT_should_be_0_bytes_but_got_1_bytes, ddtHeader.length, decompressedLength); return false; } ddtStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_decompress_DDT, ddtStopwatch.Elapsed.TotalSeconds); break; case CompressionType.None: _imageStream.EnsureRead(decompressedDdt, 0, decompressedDdt.Length); break; default: ErrorMessage = string.Format(Localization.Found_unsupported_compression_algorithm_0, (ushort)ddtHeader.compression); return false; } 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.EnsureRead(_structureBytes, 0, _structureBytes.Length); _geometryBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(_geometryBlock.identifier == BlockType.GeometryBlock) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. 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(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); } break; // Metadata block case BlockType.MetadataBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); MetadataBlock metadataBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(metadataBlock.identifier != entry.blockType) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Incorrect_identifier_for_data_block_at_position_0, entry.offset); break; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_metadata_block_at_position_0, entry.offset); var metadata = new byte[metadataBlock.blockSize]; _imageStream.Position = (long)entry.offset; _imageStream.EnsureRead(metadata, 0, metadata.Length); if(metadataBlock is { mediaSequence: > 0, lastMediaSequence: > 0 }) { _imageInfo.MediaSequence = metadataBlock.mediaSequence; _imageInfo.LastMediaSequence = metadataBlock.lastMediaSequence; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.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(MODULE_NAME, Localization.Setting_drive_firmware_revision_0, _imageInfo.DriveFirmwareRevision); } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; // Optical disc tracks block case BlockType.TracksBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); TracksHeader tracksHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(tracksHeader.identifier != BlockType.TracksBlock) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Incorrect_identifier_for_tracks_block_at_position_0, entry.offset); break; } _structureBytes = new byte[Marshal.SizeOf() * tracksHeader.entries]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); Crc64Context.Data(_structureBytes, out byte[] trksCrc); if(BitConverter.ToUInt64(trksCrc, 0) != tracksHeader.crc64) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Incorrect_CRC_found_0_X16_found_expected_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(MODULE_NAME, Localization.Found_0_tracks_at_position_1, tracksHeader.entries, entry.offset); for(ushort i = 0; i < tracksHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); TrackEntry trackEntry = Marshal.ByteArrayToStructureLittleEndian(_structureBytes); Tracks.Add(new Track { Sequence = trackEntry.sequence, Type = trackEntry.type, StartSector = (ulong)trackEntry.start, EndSector = (ulong)trackEntry.end, Pregap = (ulong)trackEntry.pregap, Session = trackEntry.session, FileType = "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(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; // CICM XML metadata block case BlockType.CicmBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); CicmMetadataBlock cicmBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(cicmBlock.identifier != BlockType.CicmBlock) break; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_CICM_XML_metadata_block_at_position_0, entry.offset); var cicmBytes = new byte[cicmBlock.length]; _imageStream.EnsureRead(cicmBytes, 0, cicmBytes.Length); var cicmMs = new MemoryStream(cicmBytes); // The converter to AaruMetadata basically overcomes this (should?) #pragma warning disable IL2026 var cicmXs = new XmlSerializer(typeof(CICMMetadataType)); #pragma warning restore IL2026 try { var sr = new StreamReader(cicmMs); // The converter to AaruMetadata basically overcomes this (should?) #pragma warning disable IL2026 AaruMetadata = (CICMMetadataType)cicmXs.Deserialize(sr); #pragma warning restore IL2026 sr.Close(); } catch(XmlException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Exception_0_processing_CICM_XML_metadata_block, ex.Message); AaruMetadata = null; } break; // Aaru Metadata JSON block case BlockType.AaruMetadataJsonBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); AaruMetadataJsonBlock aaruMetadataBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(aaruMetadataBlock.identifier != BlockType.AaruMetadataJsonBlock) break; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_Aaru_Metadata_block_at_position_0, entry.offset); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); var jsonBytes = new byte[aaruMetadataBlock.length]; _imageStream.EnsureRead(jsonBytes, 0, jsonBytes.Length); try { AaruMetadata = (JsonSerializer.Deserialize(jsonBytes, typeof(MetadataJson), MetadataJsonContext.Default) as MetadataJson)?.AaruMetadata; } catch(JsonException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Exception_0_processing_Aaru_Metadata_block, ex.Message); AaruMetadata = null; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; // Dump hardware block case BlockType.DumpHardwareBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); DumpHardwareHeader dumpBlock = Marshal.SpanToStructureLittleEndian(_structureBytes); if(dumpBlock.identifier != BlockType.DumpHardwareBlock) break; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_dump_hardware_block_at_position_0, entry.offset); _structureBytes = new byte[dumpBlock.length]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); Crc64Context.Data(_structureBytes, out byte[] dumpCrc); if(BitConverter.ToUInt64(dumpCrc, 0) != dumpBlock.crc64) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Incorrect_CRC_found_0_X16_found_expected_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.EnsureRead(_structureBytes, 0, _structureBytes.Length); DumpHardwareEntry dumpEntry = Marshal.SpanToStructureLittleEndian(_structureBytes); var dump = new DumpHardware { Software = new Software(), Extents = new List() }; byte[] tmp; if(dumpEntry.manufacturerLength > 0) { tmp = new byte[dumpEntry.manufacturerLength - 1]; _imageStream.EnsureRead(tmp, 0, tmp.Length); _imageStream.Position += 1; dump.Manufacturer = Encoding.UTF8.GetString(tmp); } if(dumpEntry.modelLength > 0) { tmp = new byte[dumpEntry.modelLength - 1]; _imageStream.EnsureRead(tmp, 0, tmp.Length); _imageStream.Position += 1; dump.Model = Encoding.UTF8.GetString(tmp); } if(dumpEntry.revisionLength > 0) { tmp = new byte[dumpEntry.revisionLength - 1]; _imageStream.EnsureRead(tmp, 0, tmp.Length); _imageStream.Position += 1; dump.Revision = Encoding.UTF8.GetString(tmp); } if(dumpEntry.firmwareLength > 0) { tmp = new byte[dumpEntry.firmwareLength - 1]; _imageStream.EnsureRead(tmp, 0, tmp.Length); _imageStream.Position += 1; dump.Firmware = Encoding.UTF8.GetString(tmp); } if(dumpEntry.serialLength > 0) { tmp = new byte[dumpEntry.serialLength - 1]; _imageStream.EnsureRead(tmp, 0, tmp.Length); _imageStream.Position += 1; dump.Serial = Encoding.UTF8.GetString(tmp); } if(dumpEntry.softwareNameLength > 0) { tmp = new byte[dumpEntry.softwareNameLength - 1]; _imageStream.EnsureRead(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.EnsureRead(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.EnsureRead(tmp, 0, tmp.Length); dump.Software.OperatingSystem = Encoding.UTF8.GetString(tmp); } tmp = new byte[16]; for(uint j = 0; j < dumpEntry.extents; j++) { _imageStream.EnsureRead(tmp, 0, tmp.Length); dump.Extents.Add(new Extent { Start = BitConverter.ToUInt64(tmp, 0), End = BitConverter.ToUInt64(tmp, 8) }); } dump.Extents = dump.Extents.OrderBy(t => t.Start).ToList(); if(dump.Extents.Count > 0) DumpHardware.Add(dump); } if(DumpHardware.Count == 0) DumpHardware = null; break; // Tape partition block case BlockType.TapePartitionBlock: _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); TapePartitionHeader partitionHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(partitionHeader.identifier != BlockType.TapePartitionBlock) break; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_tape_partition_block_at_position_0, entry.offset); var tapePartitionBytes = new byte[partitionHeader.length]; _imageStream.EnsureRead(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.EnsureRead(_structureBytes, 0, _structureBytes.Length); TapeFileHeader fileHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(fileHeader.identifier != BlockType.TapeFileBlock) break; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_tape_file_block_at_position_0, entry.offset); var tapeFileBytes = new byte[fileHeader.length]; _imageStream.EnsureRead(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.EnsureRead(_structureBytes, 0, _structureBytes.Length); CompactDiscIndexesHeader indexesHeader = Marshal.SpanToStructureLittleEndian(_structureBytes); if(indexesHeader.identifier != BlockType.CompactDiscIndexesBlock) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Incorrect_identifier_for_compact_disc_indexes_block_at_position_0, entry.offset); break; } _structureBytes = new byte[Marshal.SizeOf() * indexesHeader.entries]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); Crc64Context.Data(_structureBytes, out byte[] idsxCrc); if(BitConverter.ToUInt64(idsxCrc, 0) != indexesHeader.crc64) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization. Incorrect_CRC_found_0_X16_found_expected_1_X16_continuing, BitConverter.ToUInt64(idsxCrc, 0), indexesHeader.crc64); break; } _imageStream.Position -= _structureBytes.Length; compactDiscIndexes = new List(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_0_compact_disc_indexes_at_position_1, indexesHeader.entries, entry.offset); for(ushort i = 0; i < indexesHeader.entries; i++) { _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.EnsureRead(_structureBytes, 0, _structureBytes.Length); compactDiscIndexes.Add(Marshal. ByteArrayToStructureLittleEndian< CompactDiscIndexEntry>(_structureBytes)); } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; } } if(!foundUserDataDdt) { ErrorMessage = Localization.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.MetadataMediaType == MetadataMediaType.OpticalDisc) { if(Tracks != null && _mediaTags.TryGetValue(MediaTagType.CD_FullTOC, out byte[] fullToc)) { var tmp = new byte[fullToc.Length + 2]; Array.Copy(fullToc, 0, tmp, 2, fullToc.Length); tmp[0] = (byte)(fullToc.Length >> 8); tmp[1] = (byte)(fullToc.Length & 0xFF); FullTOC.CDFullTOC? decodedFullToc = FullTOC.Decode(tmp); if(decodedFullToc.HasValue) { Dictionary leadOutStarts = new(); // Lead-out starts foreach(FullTOC.TrackDataDescriptor trk in decodedFullToc.Value.TrackDescriptors.Where(trk => trk.ADR is 1 or 4 && trk.POINT == 0xA2)) { int phour, pmin, psec, pframe; if(trk.PFRAME == 0) { pframe = 74; if(trk.PSEC == 0) { psec = 59; if(trk.PMIN == 0) { pmin = 59; phour = trk.PHOUR - 1; } else { pmin = trk.PMIN - 1; phour = trk.PHOUR; } } else { psec = trk.PSEC - 1; pmin = trk.PMIN; phour = trk.PHOUR; } } else { pframe = trk.PFRAME - 1; psec = trk.PSEC; pmin = trk.PMIN; phour = trk.PHOUR; } int lastSector = phour * 3600 * 75 + pmin * 60 * 75 + psec * 75 + pframe - 150; leadOutStarts?.Add(trk.SessionNumber, lastSector + 1); } foreach(KeyValuePair leadOuts in leadOutStarts) { var lastTrackInSession = new Track(); foreach(Track trk in Tracks.Where(trk => trk.Session == leadOuts.Key). Where(trk => trk.Sequence > lastTrackInSession.Sequence)) lastTrackInSession = trk; if(lastTrackInSession.Sequence == 0 || lastTrackInSession.EndSector == (ulong)leadOuts.Value - 1) continue; lastTrackInSession.EndSector = (ulong)leadOuts.Value - 1; } } } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); if(Tracks == null || Tracks.Count == 0) { Tracks = new List { new() { BytesPerSector = (int)_imageInfo.SectorSize, EndSector = _imageInfo.Sectors - 1, FileType = "BINARY", RawBytesPerSector = (int)_imageInfo.SectorSize, Session = 1, Sequence = 1, Type = TrackType.Data } }; _trackFlags = new Dictionary { { 1, (byte)CdFlags.DataTrack } }; _trackIsrcs = new Dictionary(); } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); Sessions = new List(); for(var i = 1; i <= Tracks.Max(t => t.Session); i++) { Sessions.Add(new Session { Sequence = (ushort)i, StartTrack = Tracks.Where(t => t.Session == i).Min(t => t.Sequence), EndTrack = Tracks.Where(t => t.Session == i).Max(t => t.Sequence), StartSector = Tracks.Where(t => t.Session == i).Min(t => t.StartSector), EndSector = Tracks.Where(t => t.Session == i).Max(t => t.EndSector) }); } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); foreach(Track track in Tracks.OrderBy(t => t.StartSector)) { if(track.Sequence == 1) { track.Pregap = 150; track.Indexes[0] = -150; track.Indexes[1] = (int)track.StartSector; continue; } if(track.Pregap > 0) { track.Indexes[0] = (int)track.StartSector; track.Indexes[1] = (int)(track.StartSector + track.Pregap); } else track.Indexes[1] = (int)track.StartSector; } ulong currentTrackOffset = 0; Partitions = new List(); foreach(Track track in Tracks.OrderBy(t => t.StartSector)) { Partitions.Add(new Partition { Sequence = track.Sequence, Type = track.Type.ToString(), Name = string.Format(Localization.Track_0, track.Sequence), Offset = currentTrackOffset, Start = (ulong)track.Indexes[1], Size = (track.EndSector - (ulong)track.Indexes[1] + 1) * (ulong)track.BytesPerSector, Length = track.EndSector - (ulong)track.Indexes[1] + 1, Scheme = Localization.Optical_disc_track }); currentTrackOffset += (track.EndSector - track.StartSector + 1) * (ulong)track.BytesPerSector; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); Track[] tracks = Tracks.ToArray(); foreach(Track trk in tracks) { ErrorNumber errno = ReadSector(trk.StartSector, out byte[] sector); if(errno != ErrorNumber.NoError) continue; trk.BytesPerSector = sector.Length; trk.RawBytesPerSector = _sectorPrefix != null && _sectorSuffix != null || _sectorPrefixDdt != null && _sectorSuffixDdt != null ? 2352 : sector.Length; if(_sectorSubchannel == null) continue; trk.SubchannelFile = trk.File; trk.SubchannelFilter = trk.Filter; trk.SubchannelType = TrackSubchannelType.Raw; } AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); Tracks = tracks.ToList(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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.Sequence == 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, in 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(MODULE_NAME, Localization.In_memory_DDT_0, _inMemoryDdt); _imageStream.Seek(0, SeekOrigin.End); IsWriting = true; ErrorMessage = null; return true; } /// public bool WriteMediaTag(byte[] data, MediaTagType tag) { if(!IsWriting) { ErrorMessage = Localization.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 = Localization.Tried_to_write_on_a_non_writable_image; return false; } if(sectorAddress >= Info.Sectors && !IsTape) { ErrorMessage = Localization.Tried_to_write_past_image_size; return false; } if((_imageInfo.MetadataMediaType != MetadataMediaType.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.MetadataMediaType == MetadataMediaType.OpticalDisc) { trk = Tracks.FirstOrDefault(t => sectorAddress >= t.StartSector && sectorAddress <= t.EndSector) ?? new Track(); if(trk.Sequence == 0 && trk.StartSector == 0 && trk.EndSector == 0) trk.Type = TrackType.Data; // TODO: Check intersession data type } // Close current block first if(_writingBuffer != 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.Type != TrackType.Audio)) { _currentBlockHeader.length = _currentBlockOffset * _currentBlockHeader.sectorSize; _currentBlockHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); var cmpCrc64Context = new Crc64Context(); byte[] lzmaProperties = Array.Empty(); var compressedLength = 0; switch(_currentBlockHeader.compression) { case CompressionType.Flac: { uint currentSamples = _currentBlockOffset * SAMPLES_PER_SECTOR; uint flacBlockSize = _currentBlockOffset * SAMPLES_PER_SECTOR; if(flacBlockSize > MAX_FLAKE_BLOCK) flacBlockSize = MAX_FLAKE_BLOCK; if(flacBlockSize < MIN_FLAKE_BLOCK) flacBlockSize = MIN_FLAKE_BLOCK; long remaining = currentSamples % flacBlockSize; // Fill FLAC block if(remaining != 0) { for(var r = 0; r < remaining * 4; r++) _writingBuffer[_writingBufferPosition + r] = 0; } compressedLength = FLAC.EncodeBuffer(_writingBuffer, _compressedBuffer, flacBlockSize, true, false, "hamming", 12, 15, true, false, 0, 8, "Aaru"); if(compressedLength >= _writingBufferPosition) _currentBlockHeader.compression = CompressionType.None; break; } case CompressionType.Lzma: { compressedLength = LZMA.EncodeBuffer(_writingBuffer, _compressedBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); cmpCrc64Context.Update(lzmaProperties); if(compressedLength >= _writingBufferPosition) _currentBlockHeader.compression = CompressionType.None; break; } case CompressionType.None: break; // Do nothing default: throw new ArgumentOutOfRangeException(); } if(_currentBlockHeader.compression == CompressionType.None) { _currentBlockHeader.cmpCrc64 = _currentBlockHeader.crc64; _currentBlockHeader.cmpLength = (uint)_writingBufferPosition; } else { cmpCrc64Context.Update(_compressedBuffer, (uint)compressedLength); _currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _currentBlockHeader.cmpLength = (uint)compressedLength; } 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, in _currentBlockHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; if(_currentBlockHeader.compression == CompressionType.Lzma) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); if(_currentBlockHeader.compression == CompressionType.None) _imageStream.Write(_writingBuffer, 0, _writingBufferPosition); else _imageStream.Write(_compressedBuffer, 0, compressedLength); _writingBufferPosition = 0; GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, false); _currentBlockOffset = 0; } // No block set if(_writingBufferPosition == 0) { _currentBlockHeader = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.UserData, compression = _compress ? _compressionAlgorithm : CompressionType.None, sectorSize = (uint)data.Length }; if(_imageInfo.MetadataMediaType == MetadataMediaType.OpticalDisc && trk.Type == 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.Session > 1 || _imageInfo.MediaType is MediaType.VideoNow or MediaType.VideoNowColor or MediaType.VideoNowXp) && trk.Type == TrackType.Audio && _compress && _currentBlockHeader.compression == CompressionType.Flac) _currentBlockHeader.compression = CompressionType.Lzma; int maxBufferSize = (1 << _shift) * data.Length + MAX_FLAKE_BLOCK * 4; if(_writingBuffer == null || _writingBuffer.Length < maxBufferSize) { _writingBuffer = new byte[maxBufferSize]; _compressedBuffer = new byte[maxBufferSize * 2]; } _writingBufferPosition = 0; _crc64 = new Crc64Context(); } var ddtEntry = (ulong)((_imageStream.Position << _shift) + _currentBlockOffset); if(hash != null) _deduplicationTable.Add(hashString, ddtEntry); Array.Copy(data, 0, _writingBuffer, _writingBufferPosition, data.Length); _writingBufferPosition += data.Length; SetDdtEntry(sectorAddress, ddtEntry); _crc64.Update(data); _currentBlockOffset++; ErrorMessage = ""; return true; } /// public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) { if(!IsWriting) { ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image; return false; } if(sectorAddress + length > Info.Sectors) { ErrorMessage = Localization.Tried_to_write_past_image_size; return false; } var sectorSize = (uint)(data.Length / length); for(uint i = 0; i < length; i++) { var 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 = Localization.Tried_to_write_on_a_non_writable_image; return false; } byte[] sector; switch(_imageInfo.MetadataMediaType) { case MetadataMediaType.OpticalDisc: Track track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.StartSector && sectorAddress <= trk.EndSector); if(track is null) { ErrorMessage = Localization.Track_not_found; return false; } if(track.Sequence == 0 && track.StartSector == 0 && track.EndSector == 0) track.Type = TrackType.Data; if(data.Length == 2064 && _imageInfo.MediaType == MediaType.DVDROM) { sector = new byte[2048]; _sectorId ??= new byte[_imageInfo.Sectors * 4]; _sectorIed ??= new byte[_imageInfo.Sectors * 2]; _sectorCprMai ??= new byte[_imageInfo.Sectors * 6]; _sectorEdc ??= new byte[_imageInfo.Sectors * 4]; Array.Copy(data, 0, _sectorId, (int)sectorAddress * 4, 4); Array.Copy(data, 4, _sectorIed, (int)sectorAddress * 2, 2); Array.Copy(data, 6, _sectorCprMai, (int)sectorAddress * 6, 6); Array.Copy(data, 12, sector, 0, 2048); Array.Copy(data, 2060, _sectorEdc, (int)sectorAddress * 4, 4); return WriteSector(sector, sectorAddress); } if(data.Length != 2352) { ErrorMessage = Localization.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.Type) { 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 MemoryStream(); _sectorPrefixMs ??= new MemoryStream(); 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 MemoryStream(); _sectorPrefixMs ??= new MemoryStream(); 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); var 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); var 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 MetadataMediaType.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 is MediaType.AppleProfile or 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 is MediaType.AppleSonySS or MediaType.AppleSonySS: newTag = new byte[12]; Array.Copy(data, 512, newTag, 0, 12); break; // Profile tag, copy to Profile case 20 when _imageInfo.MediaType is MediaType.AppleProfile or 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 is MediaType.AppleSonySS or 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 is MediaType.AppleProfile or 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 is MediaType.AppleSonySS or 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 = Localization.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 = Localization.Unknown_long_sector_type_cannot_write; return false; } /// public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) { byte[] sector; switch(_imageInfo.MetadataMediaType) { case MetadataMediaType.OpticalDisc: switch(_imageInfo.MediaType) { case MediaType.DVDROM: if(data.Length % 2064 != 0) { ErrorMessage = Localization.Incorrect_data_size; return false; } sector = new byte[2064]; for(uint i = 0; i < length; i++) { Array.Copy(data, 2064 * i, sector, 0, 2064); if(!WriteSectorLong(sector, sectorAddress + i)) return false; } ErrorMessage = ""; return true; default: if(data.Length % 2352 != 0) { ErrorMessage = Localization.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 MetadataMediaType.BlockMedia: switch(_imageInfo.MediaType) { case MediaType.AppleFileWare: case MediaType.AppleProfile: case MediaType.AppleSonyDS: case MediaType.AppleSonySS: case MediaType.AppleWidget: case MediaType.PriamDataTower: var 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 = Localization.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 = Localization.Unknown_long_sector_type_cannot_write; return false; } /// public bool SetTracks(List tracks) { if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) { ErrorMessage = Localization.Unsupported_feature; return false; } if(!IsWriting) { ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image; return false; } Tracks = tracks; ErrorMessage = ""; return true; } /// public bool Close() { if(!IsWriting) { ErrorMessage = Localization.Image_is_not_opened_for_writing; return false; } // Close current block first if(_writingBuffer != null) { _currentBlockHeader.length = _currentBlockOffset * _currentBlockHeader.sectorSize; _currentBlockHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); var cmpCrc64Context = new Crc64Context(); byte[] lzmaProperties = Array.Empty(); var compressedLength = 0; switch(_currentBlockHeader.compression) { case CompressionType.Flac: { uint currentSamples = _currentBlockOffset * SAMPLES_PER_SECTOR; uint flacBlockSize = _currentBlockOffset * SAMPLES_PER_SECTOR; if(flacBlockSize > MAX_FLAKE_BLOCK) flacBlockSize = MAX_FLAKE_BLOCK; if(flacBlockSize < MIN_FLAKE_BLOCK) flacBlockSize = MIN_FLAKE_BLOCK; long remaining = currentSamples % flacBlockSize; // Fill FLAC block if(remaining != 0) { for(var r = 0; r < remaining * 4; r++) _writingBuffer[_writingBufferPosition + r] = 0; } compressedLength = FLAC.EncodeBuffer(_writingBuffer, _compressedBuffer, flacBlockSize, true, false, "hamming", 12, 15, true, false, 0, 8, "Aaru"); if(compressedLength >= _writingBufferPosition) _currentBlockHeader.compression = CompressionType.None; break; } case CompressionType.Lzma: { compressedLength = LZMA.EncodeBuffer(_writingBuffer, _compressedBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); cmpCrc64Context.Update(lzmaProperties); if(compressedLength >= _writingBufferPosition) _currentBlockHeader.compression = CompressionType.None; break; } case CompressionType.None: break; // Do nothing default: throw new ArgumentOutOfRangeException(); } if(_currentBlockHeader.compression == CompressionType.None) { _currentBlockHeader.cmpCrc64 = _currentBlockHeader.crc64; _currentBlockHeader.cmpLength = (uint)_writingBufferPosition; } else { cmpCrc64Context.Update(_compressedBuffer, (uint)compressedLength); _currentBlockHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _currentBlockHeader.cmpLength = (uint)compressedLength; } 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, in _currentBlockHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; if(_currentBlockHeader.compression == CompressionType.Lzma) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); if(_currentBlockHeader.compression == CompressionType.None) _imageStream.Write(_writingBuffer, 0, _writingBufferPosition); else _imageStream.Write(_compressedBuffer, 0, compressedLength); _writingBuffer = null; } if(_deduplicate) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Of_0_sectors_written_1_are_unique_2, _writtenSectors, _deduplicationTable.Count, (double)_deduplicationTable.Count / _writtenSectors); } IndexEntry idxEntry; // TODO: Reuse buffer MemoryStream blockStream; // Write media tag blocks foreach(KeyValuePair mediaTag in _mediaTags) { DataType dataType = GetDataTypeForMediaTag(mediaTag.Key); if(mediaTag.Value is null) { AaruConsole.ErrorWriteLine(Localization.Tag_type_0_is_null_skipping, dataType); continue; } idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = dataType, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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) }; var cmpBuffer = new byte[mediaTag.Value.Length + 262144]; byte[] lzmaProperties = null; var doNotCompress = false; switch(_compressionAlgorithm) { case CompressionType.Lzma: int cmpLen = LZMA.EncodeBuffer(mediaTag.Value, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); if(cmpLen + LZMA_PROPERTIES_LENGTH > mediaTag.Value.Length) doNotCompress = true; break; case CompressionType.None: doNotCompress = true; break; default: throw new ArgumentOutOfRangeException(); } byte[] tagData; // Not compressible if(doNotCompress) { tagBlock.cmpLength = tagBlock.length; tagBlock.cmpCrc64 = tagBlock.crc64; tagData = mediaTag.Value; tagBlock.compression = CompressionType.None; } else { tagData = cmpBuffer; var crc64Ctx = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) crc64Ctx.Update(lzmaProperties); crc64Ctx.Update(tagData); tagCrc = crc64Ctx.Final(); tagBlock.cmpLength = (uint)tagData.Length; tagBlock.cmpCrc64 = BitConverter.ToUInt64(tagCrc, 0); tagBlock.compression = _compressionAlgorithm; if(_compressionAlgorithm == CompressionType.Lzma) tagBlock.cmpLength += LZMA_PROPERTIES_LENGTH; } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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(MODULE_NAME, Localization.Writing_geometry_block_to_position_0, idxEntry.offset); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in _geometryBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _index.RemoveAll(t => t is { blockType: BlockType.GeometryBlock, dataType: DataType.NoData }); _index.Add(idxEntry); } // If we have dump hardware, write it if(DumpHardware != null) { var dumpMs = new MemoryStream(); foreach(DumpHardware 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.Count }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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(Extent 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(MODULE_NAME, Localization.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, in dumpBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(dumpMs.ToArray(), 0, (int)dumpMs.Length); _index.RemoveAll(t => t is { blockType: BlockType.DumpHardwareBlock, dataType: DataType.NoData }); _index.Add(idxEntry); } // If we have Aaru Metadata, write it if(AaruMetadata != null) { var jsonMs = new MemoryStream(); JsonSerializer.Serialize(jsonMs, new MetadataJson { AaruMetadata = AaruMetadata }, typeof(MetadataJson), MetadataJsonContext.Default); idxEntry = new IndexEntry { blockType = BlockType.AaruMetadataJsonBlock, dataType = DataType.NoData, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_Aaru_Metadata_block_to_position_0, idxEntry.offset); var jsonBlock = new AaruMetadataJsonBlock { identifier = BlockType.AaruMetadataJsonBlock, length = (uint)jsonMs.Length }; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in jsonBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(jsonMs.ToArray(), 0, (int)jsonMs.Length); // Ensure no CICM XML block is recorded altogether _index.RemoveAll(t => t is { blockType: BlockType.CicmBlock, dataType : DataType.NoData }); _index.RemoveAll(t => t is { blockType: BlockType.AaruMetadataJsonBlock, 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, in 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, in 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, in 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, in 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(MODULE_NAME, Localization.Writing_checksum_block_to_position_0, idxEntry.offset); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in chkHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(chkMs.ToArray(), 0, (int)chkMs.Length); _index.RemoveAll(t => t is { blockType: BlockType.ChecksumBlock, 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(MODULE_NAME, Localization.Writing_tape_partitions_to_position_0, idxEntry.offset); var tapePartitionEntries = new TapePartitionEntry[TapePartitions.Count]; for(var 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, in tapePartitionHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(tapePartitionEntriesData, 0, tapePartitionEntriesData.Length); _index.RemoveAll(t => t is { blockType: BlockType.TapePartitionBlock, dataType: DataType.UserData }); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.TapeFileBlock, dataType = DataType.UserData, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_tape_files_to_position_0, idxEntry.offset); var tapeFileEntries = new TapeFileEntry[Files.Count]; for(var 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, in tapeFileHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; _imageStream.Write(tapeFileEntriesData, 0, tapeFileEntriesData.Length); _index.RemoveAll(t => t is { blockType: BlockType.TapeFileBlock, 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(MODULE_NAME, Localization.Writing_user_data_DDT_to_position_0, idxEntry.offset); var ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.UserData, compression = _compressionAlgorithm, shift = _shift, entries = (ulong)_userDataDdt.LongLength, length = (ulong)(_userDataDdt.LongLength * sizeof(ulong)) }; _crc64 = new Crc64Context(); byte[] ddtEntries = MemoryMarshal.Cast(_userDataDdt).ToArray(); _crc64.Update(ddtEntries); var cmpBuffer = new byte[ddtEntries.Length + 262144]; int cmpLen; byte[] lzmaProperties = null; switch(_compressionAlgorithm) { case CompressionType.None: cmpBuffer = ddtEntries; cmpLen = cmpBuffer.Length; break; case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(ddtEntries, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); ddtHeader.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma && cmpBuffer != ddtEntries) ddtHeader.cmpLength += LZMA_PROPERTIES_LENGTH; var cmpCrc64Context = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma && cmpBuffer != ddtEntries) cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; if(_compressionAlgorithm == CompressionType.Lzma) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DeDuplicationTable, dataType: DataType.UserData }); _index.Add(idxEntry); } // Write the sector prefix, suffix and subchannels if present switch(_imageInfo.MetadataMediaType) { case MetadataMediaType.OpticalDisc when Tracks is { Count: > 0 }: var compressStopwatch = new Stopwatch(); // Old format if(_sectorPrefix != null && _sectorSuffix != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorPrefix, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 MemoryStream(_sectorPrefix); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorPrefix.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorPrefix, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorPrefix; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) prefixBlock.cmpLength += LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_prefix, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.CdSectorPrefix }); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSuffix, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 MemoryStream(_sectorSuffix); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorSuffix.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorSuffix, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorSuffix; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) prefixBlock.cmpLength += LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = CompressionType.Lzma; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_suffix, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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 is { blockType: BlockType.DataBlock, dataType: DataType.CdSectorSuffix }); _index.Add(idxEntry); blockStream.Close(); } 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++) { switch(_sectorPrefixDdt[i] & CD_XFIX_MASK) { case (uint)CdFixFlags.NotDumped: notDumpedPrefixes++; break; case (uint)CdFixFlags.Correct: correctPrefixes++; break; default: { if((_sectorPrefixDdt[i] & CD_DFIX_MASK) > 0) writtenPrefixes++; break; } } } for(long i = 0; i < _sectorPrefixDdt.LongLength; i++) { switch(_sectorSuffixDdt[i] & CD_XFIX_MASK) { case (uint)CdFixFlags.NotDumped: notDumpedSuffixes++; break; case (uint)CdFixFlags.Correct: correctSuffixes++; break; case (uint)CdFixFlags.Mode2Form1Ok: correctMode2Form1++; break; case (uint)CdFixFlags.Mode2Form2Ok: correctMode2Form2++; break; case (uint)CdFixFlags.Mode2Form2NoCrc: emptyMode2Form1++; break; default: { if((_sectorSuffixDdt[i] & CD_DFIX_MASK) > 0) writtenSuffixes++; break; } } } AaruConsole.DebugWriteLine(MODULE_NAME, Localization. _0_1_prefixes_are_correct_2_3_prefixes_have_not_been_dumped_4_5_prefixes_have_been_written_to_image, correctPrefixes, correctPrefixes / _imageInfo.Sectors, notDumpedPrefixes, notDumpedPrefixes / _imageInfo.Sectors, writtenPrefixes, writtenPrefixes / _imageInfo.Sectors); AaruConsole.DebugWriteLine(MODULE_NAME, Localization. _0_1_suffixes_are_correct_2_3_suffixes_have_not_been_dumped_4_5_suffixes_have_been_written_to_image, correctSuffixes, correctSuffixes / _imageInfo.Sectors, notDumpedSuffixes, notDumpedSuffixes / _imageInfo.Sectors, writtenSuffixes, writtenSuffixes / _imageInfo.Sectors); AaruConsole.DebugWriteLine(MODULE_NAME, Localization. _0_1_MODE_2_Form_1_are_correct_2_3_MODE_2_Form_2_are_correct_4_5_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(MODULE_NAME, Localization.Writing_CompactDisc_sector_prefix_DDT_to_position_0, idxEntry.offset); var ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.CdSectorPrefixCorrected, compression = _compressionAlgorithm, entries = (ulong)_sectorPrefixDdt.LongLength, length = (ulong)(_sectorPrefixDdt.LongLength * sizeof(uint)) }; _crc64 = new Crc64Context(); byte[] ddtEntries = MemoryMarshal.Cast(_sectorPrefixDdt).ToArray(); _crc64.Update(ddtEntries); var cmpBuffer = new byte[ddtEntries.Length + 262144]; int cmpLen; byte[] lzmaProperties = Array.Empty(); switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(ddtEntries, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = ddtEntries; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); ddtHeader.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) ddtHeader.cmpLength += LZMA_PROPERTIES_LENGTH; var cmpCrc64Context = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; if(_compressionAlgorithm == CompressionType.Lzma) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DeDuplicationTable, dataType : DataType.CdSectorPrefixCorrected }); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DeDuplicationTable, dataType = DataType.CdSectorSuffixCorrected, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_CompactDisc_sector_suffix_DDT_to_position_0, idxEntry.offset); ddtHeader = new DdtHeader { identifier = BlockType.DeDuplicationTable, type = DataType.CdSectorSuffixCorrected, compression = _compressionAlgorithm, entries = (ulong)_sectorSuffixDdt.LongLength, length = (ulong)(_sectorSuffixDdt.LongLength * sizeof(uint)) }; _crc64 = new Crc64Context(); ddtEntries = MemoryMarshal.Cast(_sectorSuffixDdt).ToArray(); _crc64.Update(ddtEntries); cmpBuffer = new byte[ddtEntries.Length + 262144]; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(ddtEntries, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = ddtEntries; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); ddtHeader.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) ddtHeader.cmpLength += LZMA_PROPERTIES_LENGTH; cmpCrc64Context = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc64Context.Update(lzmaProperties); cmpCrc64Context.Update(blockStream.ToArray()); ddtHeader.cmpCrc64 = BitConverter.ToUInt64(cmpCrc64Context.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in ddtHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _structureBytes = null; if(_compressionAlgorithm == CompressionType.Lzma) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DeDuplicationTable, dataType : DataType.CdSectorSuffixCorrected }); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorPrefixCorrected, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 { compressStopwatch.Restart(); byte[] sectorPrefixBuffer = _sectorPrefixMs.ToArray(); cmpBuffer = new byte[sectorPrefixBuffer.Length + 262144]; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(sectorPrefixBuffer, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = sectorPrefixBuffer; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); prefixBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) prefixBlock.cmpLength += LZMA_PROPERTIES_LENGTH; prefixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); prefixBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_prefix, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType : DataType.CdSectorPrefixCorrected }); _index.Add(idxEntry); idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSuffixCorrected, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 { compressStopwatch.Restart(); byte[] sectorSuffixBuffer = _sectorPrefixMs.ToArray(); cmpBuffer = new byte[sectorSuffixBuffer.Length + 262144]; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(sectorSuffixBuffer, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = sectorSuffixBuffer; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); suffixBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) suffixBlock.cmpLength += LZMA_PROPERTIES_LENGTH; suffixBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); suffixBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_suffix, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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.Close(); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType : DataType.CdSectorSuffixCorrected }); _index.Add(idxEntry); } if(_mode2Subheaders != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CompactDiscMode2Subheader, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 MemoryStream(_mode2Subheaders); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_mode2Subheaders.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_mode2Subheaders, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _mode2Subheaders; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subheaderBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) subheaderBlock.cmpLength += LZMA_PROPERTIES_LENGTH; subheaderBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subheaderBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_MODE2_subheaders, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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 is { blockType: BlockType.DataBlock, dataType : DataType.CompactDiscMode2Subheader }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorSubchannel != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.CdSectorSubchannel, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 MemoryStream(_sectorSubchannel); } else { compressStopwatch.Restart(); byte[] transformedSubchannel = ClauniaSubchannelTransform(_sectorSubchannel); var cmpBuffer = new byte[transformedSubchannel.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(transformedSubchannel, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = transformedSubchannel; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subchannelBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) subchannelBlock.cmpLength += LZMA_PROPERTIES_LENGTH; subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subchannelBlock.compression = _compressionAlgorithm == CompressionType.Lzma ? CompressionType.LzmaClauniaSubchannelTransform : _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_subchannel, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in subchannelBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(subchannelBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.CdSectorSubchannel }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorCprMai != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorCprMai, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_DVD_CPR_MAI_block_to_position_0, idxEntry.offset); Crc64Context.Data(_sectorCprMai, out byte[] blockCrc); var cprMaiBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorCprMai, length = (uint)_sectorCprMai.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 6 }; byte[] lzmaProperties = null; if(!_compress) { cprMaiBlock.compression = CompressionType.None; cprMaiBlock.cmpCrc64 = cprMaiBlock.crc64; cprMaiBlock.cmpLength = cprMaiBlock.length; blockStream = new MemoryStream(_sectorCprMai); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorCprMai.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorCprMai, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorCprMai; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); cprMaiBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) cprMaiBlock.cmpLength += LZMA_PROPERTIES_LENGTH; cprMaiBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); cprMaiBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_CPR_MAI, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in cprMaiBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(cprMaiBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.DvdSectorCprMai }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorId != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorId, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_DVD_ID_block_to_position_0, idxEntry.offset); Crc64Context.Data(_sectorId, out byte[] blockCrc); var idBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorId, length = (uint)_sectorId.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 4 }; byte[] lzmaProperties = null; if(!_compress) { idBlock.compression = CompressionType.None; idBlock.cmpCrc64 = idBlock.crc64; idBlock.cmpLength = idBlock.length; blockStream = new MemoryStream(_sectorId); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorId.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorId, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorId; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); idBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) idBlock.cmpLength += LZMA_PROPERTIES_LENGTH; idBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); idBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_ID, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in idBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(idBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.DvdSectorId }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorIed != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorIed, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_DVD_IED_block_to_position_0, idxEntry.offset); Crc64Context.Data(_sectorIed, out byte[] blockCrc); var iedBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorIed, length = (uint)_sectorIed.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 2 }; byte[] lzmaProperties = null; if(!_compress) { iedBlock.compression = CompressionType.None; iedBlock.cmpCrc64 = iedBlock.crc64; iedBlock.cmpLength = iedBlock.length; blockStream = new MemoryStream(_sectorIed); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorIed.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorIed, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorIed; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); iedBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) iedBlock.cmpLength += LZMA_PROPERTIES_LENGTH; iedBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); iedBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_IED, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in iedBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(iedBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.DvdSectorIed }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorEdc != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorEdc, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_DVD_EDC_block_to_position_0, idxEntry.offset); Crc64Context.Data(_sectorEdc, out byte[] blockCrc); var edcBlock = new BlockHeader { identifier = BlockType.DataBlock, type = DataType.DvdSectorEdc, length = (uint)_sectorEdc.Length, crc64 = BitConverter.ToUInt64(blockCrc, 0), sectorSize = 4 }; byte[] lzmaProperties = null; if(!_compress) { edcBlock.compression = CompressionType.None; edcBlock.cmpCrc64 = edcBlock.crc64; edcBlock.cmpLength = edcBlock.length; blockStream = new MemoryStream(_sectorEdc); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorEdc.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorEdc, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorEdc; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); edcBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) edcBlock.cmpLength += LZMA_PROPERTIES_LENGTH; edcBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); edcBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_EDC, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in edcBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(edcBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType: DataType.DvdSectorEdc }); _index.Add(idxEntry); blockStream.Close(); } if(_sectorDecryptedTitleKey != null) { idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = DataType.DvdSectorTitleKeyDecrypted, offset = (ulong)_imageStream.Position }; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 MemoryStream(_sectorDecryptedTitleKey); } else { compressStopwatch.Restart(); var cmpBuffer = new byte[_sectorDecryptedTitleKey.Length + 262144]; int cmpLen; switch(_compressionAlgorithm) { case CompressionType.Lzma: cmpLen = LZMA.EncodeBuffer(_sectorDecryptedTitleKey, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273); break; case CompressionType.None: cmpBuffer = _sectorDecryptedTitleKey; cmpLen = cmpBuffer.Length; break; default: throw new ArgumentOutOfRangeException(); } blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); titleKeyBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) titleKeyBlock.cmpLength += LZMA_PROPERTIES_LENGTH; titleKeyBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); titleKeyBlock.compression = _compressionAlgorithm; compressStopwatch.Stop(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Took_0_seconds_to_compress_decrypted_DVD_title_keys, compressStopwatch.Elapsed.TotalSeconds); } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in titleKeyBlock); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); if(titleKeyBlock.compression is CompressionType.Lzma or CompressionType.LzmaClauniaSubchannelTransform) _imageStream.Write(lzmaProperties, 0, lzmaProperties.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); _index.RemoveAll(t => t is { blockType: BlockType.DataBlock, dataType : DataType.DvdSectorTitleKeyDecrypted }); _index.Add(idxEntry); blockStream.Close(); } List trackEntries = new(); List compactDiscIndexEntries = new(); foreach(Track track in Tracks) { _trackFlags.TryGetValue((byte)track.Sequence, out byte flags); _trackIsrcs.TryGetValue((byte)track.Sequence, out string isrc); if((flags & (int)CdFlags.DataTrack) == 0 && track.Type != TrackType.Audio) flags += (byte)CdFlags.DataTrack; trackEntries.Add(new TrackEntry { sequence = (byte)track.Sequence, type = track.Type, start = (long)track.StartSector, end = (long)track.EndSector, pregap = (long)track.Pregap, session = (byte)track.Session, isrc = isrc, flags = flags }); switch(track.Indexes.ContainsKey(0)) { case false when track.Pregap > 0: track.Indexes[0] = (int)track.StartSector; track.Indexes[1] = (int)(track.StartSector + track.Pregap); break; case false when !track.Indexes.ContainsKey(1): track.Indexes[0] = (int)track.StartSector; break; } compactDiscIndexEntries.AddRange(track.Indexes.Select(trackIndex => new CompactDiscIndexEntry { Index = trackIndex.Key, Lba = trackIndex.Value, Track = (ushort)track.Sequence })); } // If there are tracks build the tracks block if(trackEntries.Count > 0) { blockStream = new MemoryStream(); 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(MODULE_NAME, Localization.Writing_tracks_to_position_0, _imageStream.Position); _index.RemoveAll(t => t is { blockType: BlockType.TracksBlock, 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, in trkHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); } // If there are track indexes bigger than 1 if(compactDiscIndexEntries.Any(i => i.Index > 1)) { blockStream = new MemoryStream(); 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(MODULE_NAME, Localization.Writing_compact_disc_indexes_to_position_0, _imageStream.Position); _index.RemoveAll(t => t is { blockType: BlockType.CompactDiscIndexesBlock, 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, in cdixHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); } break; case MetadataMediaType.BlockMedia: if(_sectorSubchannel != null && _imageInfo.MediaType is MediaType.AppleFileWare or MediaType.AppleSonySS or MediaType.AppleSonyDS or MediaType.AppleProfile or MediaType.AppleWidget or 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(MODULE_NAME, Localization.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 MemoryStream(_sectorSubchannel); } else { var cmpBuffer = new byte[_sectorSubchannel.Length + 262144]; int cmpLen = _compressionAlgorithm switch { CompressionType.Lzma => LZMA.EncodeBuffer(_sectorSubchannel, cmpBuffer, out lzmaProperties, 9, _dictionarySize, 4, 0, 2, 273), _ => throw new ArgumentOutOfRangeException() }; blockStream = new MemoryStream(cmpBuffer, 0, cmpLen); var cmpCrc = new Crc64Context(); if(_compressionAlgorithm == CompressionType.Lzma) cmpCrc.Update(lzmaProperties); cmpCrc.Update(blockStream.ToArray()); blockCrc = cmpCrc.Final(); subchannelBlock.cmpLength = (uint)blockStream.Length; if(_compressionAlgorithm == CompressionType.Lzma) subchannelBlock.cmpLength += LZMA_PROPERTIES_LENGTH; subchannelBlock.cmpCrc64 = BitConverter.ToUInt64(blockCrc, 0); subchannelBlock.compression = _compressionAlgorithm; } _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in 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.Close(); } break; } // Write metadata if present SetMetadataFromTags(); var metadataBlock = new MetadataBlock(); blockStream = new MemoryStream(); blockStream.Write(new byte[Marshal.SizeOf()], 0, Marshal.SizeOf()); byte[] tmpUtf16Le; if(_imageInfo is { MediaSequence: > 0, 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(MODULE_NAME, Localization.Writing_metadata_to_position_0, _imageStream.Position); metadataBlock.blockSize = (uint)blockStream.Length; _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, in metadataBlock); blockStream.Position = 0; blockStream.Write(_structureBytes, 0, _structureBytes.Length); _index.RemoveAll(t => t is { blockType: BlockType.MetadataBlock, dataType: DataType.NoData }); _index.Add(new IndexEntry { blockType = BlockType.MetadataBlock, dataType = DataType.NoData, offset = (ulong)_imageStream.Position }); _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); } _header.indexOffset = (ulong)_imageStream.Position; AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Writing_index_to_position_0, _header.indexOffset); blockStream = new MemoryStream(); // Write index to memory foreach(IndexEntry entry in _index) { _structureBytes = new byte[Marshal.SizeOf()]; IndexEntry indexEntry = entry; MemoryMarshal.Write(_structureBytes, in 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, in 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, in idxHeader); _imageStream.Write(_structureBytes, 0, _structureBytes.Length); } // Write index to disk _imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); blockStream.Close(); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.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 SetImageInfo(ImageInfo imageInfo) { _imageInfo.Creator = imageInfo.Creator; _imageInfo.Comments = imageInfo.Comments; _imageInfo.MediaManufacturer = imageInfo.MediaManufacturer; _imageInfo.MediaModel = imageInfo.MediaModel; _imageInfo.MediaSerialNumber = imageInfo.MediaSerialNumber; _imageInfo.MediaBarcode = imageInfo.MediaBarcode; _imageInfo.MediaPartNumber = imageInfo.MediaPartNumber; _imageInfo.MediaSequence = imageInfo.MediaSequence; _imageInfo.LastMediaSequence = imageInfo.LastMediaSequence; _imageInfo.DriveManufacturer = imageInfo.DriveManufacturer; _imageInfo.DriveModel = imageInfo.DriveModel; _imageInfo.DriveSerialNumber = imageInfo.DriveSerialNumber; _imageInfo.DriveFirmwareRevision = imageInfo.DriveFirmwareRevision; _imageInfo.MediaTitle = imageInfo.MediaTitle; return true; } /// public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) { if(!IsWriting) { ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image; return false; } if(_imageInfo.MetadataMediaType != MetadataMediaType.BlockMedia) { ErrorMessage = Localization.Tried_to_set_geometry_on_a_media_that_doesnt_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 = Localization.Tried_to_write_on_a_non_writable_image; return false; } if(sectorAddress >= _imageInfo.Sectors) { ErrorMessage = Localization.Tried_to_write_past_image_size; return false; } Track track; switch(tag) { case SectorTagType.CdTrackFlags: case SectorTagType.CdTrackIsrc: if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) { ErrorMessage = Localization.Incorrect_tag_for_disk_type; return false; } track = Tracks.FirstOrDefault(trk => sectorAddress == trk.Sequence); if(track is null || track.Sequence == 0 && track.StartSector == 0 && track.EndSector == 0) { ErrorMessage = string.Format(Localization.Cant_find_track_0, sectorAddress); return false; } break; case SectorTagType.CdSectorSubchannel: if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) { ErrorMessage = Localization.Incorrect_tag_for_disk_type; return false; } track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.StartSector && sectorAddress <= trk.EndSector); if(track is { Sequence: 0, StartSector: 0, EndSector: 0 }) track.Type = TrackType.Data; break; } switch(tag) { case SectorTagType.CdTrackFlags: { if(data.Length != 1) { ErrorMessage = Localization.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 = Localization.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.DvdSectorCmi: { if(data.Length != 1) { ErrorMessage = Localization.Incorrect_data_size_for_CMI; return false; } _sectorCprMai ??= new byte[_imageInfo.Sectors * 6]; Array.Copy(data, 0, _sectorCprMai, (int)(6 * sectorAddress), 1); return true; } case SectorTagType.DvdSectorTitleKey: { if(data.Length != 5) { ErrorMessage = Localization.Incorrect_data_size_for_title_key; return false; } _sectorCprMai ??= new byte[_imageInfo.Sectors * 6]; Array.Copy(data, 0, _sectorCprMai, (int)(1 + 6 * sectorAddress), 5); return true; } case SectorTagType.DvdSectorInformation: { if(data.Length != 1) { ErrorMessage = Localization.Incorrect_data_size_for_dvd_id_information; return false; } _sectorId ??= new byte[_imageInfo.Sectors * 4]; Array.Copy(data, 0, _sectorId, (int)(4 * sectorAddress), 1); return true; } case SectorTagType.DvdSectorNumber: { if(data.Length != 3) { ErrorMessage = Localization.Incorrect_data_size_for_dvd_id_number; return false; } _sectorId ??= new byte[_imageInfo.Sectors * 4]; Array.Copy(data, 0, _sectorId, (int)(1 + 4 * sectorAddress), 3); return true; } case SectorTagType.DvdSectorIed: { if(data.Length != 2) { ErrorMessage = Localization.Incorrect_data_size_for_ied; return false; } _sectorIed ??= new byte[_imageInfo.Sectors * 2]; Array.Copy(data, 0, _sectorIed, (int)(2 * sectorAddress), 2); return true; } case SectorTagType.DvdSectorEdc: { if(data.Length != 4) { ErrorMessage = Localization.Incorrect_data_size_for_edc; return false; } _sectorEdc ??= new byte[_imageInfo.Sectors * 4]; Array.Copy(data, 0, _sectorEdc, (int)(4 * sectorAddress), 4); return true; } case SectorTagType.DvdTitleKeyDecrypted: { if(data.Length != 5) { ErrorMessage = Localization.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 = string.Format(Localization.Dont_know_how_to_write_sector_tag_type_0, tag); return false; } } /// public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) { if(!IsWriting) { ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image; return false; } if(sectorAddress + length > _imageInfo.Sectors) { ErrorMessage = Localization.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 = Localization.Incorrect_data_size_for_subchannel; return false; } _sectorSubchannel ??= new byte[_imageInfo.Sectors * 96]; if(sectorAddress * 96 + length * 96 > (ulong)_sectorSubchannel.LongLength) { ErrorMessage = Localization.Tried_to_write_more_data_than_possible; return false; } Array.Copy(data, 0, _sectorSubchannel, (int)(96 * sectorAddress), 96 * length); return true; } default: ErrorMessage = string.Format(Localization.Dont_know_how_to_write_sector_tag_type_0, tag); return false; } } /// public bool SetDumpHardware(List dumpHardware) { DumpHardware = dumpHardware; return true; } /// public bool SetMetadata(Metadata metadata) { AaruMetadata = metadata; return true; } #endregion }