// /*************************************************************************** // 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-2025 Natalia Portillo // Copyright © 2020-2025 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.Decoders; using Aaru.Decoders.CD; using Aaru.Helpers; using Aaru.Logging; 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++; } AaruLogging.Debug(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 ex) { ErrorMessage = string.Format(Localization.Could_not_create_new_image_file_exception_0, ex.Message); AaruLogging.Exception(ex, Localization.Could_not_create_new_image_file_exception_0, ex.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 = []; _mediaTags = new Dictionary(); _checksumProvider = SHA256.Create(); _deduplicationTable = new Dictionary(); _trackIsrcs = new Dictionary(); _trackFlags = new Dictionary(); _imageInfo.ReadableSectorTags = []; // 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; } AaruLogging.Debug(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); AaruLogging.Debug(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 }); bool foundUserDataDdt = false; foreach(IndexEntry entry in _index) { _imageStream.Position = (long)entry.offset; switch(entry.blockType) { case BlockType.DataBlock: // NOP block, skip if(entry.dataType == DataType.NoData) break; _imageStream.Position = (long)entry.offset; _structureBytes = new byte[Marshal.SizeOf()]; _imageStream.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) { AaruLogging.Debug(MODULE_NAME, Localization.Incorrect_identifier_for_data_block_at_position_0, entry.offset); break; } if(blockHeader.type != entry.dataType) { AaruLogging.Debug(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; AaruLogging.Debug(MODULE_NAME, Localization.Found_data_block_type_0_at_position_1, entry.dataType, entry.offset); AaruLogging.Debug(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) { AaruLogging.Debug(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(); byte[] compressedTag = new byte[blockHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] 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(); AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds_to_decompress_block, decompressStopwatch.Elapsed.TotalSeconds); AaruLogging.Debug(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); AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); } else { AaruLogging.Debug(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) { AaruLogging.Debug(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); AaruLogging.Debug(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); AaruLogging.Debug(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); AaruLogging.Debug(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); AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.CompactDiscMode2Subheader: _mode2Subheaders = data; AaruLogging.Debug(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); AaruLogging.Debug(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); AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.DvdSectorId: _sectorId = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorInformation)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorInformation); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorNumber)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorNumber); AaruLogging.Debug("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.DvdSectorIed: _sectorIed = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorIed)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorIed); AaruLogging.Debug("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; case DataType.DvdSectorEdc: _sectorEdc = data; if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorEdc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorEdc); AaruLogging.Debug("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); break; default: MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); if(_mediaTags.ContainsKey(mediaTagType)) { AaruLogging.Debug(MODULE_NAME, Localization .Media_tag_type_0_duplicated_removing_previous_entry, mediaTagType); _mediaTags.Remove(mediaTagType); } _mediaTags.Add(mediaTagType, data); AaruLogging.Debug(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: AaruLogging.Debug(MODULE_NAME, Localization.Decompressing_DDT); var ddtStopwatch = new Stopwatch(); ddtStopwatch.Start(); byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; _imageStream.EnsureRead(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); _imageStream.EnsureRead(compressedDdt, 0, compressedDdt.Length); byte[] decompressedDdt = new byte[ddtHeader.length]; ulong 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; AaruLogging.Debug(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; } byte[] decompressedDdt = new byte[ddtHeader.length]; switch(ddtHeader.compression) { case CompressionType.Lzma: AaruLogging.Debug(MODULE_NAME, Localization.Decompressing_DDT); var ddtStopwatch = new Stopwatch(); ddtStopwatch.Start(); byte[] compressedDdt = new byte[ddtHeader.cmpLength - LZMA_PROPERTIES_LENGTH]; byte[] lzmaProperties = new byte[LZMA_PROPERTIES_LENGTH]; _imageStream.EnsureRead(lzmaProperties, 0, LZMA_PROPERTIES_LENGTH); _imageStream.EnsureRead(compressedDdt, 0, compressedDdt.Length); ulong 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(); AaruLogging.Debug(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) { AaruLogging.Debug(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; AaruLogging.Debug(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) { AaruLogging.Debug(MODULE_NAME, Localization.Incorrect_identifier_for_data_block_at_position_0, entry.offset); break; } AaruLogging.Debug(MODULE_NAME, Localization.Found_metadata_block_at_position_0, entry.offset); byte[] 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; AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(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)); AaruLogging.Debug(MODULE_NAME, Localization.Setting_drive_firmware_revision_0, _imageInfo.DriveFirmwareRevision); } AaruLogging.Debug(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) { AaruLogging.Debug(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) { AaruLogging.Debug(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 = []; _trackFlags = new Dictionary(); _trackIsrcs = new Dictionary(); AaruLogging.Debug(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; AaruLogging.Debug(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; AaruLogging.Debug(MODULE_NAME, Localization.Found_CICM_XML_metadata_block_at_position_0, entry.offset); byte[] 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) { AaruLogging.Debug(MODULE_NAME, Localization.Exception_0_processing_CICM_XML_metadata_block, ex.Message); AaruLogging.Exception(ex, 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; AaruLogging.Debug(MODULE_NAME, Localization.Found_Aaru_Metadata_block_at_position_0, entry.offset); AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); byte[] 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) { AaruLogging.Debug(MODULE_NAME, Localization.Exception_0_processing_Aaru_Metadata_block, ex.Message); AaruMetadata = null; } AaruLogging.Debug(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; AaruLogging.Debug(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) { AaruLogging.Debug(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 = []; 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 = [] }; 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; AaruLogging.Debug(MODULE_NAME, Localization.Found_tape_partition_block_at_position_0, entry.offset); byte[] tapePartitionBytes = new byte[partitionHeader.length]; _imageStream.EnsureRead(tapePartitionBytes, 0, tapePartitionBytes.Length); Span tapePartitions = MemoryMarshal.Cast(tapePartitionBytes); TapePartitions = []; 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; AaruLogging.Debug(MODULE_NAME, Localization.Found_tape_file_block_at_position_0, entry.offset); byte[] tapeFileBytes = new byte[fileHeader.length]; _imageStream.EnsureRead(tapeFileBytes, 0, tapeFileBytes.Length); Span tapeFiles = MemoryMarshal.Cast(tapeFileBytes); Files = []; 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) { AaruLogging.Debug(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) { AaruLogging.Debug(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 = []; AaruLogging.Debug(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)); } AaruLogging.Debug(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)) { byte[] 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; } } } AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); if(Tracks == null || Tracks.Count == 0) { Tracks = [ new Track { 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(); } AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); Sessions = []; for(int 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) }); } AaruLogging.Debug(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 = []; 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; } AaruLogging.Debug(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; } AaruLogging.Debug(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); Tracks = tracks.ToList(); AaruLogging.Debug(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(); } AaruLogging.Debug(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 = []; int 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(int 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(); } ulong 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; } uint sectorSize = (uint)(data.Length / length); for(uint i = 0; i < length; i++) { byte[] tmp = new byte[sectorSize]; Array.Copy(data, i * sectorSize, tmp, 0, sectorSize); if(!WriteSector(tmp, sectorAddress + i)) return false; } ErrorMessage = ""; return true; } /// public bool WriteSectorLong(byte[] data, ulong sectorAddress) { if(!IsWriting) { ErrorMessage = 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 is MediaType.DVDROM or MediaType.PS2DVD or MediaType.SACD or MediaType.PS3DVD or MediaType.DVDR or MediaType.DVDRW or MediaType.DVDPR or MediaType.DVDPRW or MediaType.DVDPRWDL or MediaType.DVDRDL or MediaType.DVDPRDL or MediaType.DVDRAM or MediaType.DVDRWDL or MediaType.DVDDownload or MediaType.Nuon) { 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); uint edc = BitConverter.ToUInt32(data, 0x92C); bool correctEdc = computedEdc == edc; sector = new byte[2324]; _sectorSuffixDdt ??= new uint[_imageInfo.Sectors]; Array.Copy(data, 24, sector, 0, 2324); if(correctEdc) _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2Ok; else if(BitConverter.ToUInt32(data, 0x92C) == 0) _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form2NoCrc; else { if((_sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) > 0) { _sectorSuffixMs.Position = ((_sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) - 1) * 288; } else _sectorSuffixMs.Seek(0, SeekOrigin.End); _sectorSuffixDdt[sectorAddress] = (uint)(_sectorSuffixMs.Position / 288 + 1); _sectorSuffixMs.Write(data, 2348, 4); } } else { bool correctEcc = SuffixIsCorrectMode2(data); uint computedEdc = ComputeEdc(0, data, 0x808, 0x10); uint edc = BitConverter.ToUInt32(data, 0x818); bool correctEdc = computedEdc == edc; sector = new byte[2048]; Array.Copy(data, 24, sector, 0, 2048); if(correctEcc && correctEdc) { _sectorSuffixDdt ??= new uint[_imageInfo.Sectors]; _sectorSuffixDdt[sectorAddress] = (uint)CdFixFlags.Mode2Form1Ok; } else { if((_sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) > 0) { _sectorSuffixMs.Position = ((_sectorSuffixDdt[sectorAddress] & CD_DFIX_MASK) - 1) * 288; } else _sectorSuffixMs.Seek(0, SeekOrigin.End); _sectorSuffixDdt[sectorAddress] = (uint)(_sectorSuffixMs.Position / 288 + 1); _sectorSuffixMs.Write(data, 2072, 280); } } Array.Copy(data, 16, _mode2Subheaders, (int)sectorAddress * 8, 8); return WriteSector(sector, sectorAddress); } break; case 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: case MediaType.PS2DVD: case MediaType.SACD: case MediaType.DVDR: case MediaType.DVDRW: case MediaType.DVDPR: case MediaType.DVDPRW: case MediaType.DVDPRWDL: case MediaType.DVDRDL: case MediaType.DVDPRDL: case MediaType.DVDRAM: case MediaType.DVDRWDL: case MediaType.DVDDownload: case MediaType.PS3DVD: case MediaType.Nuon: 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: int sectorSize = 0; if(data.Length % 524 == 0) sectorSize = 524; else if(data.Length % 532 == 0) sectorSize = 532; else if(data.Length % 536 == 0) sectorSize = 536; if(sectorSize == 0) { ErrorMessage = 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 = []; int 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(int 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) { AaruLogging.Debug(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) { AaruLogging.Error(Localization.Tag_type_0_is_null_skipping, dataType); continue; } idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = dataType, offset = (ulong)_imageStream.Position }; AaruLogging.Debug(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) }; byte[] cmpBuffer = new byte[mediaTag.Value.Length + 262144]; byte[] lzmaProperties = null; bool 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 }; AaruLogging.Debug(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 }; AaruLogging.Debug(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 }; AaruLogging.Debug(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 }; AaruLogging.Debug(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 }; AaruLogging.Debug(MODULE_NAME, Localization.Writing_tape_partitions_to_position_0, idxEntry.offset); var tapePartitionEntries = new TapePartitionEntry[TapePartitions.Count]; for(int t = 0; t < TapePartitions.Count; t++) { tapePartitionEntries[t] = new TapePartitionEntry { Number = TapePartitions[t].Number, FirstBlock = TapePartitions[t].FirstBlock, LastBlock = TapePartitions[t].LastBlock }; } byte[] tapePartitionEntriesData = MemoryMarshal.Cast(tapePartitionEntries).ToArray(); var tapePartitionHeader = new TapePartitionHeader { identifier = BlockType.TapePartitionBlock, entries = (byte)tapePartitionEntries.Length, length = (ulong)tapePartitionEntriesData.Length }; _crc64 = new Crc64Context(); _crc64.Update(tapePartitionEntriesData); tapePartitionHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, 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 }; AaruLogging.Debug(MODULE_NAME, Localization.Writing_tape_files_to_position_0, idxEntry.offset); var tapeFileEntries = new TapeFileEntry[Files.Count]; for(int t = 0; t < Files.Count; t++) { tapeFileEntries[t] = new TapeFileEntry { File = Files[t].File, FirstBlock = Files[t].FirstBlock, LastBlock = Files[t].LastBlock }; } byte[] tapeFileEntriesData = MemoryMarshal.Cast(tapeFileEntries).ToArray(); var tapeFileHeader = new TapeFileHeader { identifier = BlockType.TapeFileBlock, entries = (uint)tapeFileEntries.Length, length = (ulong)tapeFileEntriesData.Length }; _crc64 = new Crc64Context(); _crc64.Update(tapeFileEntriesData); tapeFileHeader.crc64 = BitConverter.ToUInt64(_crc64.Final(), 0); _structureBytes = new byte[Marshal.SizeOf()]; MemoryMarshal.Write(_structureBytes, 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 }; AaruLogging.Debug(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); byte[] 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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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; } } } AaruLogging.Debug(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); AaruLogging.Debug(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); AaruLogging.Debug(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 }; AaruLogging.Debug(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); byte[] cmpBuffer = new byte[ddtEntries.Length + 262144]; int cmpLen; byte[] lzmaProperties = []; 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 }; AaruLogging.Debug(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 }; AaruLogging.Debug(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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 }; AaruLogging.Debug(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(); byte[] 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(); AaruLogging.Debug(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 = []; List compactDiscIndexEntries = []; 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) }; AaruLogging.Debug(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) }; AaruLogging.Debug(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 = _imageInfo.MediaType switch { MediaType.AppleSonySS or MediaType.AppleSonyDS => DataType.AppleSonyTag, MediaType.AppleFileWare or MediaType.AppleProfile or MediaType.AppleWidget => DataType.AppleProfileTag, MediaType.PriamDataTower => DataType.PriamDataTowerTag, _ => DataType.NoData }; idxEntry = new IndexEntry { blockType = BlockType.DataBlock, dataType = tagType, offset = (ulong)_imageStream.Position }; AaruLogging.Debug(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) }; subchannelBlock.sectorSize = _imageInfo.MediaType switch { MediaType.AppleSonySS or MediaType.AppleSonyDS => 12, MediaType.AppleFileWare or MediaType.AppleProfile or MediaType.AppleWidget => 20, MediaType.PriamDataTower => 24, _ => subchannelBlock.sectorSize }; byte[] lzmaProperties = null; if(!_compress) { subchannelBlock.compression = CompressionType.None; subchannelBlock.cmpCrc64 = subchannelBlock.crc64; subchannelBlock.cmpLength = subchannelBlock.length; blockStream = new MemoryStream(_sectorSubchannel); } else { byte[] 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([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([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([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([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([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([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([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([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([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([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([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([0, 0], 0, 2); } // Check if we set up any metadata earlier, then write its block if(metadataBlock.identifier == BlockType.MetadataBlock) { AaruLogging.Debug(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; AaruLogging.Debug(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()]; MemoryMarshal.Write(_structureBytes, in entry); 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(); AaruLogging.Debug(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 }