// /*************************************************************************** // 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-2020 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Core.Logging; using Aaru.Decoders.CD; using Aaru.Devices; using Schemas; // ReSharper disable JoinDeclarationAndInitializer // ReSharper disable InlineOutVariableDeclaration // ReSharper disable TooWideLocalVariableScope namespace Aaru.Core.Devices.Dumping { partial class Dump { static bool IsData(byte[] sector) { if(sector?.Length != 2352) return false; byte[] syncMark = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; byte[] testMark = new byte[12]; Array.Copy(sector, 0, testMark, 0, 12); return syncMark.SequenceEqual(testMark) && (sector[0xF] == 0 || sector[0xF] == 1 || sector[0xF] == 2); } static bool IsScrambledData(byte[] sector, int wantedLba, out int? offset) { offset = 0; if(sector?.Length != 2352) return false; byte[] syncMark = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; byte[] testMark = new byte[12]; for(int i = 0; i <= 2336; i++) { Array.Copy(sector, i, testMark, 0, 12); if(syncMark.SequenceEqual(testMark) && (sector[i + 0xF] == 0x60 || sector[i + 0xF] == 0x61 || sector[i + 0xF] == 0x62)) { // De-scramble M and S int minute = sector[i + 12] ^ 0x01; int second = sector[i + 13] ^ 0x80; int frame = sector[i + 14]; // Convert to binary minute = ((minute / 16) * 10) + (minute & 0x0F); second = ((second / 16) * 10) + (second & 0x0F); frame = ((frame / 16) * 10) + (frame & 0x0F); // Calculate the first found LBA int lba = ((minute * 60 * 75) + (second * 75) + frame) - 150; // Calculate the difference between the found LBA and the requested one int diff = wantedLba - lba; offset = i + (2352 * diff); return true; } } return false; } // TODO: Set pregap for Track 1 // TODO: Detect errors in sectors /// 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 ReadCdiReady(uint blockSize, ref double currentSpeed, DumpHardwareType currentTry, ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, uint subSize, MmcSubchannel supportedSubchannel, ref double totalDuration, Track[] tracks, SubchannelLog subLog, MmcSubchannel desiredSubchannel, Dictionary isrcs, ref string mcn, HashSet subchannelExtents, ulong blocks, bool cdiReadyReadAsAudio, int offsetBytes, int sectorsForOffset, Dictionary smallestPregapLbaPerTrack) { ulong sectorSpeedStart = 0; // Used to calculate correct speed DateTime timeSpeedStart = DateTime.UtcNow; // Time of start for speed calculation 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 Track firstTrack = tracks.FirstOrDefault(t => t.TrackSequence == 1); uint blocksToRead = 0; // How many sectors to read at once if(firstTrack is null) return; if(cdiReadyReadAsAudio) { _dumpLog.WriteLine("Setting speed to 8x for CD-i Ready reading as audio."); UpdateStatus?.Invoke("Setting speed to 8x for CD-i Ready reading as audio."); _dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, 1416, 0, _dev.Timeout, out _); } InitProgress?.Invoke(); for(ulong i = _resume.NextBlock; i < firstTrack.TrackStartSector; i += blocksToRead) { if(_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } uint firstSectorToRead = (uint)i; blocksToRead = _maximumReadable; if(blocksToRead == 1 && cdiReadyReadAsAudio) blocksToRead += (uint)sectorsForOffset; if(cdiReadyReadAsAudio) { // TODO: FreeBSD bug if(offsetBytes < 0) { if(i == 0) firstSectorToRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1 else firstSectorToRead -= (uint)sectorsForOffset; } } #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); 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; double elapsed; // Overcome the track mode change drive error if(sense) { for(uint r = 0; r < _maximumReadable; r++) { UpdateProgress?.Invoke($"Reading sector {i + r} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i + r, (long)blocks); sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)(i + r), blockSize, (uint)sectorsForOffset + 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); totalDuration += 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(cdiReadyReadAsAudio) FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead, subSize, ref cmdBuf, blockSize, false); if(supportedSubchannel != MmcSubchannel.None) { byte[] data = new byte[sectorSize]; byte[] sub = new byte[subSize]; Array.Copy(cmdBuf, 0, data, 0, sectorSize); Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); if(cdiReadyReadAsAudio) data = Sector.Scramble(data); _outputPlugin.WriteSectorsLong(data, i + r, 1); bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i + r, 1, subLog, isrcs, 1, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, _outputPlugin, _fixSubchannel, _fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack); // Set tracks and go back if(indexesChanged) { (_outputPlugin as IWritableOpticalImage).SetTracks(tracks.ToList()); i -= _maximumReadable; continue; } } else { _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; } else { _errorLog?.WriteLine(i + r, _dev.Error, _dev.LastError, senseBuf); leadOutExtents.Add(i + r, firstTrack.TrackStartSector - 1); UpdateStatus?. Invoke($"Adding CD-i Ready hole from LBA {i + r} to {firstTrack.TrackStartSector - 1} inclusive."); _dumpLog.WriteLine("Adding CD-i Ready hole from LBA {0} to {1} inclusive.", i + r, firstTrack.TrackStartSector - 1); break; } sectorSpeedStart += r; _resume.NextBlock = i + r; elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if(elapsed < 1) continue; currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } } if(!sense && !_dev.Error) { if(cdiReadyReadAsAudio) FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead, subSize, ref cmdBuf, blockSize, false); 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]; byte[] tmpData = new byte[sectorSize]; for(int b = 0; b < blocksToRead; b++) { if(cdiReadyReadAsAudio) { Array.Copy(cmdBuf, (int)(0 + (b * blockSize)), tmpData, 0, sectorSize); tmpData = Sector.Scramble(tmpData); Array.Copy(tmpData, 0, data, sectorSize * b, sectorSize); } else 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); bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i, blocksToRead, subLog, isrcs, 1, ref mcn, tracks, subchannelExtents, _fixSubchannelPosition, _outputPlugin, _fixSubchannel, _fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack); // Set tracks and go back if(indexesChanged) { (_outputPlugin as IWritableOpticalImage).SetTracks(tracks.ToList()); i -= blocksToRead; continue; } } else { if(cdiReadyReadAsAudio) { byte[] tmpData = new byte[sectorSize]; byte[] data = new byte[sectorSize * blocksToRead]; for(int b = 0; b < blocksToRead; b++) { Array.Copy(cmdBuf, (int)(b * sectorSize), tmpData, 0, sectorSize); tmpData = Sector.Scramble(tmpData); Array.Copy(tmpData, 0, data, sectorSize * b, sectorSize); } _outputPlugin.WriteSectorsLong(data, i, blocksToRead); } else _outputPlugin.WriteSectorsLong(cmdBuf, i, blocksToRead); } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; } else { _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf); _resume.NextBlock = firstTrack.TrackStartSector; break; } 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(); } } }