// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : LeadOuts.cs // Author(s) : Natalia Portillo // // Component : CompactDisc dumping. // // --[ Description ] ---------------------------------------------------------- // // Dumps CompactDisc Lead-Out. // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ // ReSharper disable JoinDeclarationAndInitializer // ReSharper disable InlineOutVariableDeclaration // ReSharper disable TooWideLocalVariableScope using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Interfaces; using Aaru.Core.Logging; using Aaru.Devices; using Aaru.Logging; using Humanizer; using Humanizer.Bytes; using Track = Aaru.CommonTypes.Structs.Track; namespace Aaru.Core.Devices.Dumping; partial class Dump { /// Dumps inter-session lead-outs /// Size of the read sector in bytes /// Current read speed /// Current dump hardware try /// Extents /// IMGBurn log /// Duration of image write /// Lead-out extents /// Maximum speed /// MHDD log /// Minimum speed /// Device supports READ(6) /// Device supports READ(10) /// Device supports READ(12) /// Device supports READ(16) /// Device supports READ CD /// Drive's maximum supported subchannel /// Subchannel size in bytes /// Total commands duration /// Disc tracks /// Subchannel log /// Subchannel desired to save /// List of disc ISRCs /// Disc media catalogue number /// List of subchannels not yet dumped correctly /// List of smallest pregap relative address per track // TODO: Use it [SuppressMessage("ReSharper", "UnusedMember.Local")] void DumpCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16, bool readcd, MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration, SubchannelLog subLog, MmcSubchannel desiredSubchannel, Dictionary isrcs, ref string mcn, Track[] tracks, HashSet subchannelExtents, Dictionary smallestPregapLbaPerTrack) { byte[] cmdBuf = null; // Data buffer const uint sectorSize = 2352; // Full sector size bool sense = true; // Sense indicator ReadOnlySpan senseBuf = null; var outputOptical = _outputPlugin as IWritableOpticalImage; UpdateStatus?.Invoke(Localization.Core.Reading_lead_outs); InitProgress?.Invoke(); foreach((ulong item1, ulong item2) in leadOutExtents.ToArray()) { for(ulong i = item1; i <= item2; i++) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); AaruLogging.WriteLine(Localization.Core.Aborted); break; } double cmdDuration = 0; if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed; PulseProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_at_lead_out_1, i, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize())); if(readcd) { sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); totalDuration += cmdDuration; } else if(read16) { sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, i, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); } else if(read12) { sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); } else if(read10) { sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0, 1, _dev.Timeout, out cmdDuration); } else if(read6) sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, _dev.Timeout, out cmdDuration); if(!sense && !_dev.Error) { mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); extents.Add(i, _maximumReadable, true); leadOutExtents.Remove(i); _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { byte[] data = new byte[sectorSize * _maximumReadable]; byte[] sub = new byte[subSize * _maximumReadable]; for(int b = 0; b < _maximumReadable; b++) { Array.Copy(cmdBuf, (int)(0 + b * blockSize), data, sectorSize * b, sectorSize); Array.Copy(cmdBuf, (int)(sectorSize + b * blockSize), sub, subSize * b, subSize); } outputOptical.WriteSectorsLong(data, i, _maximumReadable); bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i, _maximumReadable, subLog, isrcs, 0xAA, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel, _fixSubchannelCrc, UpdateStatus, smallestPregapLbaPerTrack, true, out _); // Set tracks and go back if(indexesChanged) { outputOptical.SetTracks(tracks.ToList()); i--; continue; } } else outputOptical.WriteSectors(cmdBuf, i, _maximumReadable); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; } else { _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf.ToArray()); // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly // Write empty data _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { outputOptical.WriteSectorsLong(new byte[sectorSize * _skip], i, 1); outputOptical.WriteSectorsTag(new byte[subSize * _skip], i, 1, SectorTagType.CdSectorSubchannel); } else outputOptical.WriteSectors(new byte[blockSize * _skip], i, 1); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip); ibgLog.Write(i, 0); } _writeStopwatch.Stop(); double newSpeed = (double)blockSize * _maximumReadable / 1048576 / (cmdDuration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; _resume.NextBlock = i + 1; } } EndProgress?.Invoke(); } /// Retries inter-session lead-outs /// Size of the read sector in bytes /// Current read speed /// Current dump hardware try /// Extents /// IMGBurn log /// Duration of image write /// Lead-out extents /// Maximum speed /// MHDD log /// Minimum speed /// Device supports READ(6) /// Device supports READ(10) /// Device supports READ(12) /// Device supports READ(16) /// Device supports READ CD /// Drive's maximum supported subchannel /// Subchannel size in bytes /// Total commands duration /// Disc tracks /// Subchannel log /// Subchannel desired to save /// List of disc ISRCs /// Disc media catalogue number /// List of subchannels not yet dumped correctly /// List of smallest pregap relative address per track // TODO: Use it [SuppressMessage("ReSharper", "UnusedMember.Local")] void RetryCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16, bool readcd, MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration, SubchannelLog subLog, MmcSubchannel desiredSubchannel, Dictionary isrcs, ref string mcn, Track[] tracks, HashSet subchannelExtents, Dictionary smallestPregapLbaPerTrack) { byte[] cmdBuf = null; // Data buffer const uint sectorSize = 2352; // Full sector size bool sense = true; // Sense indicator ReadOnlySpan senseBuf = null; var outputOptical = _outputPlugin as IWritableOpticalImage; AaruLogging.WriteLine("Retrying lead-outs"); InitProgress?.Invoke(); foreach((ulong item1, ulong item2) in leadOutExtents.ToArray()) { for(ulong i = item1; i <= item2; i++) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); AaruLogging.WriteLine(Localization.Core.Aborted); break; } double cmdDuration = 0; if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed; PulseProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_at_lead_out_1, i, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize())); if(readcd) { sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); totalDuration += cmdDuration; } else if(read16) { sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, i, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); } else if(read12) { sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); } else if(read10) { sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0, 1, _dev.Timeout, out cmdDuration); } else if(read6) sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, _dev.Timeout, out cmdDuration); if(!sense && !_dev.Error) { mhddLog.Write(i, cmdDuration, _maximumReadable); ibgLog.Write(i, currentSpeed * 1024); extents.Add(i, _maximumReadable, true); leadOutExtents.Remove(i); _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { byte[] data = new byte[sectorSize * _maximumReadable]; byte[] sub = new byte[subSize * _maximumReadable]; for(int b = 0; b < _maximumReadable; b++) { Array.Copy(cmdBuf, (int)(0 + b * blockSize), data, sectorSize * b, sectorSize); Array.Copy(cmdBuf, (int)(sectorSize + b * blockSize), sub, subSize * b, subSize); } outputOptical.WriteSectorsLong(data, i, _maximumReadable); bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i, _maximumReadable, subLog, isrcs, 0xAA, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel, _fixSubchannelCrc, UpdateStatus, smallestPregapLbaPerTrack, true, out _); // Set tracks and go back if(indexesChanged) { outputOptical.SetTracks(tracks.ToList()); i--; continue; } } else outputOptical.WriteSectors(cmdBuf, i, _maximumReadable); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; } else { _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf.ToArray()); // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly // Write empty data _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { outputOptical.WriteSectorsLong(new byte[sectorSize * _skip], i, 1); if(desiredSubchannel != MmcSubchannel.None) { outputOptical.WriteSectorsTag(new byte[subSize * _skip], i, 1, SectorTagType.CdSectorSubchannel); } } else outputOptical.WriteSectors(new byte[blockSize * _skip], i, 1); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i, 0); } _writeStopwatch.Stop(); double newSpeed = (double)blockSize * _maximumReadable / 1048576 / (cmdDuration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; } } EndProgress?.Invoke(); } }