From dbbb6812d23884ecc0ba57437940ebad4357f56c Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Thu, 25 Jun 2020 01:13:02 +0100 Subject: [PATCH] Support dumping CD-i Ready when drive returns data sectors as audio. Fixes #294 --- .../Devices/Dumping/CompactDisc/CdiReady.cs | 264 ++++++++++-------- Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs | 116 ++++++-- Aaru.Core/Devices/Dumping/Dump.cs | 2 +- 3 files changed, 249 insertions(+), 133 deletions(-) diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/CdiReady.cs b/Aaru.Core/Devices/Dumping/CompactDisc/CdiReady.cs index 9dbfe59c4..bfa17a88b 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/CdiReady.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/CdiReady.cs @@ -37,6 +37,7 @@ 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; @@ -48,6 +49,71 @@ 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 @@ -77,11 +143,11 @@ namespace Aaru.Core.Devices.Dumping /// 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, bool read6, bool read10, - bool read12, bool read16, bool readcd, uint subSize, MmcSubchannel supportedSubchannel, - bool supportsLongSectors, ref double totalDuration, Track[] tracks, SubchannelLog subLog, - MmcSubchannel desiredSubchannel, Dictionary isrcs, ref string mcn, - HashSet subchannelExtents, ulong blocks) + 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) { ulong sectorSpeedStart = 0; // Used to calculate correct speed DateTime timeSpeedStart = DateTime.UtcNow; // Time of start for speed calculation @@ -91,13 +157,22 @@ namespace Aaru.Core.Devices.Dumping 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 += _maximumReadable) + for(ulong i = _resume.NextBlock; i < firstTrack.TrackStartSector; i += blocksToRead) { if(_aborted) { @@ -108,12 +183,24 @@ namespace Aaru.Core.Devices.Dumping break; } - if(i >= firstTrack.TrackStartSector) - break; - uint firstSectorToRead = (uint)i; - Track track = tracks.OrderBy(t => t.TrackStartSector).LastOrDefault(t => i >= t.TrackStartSector); + 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 @@ -133,34 +220,11 @@ namespace Aaru.Core.Devices.Dumping UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); - if(readcd) - { - sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, _maximumReadable, - MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, - true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration); + 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, _maximumReadable, false, _dev.Timeout, out cmdDuration); - } - else if(read12) - { - sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, firstSectorToRead, - blockSize, 0, _maximumReadable, false, _dev.Timeout, out cmdDuration); - } - else if(read10) - { - sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, firstSectorToRead, - blockSize, 0, (ushort)_maximumReadable, _dev.Timeout, out cmdDuration); - } - else if(read6) - { - sense = _dev.Read6(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, (byte)_maximumReadable, - _dev.Timeout, out cmdDuration); - } + totalDuration += cmdDuration; double elapsed; @@ -172,35 +236,12 @@ namespace Aaru.Core.Devices.Dumping UpdateProgress?.Invoke($"Reading sector {i + r} 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); + 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; - } - 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); - } + totalDuration += cmdDuration; if(!sense && !_dev.Error) @@ -210,6 +251,10 @@ namespace Aaru.Core.Devices.Dumping 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]; @@ -219,12 +264,14 @@ namespace Aaru.Core.Devices.Dumping Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); + if(cdiReadyReadAsAudio) + data = Sector.Scramble(data); + _outputPlugin.WriteSectorsLong(data, i + r, 1); bool indexesChanged = WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i + r, 1, - subLog, isrcs, (byte)track.TrackSequence, ref mcn, tracks, - subchannelExtents); + subLog, isrcs, 1, ref mcn, tracks, subchannelExtents); // Set tracks and go back if(indexesChanged) @@ -237,25 +284,7 @@ namespace Aaru.Core.Devices.Dumping } else { - if(supportsLongSectors) - { - _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); - } - else - { - if(cmdBuf.Length % sectorSize == 0) - { - byte[] data = new byte[2048]; - - Array.Copy(cmdBuf, 16, data, 2048, 2048); - - _outputPlugin.WriteSectors(data, i + r, 1); - } - else - { - _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); - } - } + _outputPlugin.WriteSectorsLong(cmdBuf, i + r, 1); } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; @@ -291,61 +320,68 @@ namespace Aaru.Core.Devices.Dumping 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, _maximumReadable, true); + extents.Add(i, blocksToRead, true); DateTime writeStart = DateTime.Now; if(supportedSubchannel != MmcSubchannel.None) { - byte[] data = new byte[sectorSize * _maximumReadable]; - byte[] sub = new byte[subSize * _maximumReadable]; + byte[] data = new byte[sectorSize * blocksToRead]; + byte[] sub = new byte[subSize * blocksToRead]; + byte[] tmpData = new byte[sectorSize]; - for(int b = 0; b < _maximumReadable; b++) + for(int b = 0; b < blocksToRead; b++) { - Array.Copy(cmdBuf, (int)(0 + (b * blockSize)), data, sectorSize * b, sectorSize); + 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, _maximumReadable); + _outputPlugin.WriteSectorsLong(data, i, blocksToRead); bool indexesChanged = WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, i, - _maximumReadable, subLog, isrcs, - (byte)track.TrackSequence, ref mcn, tracks, + blocksToRead, subLog, isrcs, 1, ref mcn, tracks, subchannelExtents); // Set tracks and go back if(indexesChanged) { (_outputPlugin as IWritableOpticalImage).SetTracks(tracks.ToList()); - i -= _maximumReadable; + i -= blocksToRead; continue; } } else { - if(supportsLongSectors) + if(cdiReadyReadAsAudio) { - _outputPlugin.WriteSectorsLong(cmdBuf, i, _maximumReadable); + 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 - { - if(cmdBuf.Length % sectorSize == 0) - { - byte[] data = new byte[2048 * _maximumReadable]; - - for(int b = 0; b < _maximumReadable; b++) - Array.Copy(cmdBuf, (int)(16 + (b * blockSize)), data, 2048 * b, 2048); - - _outputPlugin.WriteSectors(data, i, _maximumReadable); - } - else - { - _outputPlugin.WriteSectorsLong(cmdBuf, i, _maximumReadable); - } - } + _outputPlugin.WriteSectorsLong(cmdBuf, i, blocksToRead); } imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; @@ -357,9 +393,9 @@ namespace Aaru.Core.Devices.Dumping break; } - sectorSpeedStart += _maximumReadable; + sectorSpeedStart += blocksToRead; - _resume.NextBlock = i + _maximumReadable; + _resume.NextBlock = i + blocksToRead; elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs index 66b66b6e0..33c2379ee 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs @@ -112,10 +112,11 @@ namespace Aaru.Core.Devices.Dumping bool hiddenTrack; // Disc has a hidden track before track 1 MmcSubchannel supportedSubchannel; // Drive's maximum supported subchannel MmcSubchannel desiredSubchannel; // User requested subchannel - bool bcdSubchannel = false; // Subchannel positioning is in BCD - Dictionary isrcs = new Dictionary(); - string mcn = null; - HashSet subchannelExtents = new HashSet(); + bool bcdSubchannel = false; // Subchannel positioning is in BCD + Dictionary isrcs = new Dictionary(); + string mcn = null; + HashSet subchannelExtents = new HashSet(); + bool cdiReadyReadAsAudio = false; Dictionary mediaTags = new Dictionary(); // Media tags @@ -892,15 +893,6 @@ namespace Aaru.Core.Devices.Dumping Invoke($"Track {trk.TrackSequence} starts at LBA {trk.TrackStartSector} and ends at LBA {trk.TrackEndSector}"); #endif - if(dskType == MediaType.CDIREADY && - !_skipCdireadyHole) - { - _dumpLog.WriteLine("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them."); - - UpdateStatus?. - Invoke("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them."); - } - // Check offset if(_fixOffset) { @@ -1041,12 +1033,100 @@ namespace Aaru.Core.Devices.Dumping // Start reading start = DateTime.UtcNow; - if(dskType == MediaType.CDIREADY && _skipCdireadyHole) + if(dskType == MediaType.CDIREADY) { - ReadCdiReady(blockSize, ref currentSpeed, currentTry, extents, ibgLog, ref imageWriteDuration, - leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, read6, read10, read12, read16, readcd, - subSize, supportedSubchannel, supportsLongSectors, ref totalDuration, tracks, subLog, - desiredSubchannel, isrcs, ref mcn, subchannelExtents, blocks); + Track track0 = tracks.FirstOrDefault(t => t.TrackSequence == 0); + + track0.TrackType = TrackType.CdMode2Formless; + + if(!supportsLongSectors) + { + _dumpLog.WriteLine("Dumping CD-i Ready requires the output image format to support long sectors."); + + StoppingErrorMessage?. + Invoke("Dumping CD-i Ready requires the output image format to support long sectors."); + + return; + } + + if(!readcd) + { + _dumpLog.WriteLine("Dumping CD-i Ready requires the drive to support the READ CD command."); + + StoppingErrorMessage?. + Invoke("Dumping CD-i Ready requires the drive to support the READ CD command."); + + return; + } + + sense = _dev.ReadCd(out cmdBuf, out _, 0, 2352, 1, MmcSectorTypes.AllTypes, false, false, true, + MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, + _dev.Timeout, out _); + + hiddenData = IsData(cmdBuf); + + if(!hiddenData) + { + cdiReadyReadAsAudio = IsScrambledData(cmdBuf, 0, out combinedOffset); + + if(cdiReadyReadAsAudio) + { + offsetBytes = combinedOffset.Value; + sectorsForOffset = offsetBytes / (int)sectorSize; + + if(sectorsForOffset < 0) + sectorsForOffset *= -1; + + if(offsetBytes % sectorSize != 0) + sectorsForOffset++; + + _dumpLog.WriteLine("Enabling skipping CD-i Ready hole because drive returns data as audio."); + + UpdateStatus?.Invoke("Enabling skipping CD-i Ready hole because drive returns data as audio."); + + _skipCdireadyHole = true; + + if(driveOffset is null) + { + _dumpLog.WriteLine("Drive reading offset not found in database."); + UpdateStatus?.Invoke("Drive reading offset not found in database."); + + _dumpLog. + WriteLine($"Combined disc and drive offsets are {offsetBytes} bytes ({offsetBytes / 4} samples)."); + + UpdateStatus?. + Invoke($"Combined disc and drive offsets are {offsetBytes} bytes ({offsetBytes / 4} samples)."); + } + else + { + _dumpLog. + WriteLine($"Drive reading offset is {driveOffset} bytes ({driveOffset / 4} samples)."); + + UpdateStatus?. + Invoke($"Drive reading offset is {driveOffset} bytes ({driveOffset / 4} samples)."); + + discOffset = offsetBytes - driveOffset; + + _dumpLog.WriteLine($"Disc offsets is {discOffset} bytes ({discOffset / 4} samples)"); + + UpdateStatus?.Invoke($"Disc offsets is {discOffset} bytes ({discOffset / 4} samples)"); + } + } + } + + if(!_skipCdireadyHole) + { + _dumpLog.WriteLine("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them."); + + UpdateStatus?. + Invoke("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them."); + } + + if(_skipCdireadyHole) + ReadCdiReady(blockSize, ref currentSpeed, currentTry, extents, ibgLog, ref imageWriteDuration, + leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, subSize, supportedSubchannel, + ref totalDuration, tracks, subLog, desiredSubchannel, isrcs, ref mcn, + subchannelExtents, blocks, cdiReadyReadAsAudio, offsetBytes, sectorsForOffset); } ReadCdData(audioExtents, blocks, blockSize, ref currentSpeed, currentTry, extents, ibgLog, diff --git a/Aaru.Core/Devices/Dumping/Dump.cs b/Aaru.Core/Devices/Dumping/Dump.cs index 425d17f9c..33771bef1 100644 --- a/Aaru.Core/Devices/Dumping/Dump.cs +++ b/Aaru.Core/Devices/Dumping/Dump.cs @@ -87,7 +87,7 @@ namespace Aaru.Core.Devices.Dumping Resume _resume; Sidecar _sidecarClass; uint _skip; - readonly bool _skipCdireadyHole; + bool _skipCdireadyHole; int _speed; int _speedMultiplier; bool _supportsPlextorD8;