// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : CompactDisc.cs // Author(s) : Natalia Portillo // // Component : Core algorithms. // // --[ Description ] ---------------------------------------------------------- // // Dumps CDs and DDCDs. // // --[ 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-2020 Natalia Portillo // ****************************************************************************/ using System; using System.Linq; using Aaru.Checksums; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Structs; using Aaru.Console; using Aaru.Core.Logging; using Aaru.Decoders.CD; using Aaru.Decoders.SCSI; using Aaru.Devices; using Schemas; // ReSharper disable JoinDeclarationAndInitializer // ReSharper disable InlineOutVariableDeclaration // ReSharper disable TooWideLocalVariableScope 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 void ReadCdData(ExtentsULong audioExtents, ulong blocks, uint blockSize, ref double currentSpeed, DumpHardwareType 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) { ulong sectorSpeedStart = 0; // Used to calculate correct speed DateTime timeSpeedStart = DateTime.UtcNow; // Time of start for speed calculation uint blocksToRead = 0; // How many sectors to read at once bool 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 byte[] tmpBuf; // Temporary buffer newTrim = false; PlextorSubchannel supportedPlextorSubchannel; switch(supportedSubchannel) { case MmcSubchannel.None: supportedPlextorSubchannel = PlextorSubchannel.None; break; case MmcSubchannel.Raw: supportedPlextorSubchannel = PlextorSubchannel.All; break; case MmcSubchannel.Q16: supportedPlextorSubchannel = PlextorSubchannel.Q16; break; case MmcSubchannel.Rw: supportedPlextorSubchannel = PlextorSubchannel.Pack; break; default: supportedPlextorSubchannel = PlextorSubchannel.None; break; } InitProgress?.Invoke(); int currentReadSpeed = _speed; bool crossingLeadOut = false; bool failedCrossingLeadOut = false; for(ulong i = _resume.NextBlock; (long)i <= lastSector; i += blocksToRead) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } while(leadOutExtents.Contains(i)) { i++; } if((long)i > lastSector) break; uint firstSectorToRead = (uint)i; Track track = tracks.OrderBy(t => t.TrackStartSector).LastOrDefault(t => i >= t.TrackStartSector); blocksToRead = 0; bool inData = nextData; for(ulong j = i; j < i + _maximumReadable; j++) { if(j > (ulong)lastSector) { if(!failedCrossingLeadOut) blocksToRead += (uint)sectorsForOffset; if(sectorsForOffset > 0) crossingLeadOut = true; break; } if(nextData) { if(audioExtents.Contains(j)) { nextData = false; break; } blocksToRead++; } else { if(!audioExtents.Contains(j)) { nextData = true; break; } blocksToRead++; } } if(track.TrackSequence != 0 && (i + blocksToRead) - (ulong)sectorsForOffset > track.TrackEndSector + 1) blocksToRead = (uint)(((track.TrackEndSector + 1) - i) + (ulong)sectorsForOffset); if(blocksToRead == 1 && !inData) blocksToRead += (uint)sectorsForOffset; if(_fixOffset && !inData) { // TODO: FreeBSD bug if(offsetBytes < 0) { if(i == 0) firstSectorToRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1 else firstSectorToRead -= (uint)sectorsForOffset; } } if(!inData && currentReadSpeed == 0xFFFF) { _dumpLog.WriteLine("Setting speed to 8x for audio reading."); UpdateStatus?.Invoke("Setting speed to 8x for audio reading."); _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, 1416, 0, _dev.Timeout, out _); currentReadSpeed = 1200; } if(inData && currentReadSpeed != _speed) { _dumpLog.WriteLine($"Setting speed to {(_speed == 0xFFFF ? "MAX for data reading" : $"{_speed}x")}."); UpdateStatus?. Invoke($"Setting speed to {(_speed == 0xFFFF ? "MAX for data reading" : $"{_speed}x")}."); _speed *= _speedMultiplier; if(_speed == 0 || _speed > 0xFFFF) _speed = 0xFFFF; _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, (ushort)_speed, 0, _dev.Timeout, out _); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator // ReSharper disable CompareOfFloatsByEqualityOperator if(currentSpeed > maxSpeed && currentSpeed != 0) maxSpeed = currentSpeed; if(currentSpeed < minSpeed && currentSpeed != 0) minSpeed = currentSpeed; // ReSharper restore CompareOfFloatsByEqualityOperator #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); if(_supportsPlextorD8 && !inData) { sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, supportedPlextorSubchannel, 0, out cmdDuration); if(sense) { // As a workaround for some firmware bugs, seek far away. _dev.PlextorReadCdDa(out _, out senseBuf, firstSectorToRead - 32, blockSize, blocksToRead, supportedPlextorSubchannel, 0, out _); sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead, supportedPlextorSubchannel, _dev.Timeout, out cmdDuration); } totalDuration += cmdDuration; } else if(readcd) { 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); totalDuration += cmdDuration; } else if(read16) { sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, firstSectorToRead, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); } else if(read12) { sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, firstSectorToRead, blockSize, 0, blocksToRead, false, _dev.Timeout, out cmdDuration); } else if(read10) { sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, firstSectorToRead, blockSize, 0, (ushort)blocksToRead, _dev.Timeout, out cmdDuration); } else if(read6) { sense = _dev.Read6(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, (byte)blocksToRead, _dev.Timeout, out cmdDuration); } double elapsed; // Overcome the track mode change drive error if(inData && !nextData && sense) { for(uint r = 0; r < blocksToRead; r++) { UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i + r, (long)blocks); if(readcd) { 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); totalDuration += cmdDuration; // Some drives just happily return the next audio sector instead of the last data sector, so we need to manually check for correctness // TODO: Check the same when trimming and reading back if(!sense) sense = CdChecksums.CheckCdSector(cmdBuf) != true; if(_supportsPlextorD8 && sense) { int adjustment = 0; if(offsetBytes < 0) adjustment = -sectorsForOffset; sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, (uint)(firstSectorToRead + r + adjustment), blockSize, (uint)sectorsForOffset, supportedPlextorSubchannel, 0, out cmdDuration); if(sense) { // As a workaround for some firmware bugs, seek far away. _dev.PlextorReadCdDa(out _, out senseBuf, firstSectorToRead - 32, blockSize, blocksToRead, supportedPlextorSubchannel, 0, out _); sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, (uint)(firstSectorToRead + r + adjustment), blockSize, (uint)sectorsForOffset, supportedPlextorSubchannel, _dev.Timeout, out cmdDuration); } totalDuration += cmdDuration; if(!sense) { int offsetFix = offsetBytes < 0 ? (int)((sectorSize * sectorsForOffset) + offsetBytes) : offsetBytes; if(supportedSubchannel != MmcSubchannel.None) { // De-interleave subchannel byte[] data = new byte[sectorSize]; byte[] sub = new byte[subSize]; Array.Copy(cmdBuf, (int)blockSize, data, sectorSize, sectorSize); Array.Copy(cmdBuf, (int)(sectorSize + blockSize), sub, subSize, subSize); tmpBuf = new byte[sectorSize]; Array.Copy(data, offsetFix, tmpBuf, 0, tmpBuf.Length); data = tmpBuf; blocksToRead -= (uint)sectorsForOffset; // Re-interleave subchannel cmdBuf = new byte[blockSize * blocksToRead]; Array.Copy(data, sectorSize, cmdBuf, (int)blockSize, sectorSize); Array.Copy(sub, subSize, cmdBuf, (int)(sectorSize + blockSize), subSize); } else { tmpBuf = new byte[blockSize]; Array.Copy(cmdBuf, offsetFix, tmpBuf, 0, tmpBuf.Length); cmdBuf = tmpBuf; } cmdBuf = Sector.Scramble(cmdBuf); // TODO: In error correction, if persistent, consider we got the sector, just save it sense = CdChecksums.CheckCdSector(cmdBuf) != true; } } } else if(read16) { sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, i + r, 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 + r), 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 + r), blockSize, 0, 1, _dev.Timeout, out cmdDuration); } else if(read6) { sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)(i + r), blockSize, 1, _dev.Timeout, out cmdDuration); } if(!sense && !_dev.Error) { mhddLog.Write(i + r, cmdDuration); ibgLog.Write(i + r, currentSpeed * 1024); extents.Add(i + r, 1, true); DateTime writeStart = DateTime.Now; if(supportedSubchannel != MmcSubchannel.None) { byte[] data = new byte[sectorSize]; byte[] sub = new byte[subSize]; Array.Copy(cmdBuf, (int)blockSize, data, sectorSize, sectorSize); Array.Copy(cmdBuf, (int)(sectorSize + blockSize), sub, subSize, subSize); _outputPlugin.WriteSectorsLong(data, i + r, 1); _outputPlugin.WriteSectorsTag(sub, i + r, 1, SectorTagType.CdSectorSubchannel); } else { if(supportsLongSectors) { _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); } else { if(cmdBuf.Length % sectorSize == 0) { byte[] data = new byte[2048]; Array.Copy(cmdBuf, (int)(16 + blockSize), data, 2048, 2048); _outputPlugin.WriteSectors(data, i + r, 1); } else { _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); } } } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; } else { // Write empty data DateTime writeStart = DateTime.Now; if(supportedSubchannel != MmcSubchannel.None) { _outputPlugin.WriteSectorsLong(new byte[sectorSize], i + r, 1); _outputPlugin.WriteSectorsTag(new byte[subSize], i + r, 1, SectorTagType.CdSectorSubchannel); } else { if(supportsLongSectors) { _outputPlugin.WriteSectorsLong(new byte[blockSize], i + r, 1); } else { if(cmdBuf.Length % sectorSize == 0) _outputPlugin.WriteSectors(new byte[2048], i + r, 1); else _outputPlugin.WriteSectorsLong(new byte[blockSize], i + r, 1); } } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; _resume.BadBlocks.Add(i + r); AaruConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf)); mhddLog.Write(i + r, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i + r, 0); _dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", 1, i + r); newTrim = true; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if(elapsed < 1) continue; currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } continue; } if(!sense && !_dev.Error) { // Because one block has been partially used to fix the offset if(_fixOffset && !inData && offsetBytes != 0) { int offsetFix = offsetBytes < 0 ? (int)((sectorSize * sectorsForOffset) + offsetBytes) : offsetBytes; if(supportedSubchannel != MmcSubchannel.None) { // De-interleave subchannel byte[] data = new byte[sectorSize * blocksToRead]; byte[] sub = new byte[subSize * blocksToRead]; for(int 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(failedCrossingLeadOut) { blocksToRead += (uint)sectorsForOffset; tmpBuf = new byte[sectorSize * blocksToRead]; Array.Copy(data, 0, tmpBuf, 0, data.Length); data = tmpBuf; tmpBuf = new byte[subSize * blocksToRead]; Array.Copy(sub, 0, tmpBuf, 0, sub.Length); sub = tmpBuf; } tmpBuf = new byte[sectorSize * (blocksToRead - sectorsForOffset)]; Array.Copy(data, offsetFix, tmpBuf, 0, tmpBuf.Length); data = tmpBuf; blocksToRead -= (uint)sectorsForOffset; // Re-interleave subchannel cmdBuf = new byte[blockSize * blocksToRead]; for(int b = 0; b < blocksToRead; b++) { Array.Copy(data, sectorSize * b, cmdBuf, (int)(0 + (b * blockSize)), sectorSize); Array.Copy(sub, subSize * b, cmdBuf, (int)(sectorSize + (b * blockSize)), subSize); } } else { if(failedCrossingLeadOut) { blocksToRead += (uint)sectorsForOffset; tmpBuf = new byte[blockSize * blocksToRead]; Array.Copy(cmdBuf, 0, tmpBuf, 0, cmdBuf.Length); cmdBuf = tmpBuf; } tmpBuf = new byte[blockSize * (blocksToRead - sectorsForOffset)]; Array.Copy(cmdBuf, offsetFix, tmpBuf, 0, tmpBuf.Length); cmdBuf = tmpBuf; blocksToRead -= (uint)sectorsForOffset; } } mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); extents.Add(i, blocksToRead, true); DateTime writeStart = DateTime.Now; if(supportedSubchannel != MmcSubchannel.None) { byte[] data = new byte[sectorSize * blocksToRead]; byte[] sub = new byte[subSize * blocksToRead]; for(int 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); } _outputPlugin.WriteSectorsLong(data, i, blocksToRead); _outputPlugin.WriteSectorsTag(sub, i, blocksToRead, SectorTagType.CdSectorSubchannel); } else { if(supportsLongSectors) { _outputPlugin.WriteSectorsLong(cmdBuf, i, blocksToRead); } else { if(cmdBuf.Length % sectorSize == 0) { byte[] data = new byte[2048 * blocksToRead]; for(int b = 0; b < blocksToRead; b++) Array.Copy(cmdBuf, (int)(16 + (b * blockSize)), data, 2048 * b, 2048); _outputPlugin.WriteSectors(data, i, blocksToRead); } else { _outputPlugin.WriteSectorsLong(cmdBuf, i, blocksToRead); } } } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; } else { if(crossingLeadOut && Sense.DecodeFixed(senseBuf)?.ASC == 0x21) { failedCrossingLeadOut = true; blocksToRead = 0; continue; } // TODO: Reset device after X errors if(_stopOnError) return; // TODO: Return more cleanly if(i + _skip > blocks) _skip = (uint)(blocks - i); // Write empty data DateTime writeStart = DateTime.Now; if(supportedSubchannel != MmcSubchannel.None) { _outputPlugin.WriteSectorsLong(new byte[sectorSize * _skip], i, _skip); _outputPlugin.WriteSectorsTag(new byte[subSize * _skip], i, _skip, SectorTagType.CdSectorSubchannel); } else { if(supportsLongSectors) { _outputPlugin.WriteSectorsLong(new byte[blockSize * _skip], i, _skip); } else { if(cmdBuf.Length % sectorSize == 0) _outputPlugin.WriteSectors(new byte[2048 * _skip], i, _skip); else _outputPlugin.WriteSectorsLong(new byte[blockSize * _skip], i, _skip); } } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b); AaruConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf)); mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i, 0); _dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", _skip, i); i += _skip - blocksToRead; newTrim = true; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if(elapsed < 1) continue; currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } EndProgress?.Invoke(); if(!failedCrossingLeadOut) return; _dumpLog.WriteLine("Failed crossing into Lead-Out, dump may not be correct."); UpdateStatus?.Invoke("Failed crossing into Lead-Out, dump may not be correct."); } } }