Files
Aaru/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs

1398 lines
58 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Dump.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : CompactDisc dumping.
//
// --[ Description ] ----------------------------------------------------------
//
// Dumps CompactDiscs.
//
// --[ 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.Core.Graphics;
using Aaru.Core.Logging;
using Aaru.Core.Media.Detection;
using Aaru.Database.Models;
using Aaru.Decoders.CD;
using Aaru.Devices;
using Aaru.Images;
using Aaru.Localization;
using Aaru.Logging;
using Humanizer;
using Spectre.Console;
using Track = Aaru.CommonTypes.Structs.Track;
using TrackType = Aaru.CommonTypes.Enums.TrackType;
using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Core.Devices.Dumping;
/// <summary>Implement dumping Compact Discs</summary>
// TODO: Barcode
sealed partial class Dump
{
/// <summary>Dumps a compact disc</summary>
void CompactDisc()
{
ExtentsULong audioExtents; // Extents with audio sectors
ulong blocks; // Total number of positive sectors
uint blockSize; // Size of the read sector in bytes
bool bcdSubchannel; // Subchannel positioning is in BCD
CdOffset cdOffset; // Read offset from database
byte[] cmdBuf; // Data buffer
DumpHardware currentTry = null; // Current dump hardware try
double currentSpeed = 0; // Current read speed
int? discOffset = null; // Disc write offset
MmcSubchannel desiredSubchannel; // User requested subchannel
ExtentsULong extents = null; // Extents
bool hiddenData; // Hidden track is data
IbgLog ibgLog; // IMGBurn log
double imageWriteDuration = 0; // Duration of image write
long lastSector; // Last sector number
var leadOutExtents = new ExtentsULong(); // Lead-out extents
Dictionary<int, long> leadOutStarts = []; // Lead-out starts
double maxSpeed = double.MinValue; // Maximum speed
MhddLog mhddLog; // MHDD log
double minSpeed = double.MaxValue; // Minimum speed
bool newTrim; // Is trim a new one?
var offsetBytes = 0; // Read offset
bool ret; // Image writing return status
bool read6; // Device supports READ(6)
bool read10; // Device supports READ(10)
bool read12; // Device supports READ(12)
bool read16; // Device supports READ(16)
bool readcd; // Device supports READ CD
var sectorsForOffset = 0; // Sectors needed to fix offset
const uint sectorSize = 2352; // Full sector size
uint subSize; // Subchannel size in bytes
int sessions; // Number of sessions in disc
SubchannelLog subLog = null; // Subchannel log
var supportsLongSectors = true; // Supports reading EDC and ECC
FullTOC.CDFullTOC? toc; // Full CD TOC
double totalDuration = 0; // Total commands duration
Dictionary<byte, byte> trackFlags = []; // Track flags
Track[] tracks; // Tracks in disc
var sense = true; // Sense indicator
TrackSubchannelType subType; // Track subchannel type
bool supportsPqSubchannel; // Supports reading PQ subchannel
bool supportsRwSubchannel; // Supports reading RW subchannel
MmcSubchannel supportedSubchannel; // Drive's maximum supported subchannel
int firstTrackLastSession; // Number of first track in last session
bool hiddenTrack; // Disc has a hidden track before track 1
bool shallReturn; // Shall return due to errors
Dictionary<byte, string> isrcs = [];
string mcn = null;
HashSet<int> subchannelExtents = [];
var cdiReadyReadAsAudio = false;
uint firstLba;
var outputOptical = _outputPlugin as IWritableOpticalImage;
Dictionary<MediaTagType, byte[]> mediaTags = []; // Media tags
Dictionary<byte, int> smallestPregapLbaPerTrack = [];
MediaType dskType = MediaType.CD;
if(_dumpRaw)
{
AaruLogging.WriteLine(Localization.Core.Raw_CD_dumping_not_yet_implemented);
StoppingErrorMessage?.Invoke(Localization.Core.Raw_CD_dumping_not_yet_implemented);
return;
}
tracks = GetCdTracks(_dev,
_force,
out lastSector,
leadOutStarts,
mediaTags,
StoppingErrorMessage,
out toc,
trackFlags,
UpdateStatus);
if(tracks is null)
{
AaruLogging.WriteLine(Localization.Core.Could_not_get_tracks);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_get_tracks);
return;
}
firstLba = (uint)tracks.Min(static t => t.StartSector);
shallReturn = CheckSubchannelSupport(firstLba,
outputOptical,
out subSize,
out subType,
out supportsPqSubchannel,
out supportsRwSubchannel,
out desiredSubchannel,
out supportedSubchannel,
out read6,
out read10,
out read12,
out read16,
out readcd,
out bcdSubchannel);
if(shallReturn) return;
blockSize = sectorSize + subSize;
foreach(Track trk in tracks) trk.SubchannelType = subType;
UpdateStatus?.Invoke(Localization.Core.Calculating_pregaps__can_take_some_time);
SolveTrackPregaps(_dev,
UpdateStatus,
tracks,
supportsPqSubchannel,
supportsRwSubchannel,
_dbDev,
out bool inexactPositioning,
true);
if(inexactPositioning)
UpdateStatus?.Invoke(Localization.Core.The_drive_has_returned_incorrect_Q_positioning_calculating_pregaps);
if(!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreRawData))
{
if(!_force)
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_storing_raw_data_not_continuing);
return;
}
ErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_storing_raw_data_continuing);
}
if(!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreAudioTracks) &&
tracks.Any(static track => track.Type == TrackType.Audio))
{
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_audio_tracks_cannot_continue);
return;
}
if(!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStorePregaps) &&
tracks.Any(track => track.Sequence != tracks.First(t => t.Session == track.Session).Sequence &&
track.Pregap > 0))
{
if(!_force)
{
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_pregaps_not_continuing);
return;
}
ErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_pregaps_continuing);
}
for(var t = 1; t < tracks.Length; t++) tracks[t - 1].EndSector = tracks[t].StartSector - 1;
tracks[^1].EndSector = (ulong)lastSector;
blocks = (ulong)(lastSector + 1);
if(blocks == 0)
{
StoppingErrorMessage?.Invoke(Localization.Core.Cannot_dump_blank_media);
return;
}
ResumeSupport.Process(true,
true,
blocks,
_dev.Manufacturer,
_dev.Model,
_dev.Serial,
_dev.PlatformId,
ref _resume,
ref currentTry,
ref extents,
_dev.FirmwareRevision,
_private,
_force);
if(currentTry == null || extents == null)
{
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_process_resume_file_not_continuing);
return;
}
// Read media tags
ReadCdTags(ref dskType, mediaTags, out sessions, out firstTrackLastSession);
if(!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreSessions) && sessions > 1)
{
// TODO: Disabled until 6.0
/*if(!_force)
{*/
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_sessions);
return;
/*}
_dumpLog.WriteLine("Output format does not support sessions, this will end in a loss of data, continuing...");
ErrorMessage?.
Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
}
// Check if output format supports all disc tags we have retrieved so far
foreach(MediaTagType tag in mediaTags.Keys.Where(tag => !outputOptical.SupportedMediaTags.Contains(tag)))
{
if(_force)
ErrorMessage?.Invoke(string.Format(Localization.Core.Output_format_does_not_support_0_continuing, tag));
else
{
StoppingErrorMessage?.Invoke(string.Format(Localization.Core
.Output_format_does_not_support_0_not_continuing,
tag));
return;
}
}
if(leadOutStarts.Any())
{
UpdateStatus?.Invoke(Localization.Core.Solving_lead_outs);
foreach(KeyValuePair<int, long> leadOuts in leadOutStarts)
{
foreach(Track trk in tracks.Where(trk => trk.Session == leadOuts.Key &&
trk.EndSector >= (ulong)leadOuts.Value))
trk.EndSector = (ulong)leadOuts.Value - 1;
}
var dataExtents = new ExtentsULong();
foreach(Track trk in tracks) dataExtents.Add(trk.StartSector, trk.EndSector);
Tuple<ulong, ulong>[] dataExtentsArray = dataExtents.ToArray();
for(var i = 0; i < dataExtentsArray.Length - 1; i++)
leadOutExtents.Add(dataExtentsArray[i].Item2 + 1, dataExtentsArray[i + 1].Item1 - 1);
}
UpdateStatus?.Invoke(Localization.Core.Detecting_disc_type);
MMC.DetectDiscType(ref dskType,
sessions,
toc,
_dev,
out hiddenTrack,
out hiddenData,
firstTrackLastSession,
blocks);
// Fix CD-i discs with wrong Lead-Out type
if(dskType is MediaType.CDI or MediaType.CDIREADY && tracks.Length == 1 && tracks[0].Type == TrackType.Audio)
tracks[0].Type = TrackType.CdMode2Formless;
if(hiddenTrack || firstLba > 0)
{
UpdateStatus?.Invoke(Localization.Core.Disc_contains_a_hidden_track);
if(!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreHiddenTracks))
{
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_hidden_tracks);
return;
}
List<Track> trkList =
[
new()
{
Sequence = (uint)(tracks.Any(static t => t.Sequence == 1) ? 0 : 1),
Session = 1,
Type = hiddenData ? TrackType.Data : TrackType.Audio,
StartSector = 0,
BytesPerSector = (int)sectorSize,
RawBytesPerSector = (int)sectorSize,
SubchannelType = subType,
EndSector = tracks.First(static t => t.Sequence >= 1).StartSector - 1
},
.. tracks
];
tracks = [.. trkList];
}
if(tracks.Any(static t => t.Type == TrackType.Audio) && desiredSubchannel != MmcSubchannel.Raw)
UpdateStatus?.Invoke(Localization.Core.WARNING_If_disc_says_CDG_CDEG_CDMIDI_dump_will_be_incorrect);
CheckTracksMode(readcd, tracks, blockSize, supportedSubchannel, dskType);
if(outputOptical.Id == new Guid("12345678-AAAA-BBBB-CCCC-123456789000"))
{
if(tracks.Length > 1)
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_more_than_1_track_not_continuing);
return;
}
if(tracks.Any(static t => t.Type == TrackType.Audio))
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_audio_tracks_not_continuing);
return;
}
if(tracks.Any(static t => t.Type != TrackType.CdMode1))
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_only_supports_MODE_1_tracks_not_continuing);
return;
}
supportsLongSectors = false;
}
// Check if something prevents from dumping the first track pregap
if(_dumpFirstTrackPregap && readcd)
{
if(!outputOptical.SupportedMediaTags.Contains(MediaTagType.CD_FirstTrackPregap))
{
if(_force)
{
ErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_CD_first_track_pregap_continuing);
}
else
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_CD_first_track_pregap_not_continuing);
return;
}
_dumpFirstTrackPregap = false;
}
}
// Try how many blocks are readable at once
while(true)
{
if(readcd)
{
sense = _dev.ReadCd(out cmdBuf,
out _,
firstLba,
blockSize,
_maximumReadable,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out _);
if(_dev.Error || sense) _maximumReadable /= 2;
}
else if(read16)
{
sense = _dev.Read16(out cmdBuf,
out _,
0,
false,
true,
false,
firstLba,
blockSize,
0,
_maximumReadable,
false,
_dev.Timeout,
out _);
if(_dev.Error || sense) _maximumReadable /= 2;
}
else if(read12)
{
sense = _dev.Read12(out cmdBuf,
out _,
0,
false,
true,
false,
false,
firstLba,
blockSize,
0,
_maximumReadable,
false,
_dev.Timeout,
out _);
if(_dev.Error || sense) _maximumReadable /= 2;
}
else if(read10)
{
sense = _dev.Read10(out cmdBuf,
out _,
0,
false,
true,
false,
false,
firstLba,
blockSize,
0,
(ushort)_maximumReadable,
_dev.Timeout,
out _);
if(_dev.Error || sense) _maximumReadable /= 2;
}
else if(read6)
{
sense = _dev.Read6(out cmdBuf, out _, firstLba, blockSize, (byte)_maximumReadable, _dev.Timeout, out _);
if(_dev.Error || sense) _maximumReadable /= 2;
}
if(!_dev.Error || _maximumReadable == 1) break;
}
if(_dev.Error || sense)
{
StoppingErrorMessage?.Invoke(string.Format(Localization.Core
.Device_error_0_trying_to_guess_ideal_transfer_length,
_dev.LastError));
return;
}
var cdiWithHiddenTrack1 = false;
if(dskType is MediaType.CDIREADY && tracks.Min(static t => t.Sequence) == 1)
{
cdiWithHiddenTrack1 = true;
dskType = MediaType.CDI;
}
UpdateStatus?.Invoke(string.Format(Localization.Core.Reading_0_sectors_at_a_time, _maximumReadable));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_blocks_1_bytes,
blocks,
blocks * blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_can_read_0_blocks_at_a_time, _maximumReadable));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_bytes_per_logical_block, blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.SCSI_device_type_0, _dev.ScsiType));
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_identified_as_0, dskType.Humanize()));
ret = outputOptical.Create(_outputPath,
dskType,
_formatOptions,
blocks,
(uint)(outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities
.CanStoreNegativeSectors)
? 2750
: 0),
(uint)(outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities
.CanStoreOverflowSectors)
? 2750
: 0),
supportsLongSectors ? blockSize : 2048);
// Cannot create image
if(!ret)
{
AaruLogging.WriteLine(Localization.Core.Error_creating_output_image_not_continuing);
AaruLogging.WriteLine(outputOptical.ErrorMessage);
StoppingErrorMessage?.Invoke(Localization.Core.Error_creating_output_image_not_continuing +
Environment.NewLine +
outputOptical.ErrorMessage);
return;
}
if(outputOptical is AaruFormat aif && _errorRecovery > 0) aif.SetErasureCodingAuto((byte)_errorRecovery);
ErrorNumber errno = outputOptical.ReadMediaTag(MediaTagType.CD_MCN, out byte[] mcnBytes);
if(errno == ErrorNumber.NoError) mcn = Encoding.ASCII.GetString(mcnBytes);
// Resuming, read previous tracks
if(outputOptical.Tracks != null)
{
foreach(Track imgTrack in outputOptical.Tracks)
{
errno = outputOptical.ReadSectorTag(imgTrack.Sequence,
false,
SectorTagType.CdTrackIsrc,
out byte[] isrcBytes);
if(errno == ErrorNumber.NoError) isrcs[(byte)imgTrack.Sequence] = Encoding.ASCII.GetString(isrcBytes);
Track trk = tracks.FirstOrDefault(t => t.Sequence == imgTrack.Sequence);
if(trk == null) continue;
trk.Pregap = imgTrack.Pregap;
trk.StartSector = imgTrack.StartSector;
trk.EndSector = imgTrack.EndSector;
foreach(KeyValuePair<ushort, int> imgIdx in imgTrack.Indexes) trk.Indexes[imgIdx.Key] = imgIdx.Value;
}
}
// Send track list to output plugin. This may fail if subchannel is set but unsupported.
ret = outputOptical.SetTracks([.. tracks]);
if(!ret && desiredSubchannel == MmcSubchannel.None)
{
AaruLogging.WriteLine(Localization.Core.Error_sending_tracks_to_output_image_not_continuing);
AaruLogging.WriteLine(outputOptical.ErrorMessage);
StoppingErrorMessage?.Invoke(Localization.Core.Error_sending_tracks_to_output_image_not_continuing +
Environment.NewLine +
outputOptical.ErrorMessage);
return;
}
// If a subchannel is supported, check if output plugin allows us to write it.
if(desiredSubchannel != MmcSubchannel.None &&
!outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreSubchannelRw))
{
if(_force)
ErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_subchannels_continuing);
else
{
StoppingErrorMessage?.Invoke(Localization.Core
.Output_format_does_not_support_subchannels_not_continuing);
return;
}
}
if(supportedSubchannel != MmcSubchannel.None)
{
AaruLogging.WriteLine(string.Format(Localization.Core.Creating_subchannel_log_in_0,
Markup.Escape(_outputPrefix + ".sub.log")));
subLog = new SubchannelLog(_outputPrefix + ".sub.log", bcdSubchannel);
}
// Set track flags
foreach(KeyValuePair<byte, byte> kvp in trackFlags)
{
Track track = tracks.FirstOrDefault(t => t.Sequence == kvp.Key);
if(track is null) continue;
UpdateStatus?.Invoke(string.Format(Localization.Core.Setting_flags_for_track_0, track.Sequence));
outputOptical.WriteSectorTag([kvp.Value], kvp.Key, false, SectorTagType.CdTrackFlags);
}
// Set MCN
if(supportedSubchannel == MmcSubchannel.None)
{
sense = _dev.ReadMcn(out mcn, out _, out _, _dev.Timeout, out _);
if(!sense && mcn != null && mcn != "0000000000000")
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_Media_Catalogue_Number_0, mcn));
else
mcn = null;
}
// Set ISRCs
if(supportedSubchannel == MmcSubchannel.None)
{
foreach(Track trk in tracks)
{
sense = _dev.ReadIsrc((byte)trk.Sequence, out string isrc, out _, out _, _dev.Timeout, out _);
if(sense || isrc is null or "000000000000") continue;
isrcs[(byte)trk.Sequence] = isrc;
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_ISRC_for_track_0_1, trk.Sequence, isrc));
}
}
if(supportedSubchannel != MmcSubchannel.None && desiredSubchannel != MmcSubchannel.None)
{
subchannelExtents = [];
_resume.BadSubchannels ??= [];
foreach(int sub in _resume.BadSubchannels) subchannelExtents.Add(sub);
if(_resume.NextBlock < blocks)
for(ulong i = _resume.NextBlock; i < blocks; i++)
subchannelExtents.Add((int)i);
}
if(_resume.NextBlock > 0)
UpdateStatus?.Invoke(string.Format(Localization.Core.Resuming_from_block_0, _resume.NextBlock));
if(_skip < _maximumReadable) _skip = _maximumReadable;
#if DEBUG
foreach(Track trk in tracks)
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Track_0_starts_at_LBA_1_and_ends_at_LBA_2,
trk.Sequence,
trk.StartSector,
trk.EndSector));
}
#endif
// Check offset
if(_fixOffset)
{
if(tracks.All(static t => t.Type != TrackType.Audio))
{
// No audio tracks so no need to fix offset
UpdateStatus?.Invoke(Localization.Core.No_audio_tracks_disabling_offset_fix);
_fixOffset = false;
}
if(!readcd)
{
UpdateStatus?.Invoke(Localization.Core
.READ_CD_command_is_not_supported_disabling_offset_fix_Dump_may_not_be_correct);
_fixOffset = false;
}
}
else if(tracks.Any(static t => t.Type == TrackType.Audio))
{
UpdateStatus?.Invoke(Localization.Core
.There_are_audio_tracks_and_offset_fixing_is_disabled_dump_may_not_be_correct);
}
// Search for read offset in main database
cdOffset =
_ctx.CdOffsets.FirstOrDefault(d => (d.Manufacturer == _dev.Manufacturer ||
d.Manufacturer == _dev.Manufacturer.Replace('/', '-')) &&
(d.Model == _dev.Model || d.Model == _dev.Model.Replace('/', '-')));
Media.Info.CompactDisc.GetOffset(cdOffset,
_dbDev,
_debug,
_dev,
dskType,
tracks,
UpdateStatus,
out int? driveOffset,
out int? combinedOffset,
out _supportsPlextorD8);
if(combinedOffset is null)
{
if(driveOffset is null)
{
UpdateStatus?.Invoke(Localization.Core.Drive_reading_offset_not_found_in_database);
UpdateStatus?.Invoke(Localization.Core.Disc_offset_cannot_be_calculated);
if(tracks.Any(static t => t.Type == TrackType.Audio))
UpdateStatus?.Invoke(Localization.Core.Dump_may_not_be_correct);
if(_fixOffset) _fixOffset = false;
}
else
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Drive_reading_offset_is_0_bytes_1_samples,
driveOffset,
driveOffset / 4));
UpdateStatus?.Invoke(Localization.Core.Disc_write_offset_is_unknown_dump_may_not_be_correct);
offsetBytes = driveOffset.Value;
sectorsForOffset = offsetBytes / (int)sectorSize;
if(sectorsForOffset < 0) sectorsForOffset *= -1;
if(offsetBytes % sectorSize != 0) sectorsForOffset++;
}
}
else
{
offsetBytes = combinedOffset.Value;
sectorsForOffset = offsetBytes / (int)sectorSize;
if(sectorsForOffset < 0) sectorsForOffset *= -1;
if(offsetBytes % sectorSize != 0) sectorsForOffset++;
if(driveOffset is null)
{
UpdateStatus?.Invoke(Localization.Core.Drive_reading_offset_not_found_in_database);
UpdateStatus?.Invoke(string.Format(Localization.Core
.Combined_disc_and_drive_offset_are_0_bytes_1_samples,
offsetBytes,
offsetBytes / 4));
}
else
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Drive_reading_offset_is_0_bytes_1_samples,
driveOffset,
driveOffset / 4));
discOffset = offsetBytes - driveOffset;
UpdateStatus?.Invoke(string.Format(Localization.Core.Disc_offset_is_0_bytes_1_samples,
discOffset,
discOffset / 4));
}
}
mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin",
_dev,
blocks,
blockSize,
_maximumReadable,
_private,
_dimensions);
ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0008);
if(_createGraph)
{
Spiral.DiscParameters discSpiralParameters = Spiral.DiscParametersFromMediaType(dskType);
if(discSpiralParameters is not null)
_mediaGraph = new Spiral((int)_dimensions, (int)_dimensions, discSpiralParameters, blocks);
else
_mediaGraph = new BlockMap((int)_dimensions, (int)_dimensions, blocks);
if(_mediaGraph is not null)
{
foreach(Tuple<ulong, ulong> e in extents.ToArray())
_mediaGraph?.PaintSectorsGood(e.Item1, (uint)(e.Item2 - e.Item1 + 2));
}
_mediaGraph?.PaintSectorsBad(_resume.BadBlocks);
}
// Try to read the first track pregap
if(_dumpFirstTrackPregap && (readcd || _omnidrive))
{
ReadCdFirstTrackPregap(blockSize,
ref currentSpeed,
mediaTags,
supportedSubchannel,
ref totalDuration,
outputOptical,
subLog,
offsetBytes,
sectorsForOffset,
desiredSubchannel,
subchannelExtents);
}
audioExtents = new ExtentsULong();
foreach(Track audioTrack in tracks.Where(static t => t.Type == TrackType.Audio))
audioExtents.Add(audioTrack.StartSector, audioTrack.EndSector);
// Set speed
if(_speedMultiplier >= 0)
{
UpdateStatus?.Invoke(_speed is 0xFFFF or 0
? Localization.Core.Setting_speed_to_MAX_for_data_reading
: string.Format(Localization.Core.Setting_speed_to_0_x_for_data_reading, _speed));
_speed *= _speedMultiplier;
if(_speed is 0 or > 0xFFFF) _speed = 0xFFFF;
_dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, (ushort)_speed, 0, _dev.Timeout, out _);
}
// Start reading
_dumpStopwatch.Restart();
if(dskType == MediaType.CDIREADY || cdiWithHiddenTrack1)
{
Track track0 = tracks.FirstOrDefault(static t => t.Sequence is 0 or 1);
track0.Type = TrackType.CdMode2Formless;
if(!supportsLongSectors)
{
StoppingErrorMessage?.Invoke(Localization.Core
.Dumping_CD_i_Ready_requires_the_output_image_format_to_support_long_sectors);
return;
}
if(!readcd)
{
StoppingErrorMessage?.Invoke(Localization.Core
.Dumping_CD_i_Ready_requires_the_drive_to_support_the_READ_CD_command);
return;
}
_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++;
UpdateStatus?.Invoke(Localization.Core
.Enabling_skipping_CD_i_Ready_hole_because_drive_returns_data_as_audio);
_skipCdireadyHole = true;
if(driveOffset is null)
{
UpdateStatus?.Invoke(Localization.Core.Drive_reading_offset_not_found_in_database);
UpdateStatus?.Invoke(string.Format(Localization.Core
.Combined_disc_and_drive_offset_are_0_bytes_1_samples,
offsetBytes,
offsetBytes / 4));
}
else
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Drive_reading_offset_is_0_bytes_1_samples,
driveOffset,
driveOffset / 4));
discOffset = offsetBytes - driveOffset;
UpdateStatus?.Invoke(string.Format(Localization.Core.Disc_offset_is_0_bytes_1_samples,
discOffset,
discOffset / 4));
}
}
}
if(!_skipCdireadyHole)
{
UpdateStatus?.Invoke(Localization.Core
.There_will_be_thousand_of_errors_between_track_0_and_track_1_that_is_normal_and_you_can_ignore_them);
}
if(_skipCdireadyHole)
{
// This codepath is disabled because it's doing strange things, let the OmniDrive behave like a normal drive here
/*
if(_omnidrive)
{
OmniDriveReadCdiReady(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,
smallestPregapLbaPerTrack);
}
else
{
*/
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,
smallestPregapLbaPerTrack);
//}
}
}
if(_omnidrive)
{
ReadCdDataOmniDrive(audioExtents,
blocks,
blockSize,
ref currentSpeed,
currentTry,
extents,
ibgLog,
ref imageWriteDuration,
lastSector,
leadOutExtents,
ref maxSpeed,
mhddLog,
ref minSpeed,
out newTrim,
offsetBytes,
sectorsForOffset,
subSize,
supportedSubchannel,
supportsLongSectors,
ref totalDuration,
tracks,
subLog,
desiredSubchannel,
isrcs,
ref mcn,
subchannelExtents,
smallestPregapLbaPerTrack);
}
else
{
ReadCdData(audioExtents,
blocks,
blockSize,
ref currentSpeed,
currentTry,
extents,
ibgLog,
ref imageWriteDuration,
lastSector,
leadOutExtents,
ref maxSpeed,
mhddLog,
ref minSpeed,
out newTrim,
tracks[0].Type != TrackType.Audio,
offsetBytes,
read6,
read10,
read12,
read16,
readcd,
sectorsForOffset,
subSize,
supportedSubchannel,
supportsLongSectors,
ref totalDuration,
tracks,
subLog,
desiredSubchannel,
isrcs,
ref mcn,
subchannelExtents,
smallestPregapLbaPerTrack);
}
if(_omnidrive &&
outputOptical.OpticalCapabilities.HasFlag(OpticalImageCapabilities.CanStoreOverflowSectors) &&
_leadout)
{
leadOutExtents.Add((ulong)(lastSector + 1), (ulong)(lastSector + 2749));
DumpCdLeadOuts(blockSize,
ref currentSpeed,
currentTry,
extents,
ibgLog,
ref imageWriteDuration,
leadOutExtents,
ref maxSpeed,
mhddLog,
ref minSpeed,
read6,
read10,
read12,
read16,
readcd,
supportedSubchannel,
subSize,
ref totalDuration,
subLog,
desiredSubchannel,
isrcs,
ref mcn,
tracks,
subchannelExtents,
smallestPregapLbaPerTrack,
offsetBytes,
sectorsForOffset);
}
_dumpStopwatch.Stop();
mhddLog.Close();
ibgLog.Close(_dev,
blocks,
blockSize,
_dumpStopwatch.Elapsed.TotalSeconds,
currentSpeed * 1024,
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
_devicePath);
UpdateStatus?.Invoke(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
TrimCdUserData(audioExtents,
blockSize,
currentTry,
extents,
newTrim,
offsetBytes,
read6,
read10,
read12,
read16,
readcd,
sectorsForOffset,
subSize,
supportedSubchannel,
supportsLongSectors,
ref totalDuration,
subLog,
desiredSubchannel,
tracks,
isrcs,
ref mcn,
subchannelExtents,
smallestPregapLbaPerTrack);
if(dskType is MediaType.CDR or MediaType.CDRW && _resume.BadBlocks.Count > 0 && _ignoreCdrRunOuts > 0)
{
HandleCdrRunOutSectors(blocks,
desiredSubchannel,
extents,
subchannelExtents,
subLog,
supportsLongSectors,
trackFlags,
tracks);
}
RetryCdUserData(audioExtents,
blockSize,
currentTry,
extents,
offsetBytes,
readcd,
sectorsForOffset,
subSize,
supportedSubchannel,
ref totalDuration,
subLog,
desiredSubchannel,
tracks,
isrcs,
ref mcn,
subchannelExtents,
smallestPregapLbaPerTrack,
supportsLongSectors);
foreach(Tuple<ulong, ulong> leadoutExtent in leadOutExtents.ToArray())
for(ulong e = leadoutExtent.Item1; e <= leadoutExtent.Item2; e++)
subchannelExtents.Remove((int)e);
if(subchannelExtents.Count > 0 && _retryPasses > 0 && _retrySubchannel)
{
RetrySubchannel(readcd,
subSize,
supportedSubchannel,
ref totalDuration,
subLog,
desiredSubchannel,
tracks,
isrcs,
ref mcn,
subchannelExtents,
smallestPregapLbaPerTrack);
}
// Write media tags to image
if(!_aborted)
{
foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags)
{
if(tag.Value is null)
{
AaruLogging.Error(Localization.Core.Error_Tag_type_0_is_null_skipping, tag.Key);
continue;
}
ret = outputOptical.WriteMediaTag(tag.Value, tag.Key);
if(ret || _force) continue;
// Cannot write tag to image
StoppingErrorMessage?.Invoke(outputOptical.ErrorMessage);
return;
}
}
_resume.BadBlocks.Sort();
foreach(ulong bad in _resume.BadBlocks)
AaruLogging.Information(Localization.Core.Sector_0_could_not_be_read, bad);
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_resume.BadSubchannels = [];
_resume.BadSubchannels.AddRange(subchannelExtents);
_resume.BadSubchannels.Sort();
if(_generateSubchannels &&
outputOptical.SupportedSectorTags.Contains(SectorTagType.CdSectorSubchannel) &&
!_aborted)
{
Media.CompactDisc.GenerateSubchannels(subchannelExtents,
tracks,
trackFlags,
blocks,
subLog,
InitProgress,
UpdateProgress,
EndProgress,
outputOptical);
}
// TODO: Disc ID
var metadata = new CommonTypes.Structs.ImageInfo
{
Application = "Aaru",
ApplicationVersion = Version.GetInformationalVersion()
};
if(!outputOptical.SetImageInfo(metadata))
{
ErrorMessage?.Invoke(Localization.Core.Error_0_setting_metadata +
Environment.NewLine +
outputOptical.ErrorMessage);
}
outputOptical.SetDumpHardware(_resume.Tries);
if(_preSidecar != null) outputOptical.SetMetadata(_preSidecar);
foreach(KeyValuePair<byte, string> isrc in isrcs)
{
// TODO: Track tags
if(!outputOptical.WriteSectorTag(Encoding.ASCII.GetBytes(isrc.Value),
isrc.Key,
false,
SectorTagType.CdTrackIsrc))
continue;
UpdateStatus?.Invoke(string.Format(Localization.Core.Setting_ISRC_for_track_0_to_1, isrc.Key, isrc.Value));
}
if(mcn != null && outputOptical.WriteMediaTag(Encoding.ASCII.GetBytes(mcn), MediaTagType.CD_MCN))
UpdateStatus?.Invoke(string.Format(Localization.Core.Setting_disc_Media_Catalogue_Number_to_0, mcn));
foreach(Track trk in tracks)
{
// Fix track starts in each session's first track
if(tracks.Where(t => t.Session == trk.Session).MinBy(static t => t.Sequence).Sequence == trk.Sequence)
{
if(trk.Sequence == 1) continue;
trk.StartSector -= trk.Pregap;
trk.Indexes[0] = (int)trk.StartSector;
continue;
}
if(trk.Indexes.TryGetValue(0, out int idx0) && trk.Indexes.TryGetValue(1, out int idx1) && idx0 == idx1)
trk.Indexes.Remove(0);
}
outputOptical.SetTracks(tracks.ToList());
UpdateStatus?.Invoke(Localization.Core.Closing_output_file);
_imageCloseStopwatch.Restart();
outputOptical.Close();
_imageCloseStopwatch.Stop();
UpdateStatus?.Invoke(string.Format(Localization.Core.Closed_in_0,
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
subLog?.Close();
if(_aborted)
{
AaruLogging.WriteLine(Localization.Core.Aborted);
return;
}
double totalChkDuration = 0;
if(_metadata)
{
WriteOpticalSidecar(blockSize,
blocks,
dskType,
null,
mediaTags,
sessions,
out totalChkDuration,
discOffset);
}
_dumpStopwatch.Stop();
UpdateStatus?.Invoke("");
UpdateStatus?.Invoke(string.Format(Localization.Core
.Took_a_total_of_0_1_processing_commands_2_checksumming_3_writing_4_closing,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second),
totalDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
totalChkDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
imageWriteDuration.Seconds().Humanize(minUnit: TimeUnit.Second),
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
if(maxSpeed > 0)
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Fastest_speed_burst_0,
ByteSize.FromMegabytes(maxSpeed).Per(_oneSecond).Humanize()));
}
if(minSpeed is > 0 and < double.MaxValue)
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Slowest_speed_burst_0,
ByteSize.FromMegabytes(minSpeed).Per(_oneSecond).Humanize()));
}
if(_omnidrive)
{
UpdateStatus?.Invoke(string.Format(UI._0_sectors_were_correct_when_read, _correctSectors));
UpdateStatus?.Invoke(string.Format(UI._0_sectors_had_to_be_fixed, _fixedSectors));
}
UpdateStatus?.Invoke(string.Format(Localization.Core._0_sectors_could_not_be_read, _resume.BadBlocks.Count));
UpdateStatus?.Invoke(string.Format(Localization.Core._0_subchannels_could_not_be_read,
_resume.BadSubchannels.Count));
UpdateStatus?.Invoke("");
Statistics.AddMedia(dskType, true);
}
}