Add support for offset correction of first track pregap.

This commit is contained in:
2026-07-04 20:13:39 +01:00
parent 759db9e293
commit d352f34004
2 changed files with 205 additions and 51 deletions

View File

@@ -834,7 +834,12 @@ sealed partial class Dump
mediaTags,
supportedSubchannel,
ref totalDuration,
outputOptical);
outputOptical,
subLog,
offsetBytes,
sectorsForOffset,
desiredSubchannel,
subchannelExtents);
}
audioExtents = new ExtentsULong();

View File

@@ -44,6 +44,8 @@ using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Graphics;
using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Devices;
using Aaru.Logging;
using Humanizer;
@@ -53,32 +55,56 @@ namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
// TODO: Fix offset
/// <summary>Reads the first track pregap from a CompactDisc</summary>
/// <param name="blockSize">Block size in bytes</param>
/// <param name="currentSpeed">Current speed</param>
/// <param name="mediaTags">List of media tags</param>
/// <param name="supportedSubchannel">Subchannel the drive can read</param>
/// <param name="totalDuration">Total time spent sending commands to a drive</param>
/// <param name="outputOptical"></param>
/// <param name="outputOptical">Output optical image</param>
/// <param name="subLog">Subchannel log</param>
/// <param name="offsetBytes">Drive read offset in bytes</param>
/// <param name="sectorsForOffset">Number of sectors needed to cover the offset</param>
/// <param name="desiredSubchannel">Subchannel type the user wants written to the image</param>
/// <param name="subchannelExtents">Set of sector LBAs that still need subchannel data</param>
void ReadCdFirstTrackPregap(uint blockSize, ref double currentSpeed, Dictionary<MediaTagType, byte[]> mediaTags,
MmcSubchannel supportedSubchannel, ref double totalDuration,
IWritableOpticalImage outputOptical)
IWritableOpticalImage outputOptical, SubchannelLog subLog, int offsetBytes,
int sectorsForOffset, MmcSubchannel desiredSubchannel, HashSet<int> subchannelExtents)
{
bool sense; // Sense indicator
byte[] cmdBuf; // Data buffer
double cmdDuration; // Command execution time
ulong sectorSpeedStart = 0; // Used to calculate correct speed
var gotFirstTrackPregap = false;
var firstTrackPregapSectorsGood = 0;
var firstTrackPregapMs = new MemoryStream();
uint subSize = supportedSubchannel switch
{
MmcSubchannel.None => 0,
MmcSubchannel.Raw => 96,
_ => 16
};
bool needsOffsetCorrection = _fixOffset && offsetBytes != 0;
// Expand read range to include extra sectors needed for offset correction.
// For positive offset: we must read a few sectors past LBA -1 (into LBA 0, 1, …).
// For negative offset: we must read a few sectors before LBA -150 (deeper lead-in).
int startLba = needsOffsetCorrection && offsetBytes < 0 ? -150 - sectorsForOffset : -150;
int endLba = needsOffsetCorrection && offsetBytes > 0 ? sectorsForOffset - 1 : -1;
int totalSectorsToRead = endLba - startLba + 1;
var allSectorsBuffer = new byte[totalSectorsToRead * blockSize];
var readSuccess = new bool[totalSectorsToRead];
var sectorIsAudio = new bool[totalSectorsToRead];
UpdateStatus?.Invoke(Localization.Core.Reading_first_track_pregap);
InitProgress?.Invoke();
_speedStopwatch.Restart();
for(int firstTrackPregapBlock = -150;
firstTrackPregapBlock < 0 && _resume.NextBlock == 0;
for(int firstTrackPregapBlock = startLba;
firstTrackPregapBlock <= endLba && _resume.NextBlock == 0;
firstTrackPregapBlock++)
{
if(_aborted)
@@ -88,9 +114,14 @@ partial class Dump
break;
}
PulseProgress?.Invoke(string.Format(Localization.Core.Trying_to_read_first_track_pregap_sector_0_1,
firstTrackPregapBlock,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()));
int bufferIndex = firstTrackPregapBlock - startLba;
if(firstTrackPregapBlock >= -150)
{
PulseProgress?.Invoke(string.Format(Localization.Core.Trying_to_read_first_track_pregap_sector_0_1,
firstTrackPregapBlock,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()));
}
// ReSharper disable IntVariableOverflowInUncheckedContext
sense = _dev.ReadCd(out cmdBuf,
@@ -114,54 +145,29 @@ partial class Dump
if(!sense && !_dev.Error)
{
if(outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreNegativeSectors))
Array.Copy(cmdBuf, 0, allSectorsBuffer, bufferIndex * (int)blockSize, (int)blockSize);
// Determine if this sector is audio by checking for the CD sync mark
var sectorData = new byte[2352];
Array.Copy(cmdBuf, 0, sectorData, 0, 2352);
sectorIsAudio[bufferIndex] = !IsData(sectorData);
readSuccess[bufferIndex] = true;
if(firstTrackPregapBlock >= -150)
{
var data = new byte[2352];
Array.Copy(cmdBuf, 0, data, 0, 2352);
outputOptical.WriteSectorLong(data, (ulong)(firstTrackPregapBlock * -1), true, SectorStatus.Dumped);
if(supportedSubchannel == MmcSubchannel.Raw)
{
var subchannel = new byte[96];
Array.Copy(cmdBuf, 2352, subchannel, 0, 96);
outputOptical.WriteSectorTag(subchannel,
(ulong)(firstTrackPregapBlock * -1),
true,
SectorTagType.CdSectorSubchannel);
}
firstTrackPregapSectorsGood++;
totalDuration += cmdDuration;
(_mediaGraph as Spiral)?.PaintCdLeadInSector(firstTrackPregapBlock, SKColors.Green);
}
else
firstTrackPregapMs.Write(cmdBuf, 0, (int)blockSize);
gotFirstTrackPregap = true;
firstTrackPregapSectorsGood++;
totalDuration += cmdDuration;
(_mediaGraph as Spiral)?.PaintCdLeadInSector(firstTrackPregapBlock, SKColors.Green);
}
else
{
// Write empty data
if(outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreNegativeSectors))
{
outputOptical.WriteSectorLong(new byte[2352],
(ulong)(firstTrackPregapBlock * -1),
true,
SectorStatus.Errored);
if(supportedSubchannel == MmcSubchannel.Raw)
{
outputOptical.WriteSectorTag(new byte[96],
(ulong)(firstTrackPregapBlock * -1),
true,
SectorTagType.CdSectorSubchannel);
}
}
else if(gotFirstTrackPregap) firstTrackPregapMs.Write(new byte[blockSize], 0, (int)blockSize);
(_mediaGraph as Spiral)?.PaintCdLeadInSector(firstTrackPregapBlock, SKColors.Red);
if(firstTrackPregapBlock >= -150)
(_mediaGraph as Spiral)?.PaintCdLeadInSector(firstTrackPregapBlock, SKColors.Red);
}
if(firstTrackPregapBlock < -150) continue;
sectorSpeedStart++;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
@@ -175,6 +181,149 @@ partial class Dump
_speedStopwatch.Stop();
// Apply offset correction if any audio sector is present in the pregap range
if(needsOffsetCorrection)
{
var hasAudioSectors = false;
for(var i = 0; i < totalSectorsToRead && !hasAudioSectors; i++)
{
int lba = startLba + i;
if(lba is >= -150 and <= -1 && readSuccess[i] && sectorIsAudio[i]) hasAudioSectors = true;
}
if(hasAudioSectors)
{
var blocksToRead = (uint)totalSectorsToRead;
FixOffsetData(offsetBytes,
2352,
sectorsForOffset,
supportedSubchannel,
ref blocksToRead,
subSize,
ref allSectorsBuffer,
blockSize,
false);
}
}
// Write sectors LBA -150 to -1 from the (possibly offset-corrected) buffer.
// After FixOffsetData the buffer always contains the corrected 150 pregap sectors
// starting at index 0, regardless of whether the offset was positive or negative.
var firstSectorWritten = false;
for(int firstTrackPregapBlock = -150; firstTrackPregapBlock <= -1; firstTrackPregapBlock++)
{
int bufferIndex = firstTrackPregapBlock + 150; // 0 = LBA -150, 149 = LBA -1
// Map back to the pre-correction read index to check whether the read succeeded.
// For negative offset the extra sectors were prepended, so index shifts by sectorsForOffset.
int originalIndex = needsOffsetCorrection && offsetBytes < 0 ? bufferIndex + sectorsForOffset : bufferIndex;
bool success = originalIndex < readSuccess.Length && readSuccess[originalIndex];
if(outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreNegativeSectors))
{
var data = new byte[2352];
Array.Copy(allSectorsBuffer, bufferIndex * (int)blockSize, data, 0, 2352);
outputOptical.WriteSectorLong(data,
(ulong)-firstTrackPregapBlock,
true,
success ? SectorStatus.Dumped : SectorStatus.Errored);
if(supportedSubchannel != MmcSubchannel.None && desiredSubchannel != MmcSubchannel.None)
{
var sub = new byte[subSize];
Array.Copy(allSectorsBuffer, bufferIndex * (int)blockSize + 2352, sub, 0, (int)subSize);
if(supportedSubchannel == MmcSubchannel.Q16) sub = Subchannel.ConvertQToRaw(sub);
subLog?.WriteEntry(sub,
supportedSubchannel == MmcSubchannel.Raw,
firstTrackPregapBlock,
1,
false,
false);
if(_fixSubchannelPosition)
{
// Write subchannel to the sector address its Q channel indicates,
// which may differ from the read position (positive or negative LBA).
byte[] deSub = Subchannel.Deinterleave(sub);
var q = new byte[12];
Array.Copy(deSub, 12, q, 0, 12);
CRC16CcittContext.Data(q, 10, out byte[] crc);
bool crcOk = crc[0] == q[10] && crc[1] == q[11];
if(crcOk && (q[0] & 0x3) == 1)
{
var amin = (byte)(q[7] / 16 * 10 + (q[7] & 0x0F));
var asec = (byte)(q[8] / 16 * 10 + (q[8] & 0x0F));
var aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F));
int aPos = amin * 60 * 75 + asec * 75 + aframe - 150;
if(aPos < 0)
{
outputOptical.WriteSectorTag(sub, (ulong)-aPos, true, SectorTagType.CdSectorSubchannel);
subchannelExtents.Remove(aPos);
}
else
{
outputOptical.WriteSectorTag(sub, (ulong)aPos, false, SectorTagType.CdSectorSubchannel);
subchannelExtents.Remove(aPos);
}
}
else
{
// Q CRC bad or not a position packet: fall back to read address
outputOptical.WriteSectorTag(sub,
(ulong)-firstTrackPregapBlock,
true,
SectorTagType.CdSectorSubchannel);
}
}
else
{
outputOptical.WriteSectorTag(sub,
(ulong)-firstTrackPregapBlock,
true,
SectorTagType.CdSectorSubchannel);
}
}
}
else
{
if(success)
{
firstSectorWritten = true;
firstTrackPregapMs.Write(allSectorsBuffer, bufferIndex * (int)blockSize, (int)blockSize);
}
else if(firstSectorWritten) firstTrackPregapMs.Write(new byte[blockSize], 0, (int)blockSize);
// Log subchannel even when we cannot store it individually in the image
if(supportedSubchannel != MmcSubchannel.None && success)
{
var sub = new byte[subSize];
Array.Copy(allSectorsBuffer, bufferIndex * (int)blockSize + 2352, sub, 0, (int)subSize);
if(supportedSubchannel == MmcSubchannel.Q16) sub = Subchannel.ConvertQToRaw(sub);
subLog?.WriteEntry(sub,
supportedSubchannel == MmcSubchannel.Raw,
firstTrackPregapBlock,
1,
false,
false);
}
}
}
if(firstTrackPregapSectorsGood > 0 &&
!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreNegativeSectors))
mediaTags.Add(MediaTagType.CD_FirstTrackPregap, firstTrackPregapMs.ToArray());