diff --git a/Aaru.Core/Aaru.Core.csproj b/Aaru.Core/Aaru.Core.csproj
index f1fb6140c..8ad8e47c1 100644
--- a/Aaru.Core/Aaru.Core.csproj
+++ b/Aaru.Core/Aaru.Core.csproj
@@ -54,100 +54,101 @@
false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
@@ -160,12 +161,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -174,23 +175,23 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs
index 8cceef1ab..03b988a8d 100644
--- a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs
+++ b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs
@@ -1234,6 +1234,12 @@ sealed partial class Dump
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);
diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Recordable.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Recordable.cs
new file mode 100644
index 000000000..e47f7aaef
--- /dev/null
+++ b/Aaru.Core/Devices/Dumping/CompactDisc/Recordable.cs
@@ -0,0 +1,153 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Recordable.cs
+// Author(s) : Natalia Portillo
+//
+// Component : CompactDisc dumping.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Handles run-out sectors at end of CD-R(W) discs.
+//
+// --[ 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-2022 Natalia Portillo
+// ****************************************************************************/
+
+namespace Aaru.Core.Devices.Dumping;
+
+using System.Collections.Generic;
+using System.Linq;
+using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Extents;
+using Aaru.CommonTypes.Interfaces;
+using Aaru.CommonTypes.Structs;
+using Aaru.Core.Logging;
+using Aaru.Decoders.CD;
+using Aaru.Devices;
+
+partial class Dump
+{
+ void HandleCdrRunOutSectors(ulong blocks, MmcSubchannel desiredSubchannel, ExtentsULong extents,
+ HashSet subchannelExtents, SubchannelLog subLog, bool supportsLongSectors,
+ Dictionary trackFlags, Track[] tracks)
+ {
+ List runOutSectors = new();
+
+ if(_outputPlugin is not IWritableOpticalImage outputOptical)
+ return;
+
+ // Count how many run end sectors are detected as bad blocks
+ for(ulong i = blocks - 1; i > blocks - 1 - _ignoreCdrRunOuts; i--)
+ if(_resume.BadBlocks.Contains(i))
+ runOutSectors.Add(i);
+ else
+ break;
+
+ if(runOutSectors.Count == 0)
+ return;
+
+ _dumpLog.WriteLine($"{runOutSectors.Count} sectors at the end of the disc are unreadable. This is normal in CD-R(W) discs as these sectors are created by burning software as part of the recording process. Empty ones will be generated and stored in the image.");
+
+ UpdateStatus?.
+ Invoke($"{runOutSectors.Count} sectors at the end of the disc are unreadable. This is normal in CD-R(W) discs as these sectors are created by burning software as part of the recording process. Empty ones will be generated and stored in the image.");
+
+ foreach(ulong s in runOutSectors)
+ {
+ Track track = tracks.FirstOrDefault(t => t.StartSector <= s && t.EndSector >= s);
+
+ if(track is null)
+ continue;
+
+ var sector = new byte[2352];
+
+ switch(track.Type)
+ {
+ case TrackType.Audio: break;
+ case TrackType.Data:
+ sector = new byte[2048];
+
+ break;
+ case TrackType.CdMode1: break;
+ case TrackType.CdMode2Formless: break;
+ case TrackType.CdMode2Form1: break;
+ case TrackType.CdMode2Form2: break;
+ default: continue;
+ }
+
+ if(track.Type != TrackType.Audio &&
+ track.Type != TrackType.Data)
+ {
+ SectorBuilder sb = new();
+ sb.ReconstructPrefix(ref sector, track.Type, (long)s);
+ sb.ReconstructEcc(ref sector, track.Type);
+ }
+
+ if(supportsLongSectors)
+ outputOptical.WriteSectorLong(sector, s);
+ else
+ outputOptical.WriteSector(Sector.GetUserData(sector), s);
+
+ _resume.BadBlocks.Remove(s);
+ extents.Add(s);
+
+ if(desiredSubchannel == MmcSubchannel.None)
+ continue;
+
+ // Hidden track
+ ulong trackStart;
+
+ ulong pregap;
+
+ if(track.Sequence == 0)
+ {
+ track = tracks.FirstOrDefault(t => (int)t.Sequence == 1);
+ trackStart = 0;
+ pregap = track?.StartSector ?? 0;
+ }
+ else
+ {
+ trackStart = track.StartSector;
+ pregap = track.Pregap;
+ }
+
+ byte flags;
+
+ if(!trackFlags.TryGetValue((byte)(track?.Sequence ?? 0), out byte trkFlags) &&
+ track?.Type != TrackType.Audio)
+ flags = (byte)CdFlags.DataTrack;
+ else
+ flags = trkFlags;
+
+ byte index;
+
+ if(track?.Indexes?.Count > 0)
+ index = (byte)track.Indexes.LastOrDefault(i => i.Value >= (int)s).Key;
+ else
+ index = 0;
+
+ byte[] sub = Subchannel.Generate((int)s, track?.Sequence ?? 0, (int)pregap, (int)trackStart, flags, index);
+
+ outputOptical.WriteSectorsTag(sub, s, 1, SectorTagType.CdSectorSubchannel);
+
+ subLog?.WriteEntry(sub, true, (long)s, 1, true, false);
+ subchannelExtents.Remove((int)s);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Devices/Dumping/Dump.cs b/Aaru.Core/Devices/Dumping/Dump.cs
index 17c3692a0..aec1866fb 100644
--- a/Aaru.Core/Devices/Dumping/Dump.cs
+++ b/Aaru.Core/Devices/Dumping/Dump.cs
@@ -99,6 +99,7 @@ public partial class Dump
Database.Models.Device _dbDev; // Device database entry
bool _dumpFirstTrackPregap;
bool _fixOffset;
+ readonly uint _ignoreCdrRunOuts;
uint _maximumReadable; // Maximum number of sectors drive can read at once
Resume _resume;
Sidecar _sidecarClass;
@@ -149,6 +150,7 @@ public partial class Dump
///
/// Store encrypted data as is
/// Dump DVD CSS title keys
+ /// How many CD-R(W) run end sectors to ignore and regenerate
public Dump(bool doResume, Device dev, string devicePath, IBaseWritableImage outputPlugin, ushort retryPasses,
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, DumpLog dumpLog,
Encoding encoding, string outputPrefix, string outputPath, Dictionary formatOptions,
@@ -156,7 +158,7 @@ public partial class Dump
bool fixOffset, bool debug, DumpSubchannel subchannel, int speed, bool @private,
bool fixSubchannelPosition, bool retrySubchannel, bool fixSubchannel, bool fixSubchannelCrc,
bool skipCdireadyHole, ErrorLog errorLog, bool generateSubchannels, uint maximumReadable,
- bool useBufferedReads, bool storeEncrypted, bool titleKeys)
+ bool useBufferedReads, bool storeEncrypted, bool titleKeys, uint ignoreCdrRunOuts)
{
_doResume = doResume;
_dev = dev;
@@ -196,6 +198,7 @@ public partial class Dump
_useBufferedReads = useBufferedReads;
_storeEncrypted = storeEncrypted;
_titleKeys = titleKeys;
+ _ignoreCdrRunOuts = ignoreCdrRunOuts;
}
/// Starts dumping with the established fields and autodetecting the device type
diff --git a/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs b/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
index 24470df75..7f33e21ee 100644
--- a/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
+++ b/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
@@ -812,7 +812,7 @@ public sealed class MediaDumpViewModel : ViewModelBase
StopOnError, _resume, dumpLog, encoding, _outputPrefix, Destination, parsedOptions, _sidecar,
(uint)Skipped, ExistingMetadata == false, Trim == false, Track1Pregap, true, false,
DumpSubchannel.Any, 0, false, false, false, false, false, true, errorLog, false, 64, true,
- true, false);
+ true, false, 10);
new Thread(DoWork).Start();
}
diff --git a/Aaru/Commands/Media/Dump.cs b/Aaru/Commands/Media/Dump.cs
index d3dbacdd6..7d51f40bb 100644
--- a/Aaru/Commands/Media/Dump.cs
+++ b/Aaru/Commands/Media/Dump.cs
@@ -201,6 +201,11 @@ sealed class DumpMediaCommand : Command
"--title-keys"
}, () => true, "Try to read the title keys from CSS encrypted DVDs (very slow)."));
+ Add(new Option(new[]
+ {
+ "--ignore-cdr-runouts"
+ }, () => 10, "How many CD-R(W) run-out sectors to ignore and regenerate (0 for none)."));
+
Handler = CommandHandler.Create(GetType().GetMethod(nameof(Invoke)));
}
@@ -210,7 +215,8 @@ sealed class DumpMediaCommand : Command
bool stopOnError, string format, string subchannel, bool @private,
bool fixSubchannelPosition, bool retrySubchannel, bool fixSubchannel,
bool fixSubchannelCrc, bool generateSubchannels, bool skipCdiReadyHole, bool eject,
- uint maxBlocks, bool useBufferedReads, bool storeEncrypted, bool titleKeys)
+ uint maxBlocks, bool useBufferedReads, bool storeEncrypted, bool titleKeys,
+ uint ignoreCdrRunOuts)
{
MainClass.PrintCopyright();
@@ -278,6 +284,7 @@ sealed class DumpMediaCommand : Command
AaruConsole.DebugWriteLine("Dump-Media command", "--use-buffered-reads={0}", useBufferedReads);
AaruConsole.DebugWriteLine("Dump-Media command", "--store-encrypted={0}", storeEncrypted);
AaruConsole.DebugWriteLine("Dump-Media command", "--title-keys={0}", titleKeys);
+ AaruConsole.DebugWriteLine("Dump-Media command", "--ignore-cdr-runouts={0}", ignoreCdrRunOuts);
// TODO: Disabled temporarily
//AaruConsole.DebugWriteLine("Dump-Media command", "--raw={0}", raw);
@@ -570,7 +577,8 @@ sealed class DumpMediaCommand : Command
outputPrefix + extension, parsedOptions, sidecar, skip, metadata, trim, firstPregap,
fixOffset, debug, wantedSubchannel, speed, @private, fixSubchannelPosition,
retrySubchannel, fixSubchannel, fixSubchannelCrc, skipCdiReadyHole, errorLog,
- generateSubchannels, maxBlocks, useBufferedReads, storeEncrypted, titleKeys);
+ generateSubchannels, maxBlocks, useBufferedReads, storeEncrypted, titleKeys,
+ ignoreCdrRunOuts);
AnsiConsole.Progress().AutoClear(true).HideCompleted(true).
Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()).