Support dumping CD-i Ready when drive returns data sectors as audio. Fixes #294

This commit is contained in:
2020-06-25 01:13:02 +01:00
parent 651a2df2aa
commit dbbb6812d2
3 changed files with 249 additions and 133 deletions

View File

@@ -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
/// <summary>Reads all CD user data</summary>
/// <param name="audioExtents">Extents with audio sectors</param>
/// <param name="blocks">Total number of positive sectors</param>
@@ -77,11 +143,11 @@ namespace Aaru.Core.Devices.Dumping
/// <param name="totalDuration">Total commands duration</param>
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<byte, string> isrcs, ref string mcn,
HashSet<int> 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<byte, string> isrcs,
ref string mcn, HashSet<int> 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;

View File

@@ -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<byte, string> isrcs = new Dictionary<byte, string>();
string mcn = null;
HashSet<int> subchannelExtents = new HashSet<int>();
bool bcdSubchannel = false; // Subchannel positioning is in BCD
Dictionary<byte, string> isrcs = new Dictionary<byte, string>();
string mcn = null;
HashSet<int> subchannelExtents = new HashSet<int>();
bool cdiReadyReadAsAudio = false;
Dictionary<MediaTagType, byte[]> mediaTags = new Dictionary<MediaTagType, byte[]>(); // 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,

View File

@@ -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;