// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Data.cs // Author(s) : Natalia Portillo // // Component : CompactDisc dumping. // // --[ Description ] ---------------------------------------------------------- // // Dumps user data part. // // --[ 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-2023 Natalia Portillo // ****************************************************************************/ // ReSharper disable JoinDeclarationAndInitializer // ReSharper disable InlineOutVariableDeclaration // ReSharper disable TooWideLocalVariableScope using System; using System.Collections.Generic; using System.IO; using System.Linq; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Interfaces; using Aaru.Console; using Aaru.Core.Logging; using Aaru.Decoders.CD; using Aaru.Decoders.SCSI; using Aaru.Devices; using Humanizer; using Humanizer.Bytes; using Track = Aaru.CommonTypes.Structs.Track; using TrackType = Aaru.CommonTypes.Enums.TrackType; namespace Aaru.Core.Devices.Dumping; partial class Dump { /// Reads all CD user data /// Extents with audio sectors /// Total number of positive sectors /// Size of the read sector in bytes /// Current read speed /// Current dump hardware try /// Extents /// IMGBurn log /// Duration of image write /// Last sector number /// Lead-out extents /// Maximum speed /// MHDD log /// Minimum speed /// Is trim a new one? /// Next cluster of sectors is all data /// Read offset /// Device supports READ(6) /// Device supports READ(10) /// Device supports READ(12) /// Device supports READ(16) /// Device supports READ CD /// Sectors needed to fix offset /// Subchannel size in bytes /// Drive's maximum supported subchannel /// Supports reading EDC and ECC /// 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 void ReadCdData(ExtentsULong audioExtents, ulong blocks, uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, long lastSector, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, out bool newTrim, bool nextData, int offsetBytes, bool read6, bool read10, bool read12, bool read16, bool readcd, int sectorsForOffset, uint subSize, MmcSubchannel supportedSubchannel, bool supportsLongSectors, ref double totalDuration, Track[] tracks, SubchannelLog subLog, MmcSubchannel desiredSubchannel, Dictionary isrcs, ref string mcn, HashSet subchannelExtents, Dictionary smallestPregapLbaPerTrack) { ulong sectorSpeedStart = 0; // Used to calculate correct speed uint blocksToRead; // How many sectors to read at once var sense = true; // Sense indicator byte[] cmdBuf = null; // Data buffer byte[] senseBuf = null; // Sense buffer double cmdDuration = 0; // Command execution time const uint sectorSize = 2352; // Full sector size newTrim = false; PlextorSubchannel supportedPlextorSubchannel; var outputFormat = _outputPlugin as IWritableImage; supportedPlextorSubchannel = supportedSubchannel switch { MmcSubchannel.None => PlextorSubchannel.None, MmcSubchannel.Raw => PlextorSubchannel.Pack, MmcSubchannel.Q16 => PlextorSubchannel.Q16, _ => PlextorSubchannel.None }; InitProgress?.Invoke(); int currentReadSpeed = _speed; var crossingLeadOut = false; var failedCrossingLeadOut = false; var skippingLead = false; for(ulong i = _resume.NextBlock; (long)i <= lastSector; i += blocksToRead) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke(Localization.Core.Aborted); _dumpLog.WriteLine(Localization.Core.Aborted); break; } while(leadOutExtents.Contains(i)) { skippingLead = true; i++; } if((long)i > lastSector) break; var firstSectorToRead = (uint)i; Track track = tracks.OrderBy(t => t.StartSector).LastOrDefault(t => i >= t.StartSector); blocksToRead = 0; bool inData = nextData; for(ulong j = i; j < i + _maximumReadable; j++) { if(j > (ulong)lastSector) { if(!failedCrossingLeadOut && !inData) blocksToRead += (uint)sectorsForOffset; if(sectorsForOffset > 0 && !inData) crossingLeadOut = true; break; } if(nextData) { if(audioExtents.Contains(j)) { nextData = false; break; } blocksToRead++; } else { if(!audioExtents.Contains(j)) { nextData = true; break; } blocksToRead++; } } if(track.Sequence != 0 && i + blocksToRead - (ulong)sectorsForOffset > track.EndSector + 1) blocksToRead = (uint)(track.EndSector + 1 - i + (ulong)sectorsForOffset); if(blocksToRead == 1 && !inData) blocksToRead += (uint)sectorsForOffset; if(blocksToRead == 0) { if(!skippingLead) i += (ulong)sectorsForOffset; skippingLead = false; continue; } if(_fixOffset && !inData) { if(offsetBytes < 0) { if(i == 0) firstSectorToRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1 else firstSectorToRead -= (uint)sectorsForOffset; if(blocksToRead <= sectorsForOffset) blocksToRead += (uint)sectorsForOffset; } } switch(inData) { case false when currentReadSpeed == 0xFFFF: _dumpLog.WriteLine(Localization.Core.Setting_speed_to_8x_for_audio_reading); UpdateStatus?.Invoke(Localization.Core.Setting_speed_to_8x_for_audio_reading); _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, 1416, 0, _dev.Timeout, out _); currentReadSpeed = 1200; break; case true when currentReadSpeed != _speed: { _dumpLog.WriteLine(_speed == 0xFFFF ? Localization.Core.Setting_speed_to_MAX_for_data_reading : string.Format(Localization.Core.Setting_speed_to_0_x_for_data_reading, _speed)); UpdateStatus?.Invoke(_speed == 0xFFFF ? Localization.Core.Setting_speed_to_MAX_for_data_reading : string.Format(Localization.Core.Setting_speed_to_0_x_for_data_reading, _speed)); _speed *= _speedMultiplier; if(_speed is 0 or > 0xFFFF) _speed = 0xFFFF; currentReadSpeed = _speed; _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, (ushort)_speed, 0, _dev.Timeout, out _); break; } } if(inData && crossingLeadOut) { firstSectorToRead = (uint)i; blocksToRead = (uint)(lastSector - firstSectorToRead) + 1; crossingLeadOut = false; } if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed; UpdateProgress?. Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, i, blocks, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), (long)i, (long)blocks); if(crossingLeadOut && failedCrossingLeadOut && blocksToRead > 1) blocksToRead--; if(_supportsPlextorD8 && !inData) { _speedStopwatch.Start(); sense = ReadPlextorWithSubchannel(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, supportedPlextorSubchannel, out cmdDuration); totalDuration += cmdDuration; _speedStopwatch.Stop(); } else if(readcd) { if(inData) { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); if(sense) { DecodedSense? decSense = Sense.Decode(senseBuf); // Try to workaround firmware if(decSense?.ASC == 0x64) { var goBackTrackTypeChange = false; // Go one for one as the drive does not tell us which one failed for(var bi = 0; bi < blocksToRead; bi++) { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)(firstSectorToRead + bi), blockSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out double cmdDuration2); _speedStopwatch.Stop(); cmdDuration += cmdDuration2; if(!sense && cmdBuf[0] == 0x00 && cmdBuf[1] == 0xFF && cmdBuf[2] == 0xFF && cmdBuf[3] == 0xFF && cmdBuf[4] == 0xFF && cmdBuf[5] == 0xFF && cmdBuf[6] == 0xFF && cmdBuf[7] == 0xFF && cmdBuf[8] == 0xFF && cmdBuf[9] == 0xFF && cmdBuf[10] == 0xFF && cmdBuf[11] == 0x00) continue; // Set those sectors as audio for(int bip = bi; bip < blocksToRead; bip++) audioExtents.Add((ulong)(firstSectorToRead + bip)); goBackTrackTypeChange = true; break; } // Go back to read again if(goBackTrackTypeChange) { blocksToRead = 0; nextData = true; continue; } // Drive definitively didn't like to read everything so just do something clever... // Try again _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); if(sense) // Try reading one less every time { for(uint bi = blocksToRead; bi > 0; bi--) { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, bi, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); if(sense) continue; blocksToRead = bi; break; } } } } } else { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); if(sense) { DecodedSense? decSense = Sense.Decode(senseBuf); // Try to workaround firmware if(decSense is { ASC: 0x11, ASCQ: 0x05 } || decSense?.ASC == 0x64) { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out _, firstSectorToRead, blockSize, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out double cmdDuration2); _speedStopwatch.Stop(); cmdDuration += cmdDuration2; } } } totalDuration += cmdDuration; } else if(read16) { _speedStopwatch.Start(); sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, false, false, firstSectorToRead, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read12) { _speedStopwatch.Start(); sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, false, false, false, firstSectorToRead, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read10) { _speedStopwatch.Start(); sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, false, false, false, firstSectorToRead, blockSize, 0, (ushort)blocksToRead, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read6) { _speedStopwatch.Start(); sense = _dev.Read6(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, (byte)blocksToRead, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } double elapsed; // Overcome the track mode change drive error if(inData && !nextData && sense) { for(uint r = 0; r < blocksToRead; r++) { UpdateProgress?. Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, i + r, blocks, ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), (long)i + r, (long)blocks); if(_supportsPlextorD8) { var adjustment = 0; if(offsetBytes < 0) adjustment = -sectorsForOffset; _speedStopwatch.Start(); sense = ReadPlextorWithSubchannel(out cmdBuf, out senseBuf, (uint)(firstSectorToRead + r + adjustment), blockSize, (uint)sectorsForOffset + 1, supportedPlextorSubchannel, out cmdDuration); _speedStopwatch.Stop(); totalDuration += cmdDuration; if(!sense) { var sectorsForFix = (uint)(1 + sectorsForOffset); FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref sectorsForFix, subSize, ref cmdBuf, blockSize, false); // TODO: Implement sector validity cmdBuf = Sector.Scramble(cmdBuf); } } else if(readcd) { _speedStopwatch.Start(); sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)(i + r), blockSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); totalDuration += cmdDuration; } else if(read16) { _speedStopwatch.Start(); sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, i + r, blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read12) { _speedStopwatch.Start(); sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)(i + r), blockSize, 0, 1, false, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read10) { _speedStopwatch.Start(); sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)(i + r), blockSize, 0, 1, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } else if(read6) { _speedStopwatch.Start(); sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)(i + r), blockSize, 1, _dev.Timeout, out cmdDuration); _speedStopwatch.Stop(); } if(!sense && !_dev.Error) { mhddLog.Write(i + r, cmdDuration); ibgLog.Write(i + r, currentSpeed * 1024); extents.Add(i + r, 1, true); _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { var data = new byte[sectorSize]; var sub = new byte[subSize]; Array.Copy(cmdBuf, 0, data, 0, sectorSize); Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); if(supportsLongSectors) outputFormat.WriteSectorsLong(data, i + r, 1); else { var cooked = new MemoryStream(); var sector = new byte[sectorSize]; for(var b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(0 + b * blockSize), sector, 0, sectorSize); byte[] cookedSector = Sector.GetUserData(sector); cooked.Write(cookedSector, 0, cookedSector.Length); } outputFormat.WriteSectors(cooked.ToArray(), i, blocksToRead); } bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i + r, 1, subLog, isrcs, (byte)track.Sequence, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, outputFormat as IWritableOpticalImage, _fixSubchannel, _fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true, out List newPregapSectors); // Set tracks and go back if(indexesChanged) { (outputFormat as IWritableOpticalImage).SetTracks(tracks.ToList()); foreach(ulong newPregapSector in newPregapSectors) _resume.BadBlocks.Add(newPregapSector); if(i >= blocksToRead) i -= blocksToRead; else i = 0; if(i > 0) i--; foreach(Track aTrack in tracks.Where(aTrack => aTrack.Type == TrackType.Audio)) audioExtents.Add(aTrack.StartSector, aTrack.EndSector); continue; } } else { if(supportsLongSectors) outputFormat.WriteSectorsLong(cmdBuf, i + r, 1); else { var cooked = new MemoryStream(); var sector = new byte[sectorSize]; for(var b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(b * sectorSize), sector, 0, sectorSize); byte[] cookedSector = Sector.GetUserData(sector); cooked.Write(cookedSector, 0, cookedSector.Length); } outputFormat.WriteSectors(cooked.ToArray(), i, blocksToRead); } } _mediaGraph?.PaintSectorGood(i + r); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; } else { _errorLog?.WriteLine(i + r, _dev.Error, _dev.LastError, senseBuf); // Write empty data _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { outputFormat.WriteSectorsLong(new byte[sectorSize], i + r, 1); if(desiredSubchannel != MmcSubchannel.None) { outputFormat.WriteSectorsTag(new byte[subSize], i + r, 1, SectorTagType.CdSectorSubchannel); } } else { if(supportsLongSectors) outputFormat.WriteSectorsLong(new byte[blockSize], i + r, 1); else { if(cmdBuf.Length % sectorSize == 0) outputFormat.WriteSectors(new byte[2048], i + r, 1); else outputFormat.WriteSectorsLong(new byte[blockSize], i + r, 1); } } imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; _mediaGraph?.PaintSectorBad(i + r); _resume.BadBlocks.Add(i + r); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.READ_error_0, Sense.PrettifySense(senseBuf)); mhddLog.Write(i + r, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i + r, 0); _dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, 1, i + r); newTrim = true; } _writeStopwatch.Stop(); sectorSpeedStart += r; _resume.NextBlock = i + r; elapsed = _speedStopwatch.Elapsed.TotalSeconds; if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue; currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); sectorSpeedStart = 0; _speedStopwatch.Reset(); } continue; } if(!sense && !_dev.Error) { if(crossingLeadOut && failedCrossingLeadOut) { var tmp = new byte[cmdBuf.Length + blockSize]; Array.Copy(cmdBuf, 0, tmp, 0, cmdBuf.Length); } // Because one block has been partially used to fix the offset if(_fixOffset && !inData && offsetBytes != 0) { FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead, subSize, ref cmdBuf, blockSize, failedCrossingLeadOut); } mhddLog.Write(i, cmdDuration, blocksToRead); ibgLog.Write(i, currentSpeed * 1024); extents.Add(i, blocksToRead, true); _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { var data = new byte[sectorSize * blocksToRead]; var sub = new byte[subSize * blocksToRead]; for(var b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(0 + b * blockSize), data, sectorSize * b, sectorSize); Array.Copy(cmdBuf, (int)(sectorSize + b * blockSize), sub, subSize * b, subSize); } if(supportsLongSectors) outputFormat.WriteSectorsLong(data, i, blocksToRead); else { var cooked = new MemoryStream(); var sector = new byte[sectorSize]; for(var b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(0 + b * blockSize), sector, 0, sectorSize); byte[] cookedSector = Sector.GetUserData(sector); cooked.Write(cookedSector, 0, cookedSector.Length); } outputFormat.WriteSectors(cooked.ToArray(), i, blocksToRead); } bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i, blocksToRead, subLog, isrcs, (byte)track.Sequence, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, outputFormat as IWritableOpticalImage, _fixSubchannel, _fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true, out List newPregapSectors); // Set tracks and go back if(indexesChanged) { (outputFormat as IWritableOpticalImage).SetTracks(tracks.ToList()); foreach(ulong newPregapSector in newPregapSectors) _resume.BadBlocks.Add(newPregapSector); if(i >= blocksToRead) i -= blocksToRead; else i = 0; if(i > 0) i--; foreach(Track aTrack in tracks.Where(aTrack => aTrack.Type == TrackType.Audio)) audioExtents.Add(aTrack.StartSector, aTrack.EndSector); continue; } } else { if(supportsLongSectors) outputFormat.WriteSectorsLong(cmdBuf, i, blocksToRead); else { var cooked = new MemoryStream(); var sector = new byte[sectorSize]; for(var b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(b * sectorSize), sector, 0, sectorSize); byte[] cookedSector = Sector.GetUserData(sector); cooked.Write(cookedSector, 0, cookedSector.Length); } outputFormat.WriteSectors(cooked.ToArray(), i, blocksToRead); } } _mediaGraph?.PaintSectorsGood(i, blocksToRead); imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; } else { if(crossingLeadOut && Sense.Decode(senseBuf)?.ASC == 0x21) { if(failedCrossingLeadOut) break; failedCrossingLeadOut = true; blocksToRead = 0; continue; } _errorLog?.WriteLine(firstSectorToRead, _dev.Error, _dev.LastError, senseBuf); // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly if(i + _skip > blocks) _skip = (uint)(blocks - i); // Write empty data _writeStopwatch.Restart(); if(supportedSubchannel != MmcSubchannel.None) { outputFormat.WriteSectorsLong(new byte[sectorSize * _skip], i, _skip); if(desiredSubchannel != MmcSubchannel.None) { outputFormat.WriteSectorsTag(new byte[subSize * _skip], i, _skip, SectorTagType.CdSectorSubchannel); } } else { if(supportsLongSectors) outputFormat.WriteSectorsLong(new byte[blockSize * _skip], i, _skip); else { if(cmdBuf.Length % sectorSize == 0) outputFormat.WriteSectors(new byte[2048 * _skip], i, _skip); else outputFormat.WriteSectorsLong(new byte[blockSize * _skip], i, _skip); } } imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b); AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.READ_error_0, Sense.PrettifySense(senseBuf)); mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip); ibgLog.Write(i, 0); _dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i); i += _skip - blocksToRead; newTrim = true; } _writeStopwatch.Stop(); sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; elapsed = _speedStopwatch.Elapsed.TotalSeconds; if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue; currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); sectorSpeedStart = 0; _speedStopwatch.Reset(); } _speedStopwatch.Stop(); EndProgress?.Invoke(); _resume.BadBlocks = _resume.BadBlocks.Distinct().ToList(); if(!failedCrossingLeadOut) return; _dumpLog.WriteLine(Localization.Core.Failed_crossing_into_Lead_Out); UpdateStatus?.Invoke(Localization.Core.Failed_crossing_into_Lead_Out); } }