Ejemplo n.º 1
0
        /// <summary>
        ///     Dumps an Xbox Game Disc using a Kreon drive
        /// </summary>
        /// <param name="dev">Device</param>
        /// <param name="devicePath">Path to the device</param>
        /// <param name="outputPrefix">Prefix for output data files</param>
        /// <param name="outputPlugin">Plugin for output file</param>
        /// <param name="retryPasses">How many times to retry</param>
        /// <param name="force">Force to continue dump whenever possible</param>
        /// <param name="dumpRaw">Dump raw/long sectors</param>
        /// <param name="persistent">Store whatever data the drive returned on error</param>
        /// <param name="stopOnError">Stop dump on first error</param>
        /// <param name="resume">Information for dump resuming</param>
        /// <param name="dumpLog">Dump logger</param>
        /// <param name="encoding">Encoding to use when analyzing dump</param>
        /// <param name="mediaTags">Media tags as retrieved in MMC layer</param>
        /// <param name="dskType">Disc type as detected in MMC layer</param>
        /// <param name="outputPath">Path to output file</param>
        /// <param name="formatOptions">Formats to pass to output file plugin</param>
        /// <exception cref="InvalidOperationException">
        ///     If the provided resume does not correspond with the current in progress
        ///     dump
        /// </exception>
        internal static void Dump(Device dev, string devicePath,
                                  IWritableImage outputPlugin, ushort retryPasses,
                                  bool force, bool dumpRaw,
                                  bool persistent, bool stopOnError,
                                  Dictionary <MediaTagType, byte[]> mediaTags, ref MediaType dskType,
                                  ref Resume resume,
                                  ref DumpLog dumpLog, Encoding encoding,
                                  string outputPrefix, string outputPath,
                                  Dictionary <string, string> formatOptions, CICMMetadataType preSidecar,
                                  uint skip,
                                  bool nometadata, bool notrim)
        {
            bool       sense;
            ulong      blocks;
            const uint BLOCK_SIZE   = 2048;
            uint       blocksToRead = 64;
            DateTime   start;
            DateTime   end;
            double     totalDuration = 0;
            double     currentSpeed  = 0;
            double     maxSpeed      = double.MinValue;
            double     minSpeed      = double.MaxValue;
            bool       aborted       = false;

            System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;

            if (mediaTags.ContainsKey(MediaTagType.DVD_PFI))
            {
                mediaTags.Remove(MediaTagType.DVD_PFI);
            }
            if (mediaTags.ContainsKey(MediaTagType.DVD_DMI))
            {
                mediaTags.Remove(MediaTagType.DVD_DMI);
            }

            dumpLog.WriteLine("Reading Xbox Security Sector.");
            sense = dev.KreonExtractSs(out byte[] ssBuf, out byte[] senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get Xbox Security Sector, not continuing.");
                DicConsole.ErrorWriteLine("Cannot get Xbox Security Sector, not continuing.");
                return;
            }

            dumpLog.WriteLine("Decoding Xbox Security Sector.");
            SS.SecuritySector?xboxSs = SS.Decode(ssBuf);
            if (!xboxSs.HasValue)
            {
                dumpLog.WriteLine("Cannot decode Xbox Security Sector, not continuing.");
                DicConsole.ErrorWriteLine("Cannot decode Xbox Security Sector, not continuing.");
                return;
            }

            byte[] tmpBuf = new byte[ssBuf.Length - 4];
            Array.Copy(ssBuf, 4, tmpBuf, 0, ssBuf.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_SecuritySector, tmpBuf);

            ulong l0Video, l1Video, middleZone, gameSize, totalSize, layerBreak;

            // Get video partition size
            DicConsole.DebugWriteLine("Dump-media command", "Getting video partition size");
            dumpLog.WriteLine("Locking drive.");
            sense = dev.KreonLock(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot lock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot lock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting video partition size.");
            sense = dev.ReadCapacity(out byte[] readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            totalSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]);
            dumpLog.WriteLine("Reading Physical Format Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get PFI.");
                DicConsole.ErrorWriteLine("Cannot get PFI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf);
            DicConsole.DebugWriteLine("Dump-media command", "Video partition total size: {0} sectors", totalSize);
            l0Video = PFI.Decode(readBuffer).Value.Layer0EndPSN - PFI.Decode(readBuffer).Value.DataAreaStartPSN + 1;
            l1Video = totalSize - l0Video + 1;
            dumpLog.WriteLine("Reading Disc Manufacturing Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get DMI.");
                DicConsole.ErrorWriteLine("Cannot get DMI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf);

            // Get game partition size
            DicConsole.DebugWriteLine("Dump-media command", "Getting game partition size");
            dumpLog.WriteLine("Unlocking drive (Xtreme).");
            sense = dev.KreonUnlockXtreme(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting game partition size.");
            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            gameSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]) +
                       1;
            DicConsole.DebugWriteLine("Dump-media command", "Game partition total size: {0} sectors", gameSize);

            // Get middle zone size
            DicConsole.DebugWriteLine("Dump-media command", "Getting middle zone size");
            dumpLog.WriteLine("Unlocking drive (Wxripper).");
            sense = dev.KreonUnlockWxripper(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting disc size.");
            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            totalSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]);
            dumpLog.WriteLine("Reading Physical Format Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get PFI.");
                DicConsole.ErrorWriteLine("Cannot get PFI.");
                return;
            }

            DicConsole.DebugWriteLine("Dump-media command", "Unlocked total size: {0} sectors", totalSize);
            blocks     = totalSize + 1;
            middleZone =
                totalSize - (PFI.Decode(readBuffer).Value.Layer0EndPSN -
                             PFI.Decode(readBuffer).Value.DataAreaStartPSN +
                             1) - gameSize + 1;

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_PFI, tmpBuf);

            dumpLog.WriteLine("Reading Disc Manufacturing Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get DMI.");
                DicConsole.ErrorWriteLine("Cannot get DMI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_DMI, tmpBuf);

            totalSize  = l0Video + l1Video + middleZone * 2 + gameSize;
            layerBreak = l0Video + middleZone + gameSize / 2;

            DicConsole.WriteLine("Video layer 0 size: {0} sectors", l0Video);
            DicConsole.WriteLine("Video layer 1 size: {0} sectors", l1Video);
            DicConsole.WriteLine("Middle zone size: {0} sectors", middleZone);
            DicConsole.WriteLine("Game data size: {0} sectors", gameSize);
            DicConsole.WriteLine("Total size: {0} sectors", totalSize);
            DicConsole.WriteLine("Real layer break: {0}", layerBreak);
            DicConsole.WriteLine();

            dumpLog.WriteLine("Video layer 0 size: {0} sectors", l0Video);
            dumpLog.WriteLine("Video layer 1 size: {0} sectors", l1Video);
            dumpLog.WriteLine("Middle zone 0 size: {0} sectors", middleZone);
            dumpLog.WriteLine("Game data 0 size: {0} sectors", gameSize);
            dumpLog.WriteLine("Total 0 size: {0} sectors", totalSize);
            dumpLog.WriteLine("Real layer break: {0}", layerBreak);

            bool read12 = !dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, BLOCK_SIZE, 0, 1,
                                      false, dev.Timeout, out _);

            if (!read12)
            {
                dumpLog.WriteLine("Cannot read medium, aborting scan...");
                DicConsole.ErrorWriteLine("Cannot read medium, aborting scan...");
                return;
            }

            dumpLog.WriteLine("Using SCSI READ (12) command.");
            DicConsole.WriteLine("Using SCSI READ (12) command.");

            while (true)
            {
                if (read12)
                {
                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, 0, BLOCK_SIZE, 0,
                                       blocksToRead, false, dev.Timeout, out _);
                    if (sense || dev.Error)
                    {
                        blocksToRead /= 2;
                    }
                }

                if (!dev.Error || blocksToRead == 1)
                {
                    break;
                }
            }

            if (dev.Error)
            {
                dumpLog.WriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                return;
            }

            if (skip < blocksToRead)
            {
                skip = blocksToRead;
            }

            bool ret = true;

            foreach (MediaTagType tag in mediaTags.Keys)
            {
                if (outputPlugin.SupportedMediaTags.Contains(tag))
                {
                    continue;
                }

                ret = false;
                dumpLog.WriteLine($"Output format does not support {tag}.");
                DicConsole.ErrorWriteLine($"Output format does not support {tag}.");
            }

            if (!ret)
            {
                dumpLog.WriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not ");
                DicConsole.ErrorWriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not ");
                if (!force)
                {
                    return;
                }
            }

            dumpLog.WriteLine("Reading {0} sectors at a time.", blocksToRead);
            DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead);

            MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, BLOCK_SIZE, blocksToRead);
            IbgLog  ibgLog  = new IbgLog(outputPrefix + ".ibg", 0x0010);

            ret = outputPlugin.Create(outputPath, dskType, formatOptions, blocks, BLOCK_SIZE);

            // Cannot create image
            if (!ret)
            {
                dumpLog.WriteLine("Error creating output image, not continuing.");
                dumpLog.WriteLine(outputPlugin.ErrorMessage);
                DicConsole.ErrorWriteLine("Error creating output image, not continuing.");
                DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage);
                return;
            }

            start = DateTime.UtcNow;
            double imageWriteDuration = 0;

            double           cmdDuration      = 0;
            uint             saveBlocksToRead = blocksToRead;
            DumpHardwareType currentTry       = null;
            ExtentsULong     extents          = null;

            ResumeSupport.Process(true, true, totalSize, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId,
                                  ref resume, ref currentTry, ref extents);
            if (currentTry == null || extents == null)
            {
                throw new NotImplementedException("Could not process resume file, not continuing...");
            }

            outputPlugin.SetTracks(new List <Track>
            {
                new Track
                {
                    TrackBytesPerSector    = (int)BLOCK_SIZE,
                    TrackEndSector         = blocks - 1,
                    TrackSequence          = 1,
                    TrackRawBytesPerSector = (int)BLOCK_SIZE,
                    TrackSubchannelType    = TrackSubchannelType.None,
                    TrackSession           = 1,
                    TrackType = TrackType.Data
                }
            });

            ulong currentSector = resume.NextBlock;

            if (resume.NextBlock > 0)
            {
                dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock);
            }
            bool newTrim = false;

            dumpLog.WriteLine("Reading game partition.");
            for (int e = 0; e <= 16; e++)
            {
                if (aborted)
                {
                    resume.NextBlock   = currentSector;
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (currentSector >= blocks)
                {
                    break;
                }

                ulong extentStart, extentEnd;
                // Extents
                if (e < 16)
                {
                    if (xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN)
                    {
                        extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000;
                    }
                    else
                    {
                        extentStart = (xboxSs.Value.Layer0EndPSN + 1) * 2 -
                                      ((xboxSs.Value.Extents[e].StartPSN ^ 0xFFFFFF) + 1) - 0x30000;
                    }
                    if (xboxSs.Value.Extents[e].EndPSN <= xboxSs.Value.Layer0EndPSN)
                    {
                        extentEnd = xboxSs.Value.Extents[e].EndPSN - 0x30000;
                    }
                    else
                    {
                        extentEnd = (xboxSs.Value.Layer0EndPSN + 1) * 2 -
                                    ((xboxSs.Value.Extents[e].EndPSN ^ 0xFFFFFF) + 1) - 0x30000;
                    }
                }
                // After last extent
                else
                {
                    extentStart = blocks;
                    extentEnd   = blocks;
                }

                if (currentSector > extentEnd)
                {
                    continue;
                }

                for (ulong i = currentSector; i < extentStart; i += blocksToRead)
                {
                    saveBlocksToRead = blocksToRead;

                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    if (extentStart - i < blocksToRead)
                    {
                        blocksToRead = (uint)(extentStart - i);
                    }

                    #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                    if (currentSpeed > maxSpeed && currentSpeed != 0)
                    {
                        maxSpeed = currentSpeed;
                    }
                    if (currentSpeed < minSpeed && currentSpeed != 0)
                    {
                        minSpeed = currentSpeed;
                    }
                    #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                    DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, totalSize, currentSpeed);

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)i, BLOCK_SIZE,
                                       0, blocksToRead, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (!sense && !dev.Error)
                    {
                        mhddLog.Write(i, cmdDuration);
                        ibgLog.Write(i, currentSpeed * 1024);
                        DateTime writeStart = DateTime.Now;
                        outputPlugin.WriteSectors(readBuffer, i, blocksToRead);
                        imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                        extents.Add(i, blocksToRead, true);
                    }
                    else
                    {
                        // TODO: Reset device after X errors
                        if (stopOnError)
                        {
                            return;             // TODO: Return more cleanly
                        }
                        if (i + skip > blocks)
                        {
                            skip = (uint)(blocks - i);
                        }

                        // Write empty data
                        DateTime writeStart = DateTime.Now;
                        outputPlugin.WriteSectors(new byte[BLOCK_SIZE * skip], i, skip);
                        imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;

                        for (ulong b = i; b < i + skip; b++)
                        {
                            resume.BadBlocks.Add(b);
                        }

                        DicConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf));
                        mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);

                        ibgLog.Write(i, 0);

                        dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i);
                        i += skip - blocksToRead;
                        string[] senseLines = Sense.PrettifySense(senseBuf).Split(new[] { Environment.NewLine },
                                                                                  StringSplitOptions
                                                                                  .RemoveEmptyEntries);
                        foreach (string senseLine in senseLines)
                        {
                            dumpLog.WriteLine(senseLine);
                        }
                        newTrim = true;
                    }

                    double newSpeed =
                        (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000);
                    if (!double.IsInfinity(newSpeed))
                    {
                        currentSpeed = newSpeed;
                    }
                    blocksToRead     = saveBlocksToRead;
                    currentSector    = i + 1;
                    resume.NextBlock = currentSector;
                }

                for (ulong i = extentStart; i <= extentEnd; i += blocksToRead)
                {
                    saveBlocksToRead = blocksToRead;
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    if (extentEnd - i < blocksToRead)
                    {
                        blocksToRead = (uint)(extentEnd - i) + 1;
                    }

                    mhddLog.Write(i, cmdDuration);
                    ibgLog.Write(i, currentSpeed * 1024);
                    // Write empty data
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], i, blocksToRead);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    blocksToRead        = saveBlocksToRead;
                    extents.Add(i, blocksToRead, true);
                    currentSector    = i + 1;
                    resume.NextBlock = currentSector;
                }

                if (!aborted)
                {
                    currentSector = extentEnd + 1;
                }
            }

            // Middle Zone D
            dumpLog.WriteLine("Writing Middle Zone D (empty).");
            for (ulong middle = currentSector - blocks - 1; middle < middleZone - 1; middle += blocksToRead)
            {
                if (aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (middleZone - 1 - middle < blocksToRead)
                {
                    blocksToRead = (uint)(middleZone - 1 - middle);
                }

                DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", middle + currentSector, totalSize,
                                 currentSpeed);

                mhddLog.Write(middle + currentSector, cmdDuration);
                ibgLog.Write(middle + currentSector, currentSpeed * 1024);
                // Write empty data
                DateTime writeStart = DateTime.Now;
                outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], middle + currentSector, blocksToRead);
                imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                extents.Add(currentSector, blocksToRead, true);

                currentSector   += blocksToRead;
                resume.NextBlock = currentSector;
            }

            blocksToRead = saveBlocksToRead;

            dumpLog.WriteLine("Locking drive.");
            sense = dev.KreonLock(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot lock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot lock drive, not continuing.");
                return;
            }

            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            // Video Layer 1
            dumpLog.WriteLine("Reading Video Layer 1.");
            for (ulong l1 = currentSector - blocks - middleZone + l0Video; l1 < l0Video + l1Video; l1 += blocksToRead)
            {
                if (aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (l0Video + l1Video - l1 < blocksToRead)
                {
                    blocksToRead = (uint)(l0Video + l1Video - l1);
                }

                #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                if (currentSpeed > maxSpeed && currentSpeed != 0)
                {
                    maxSpeed = currentSpeed;
                }
                if (currentSpeed < minSpeed && currentSpeed != 0)
                {
                    minSpeed = currentSpeed;
                }
                #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", currentSector, totalSize,
                                 currentSpeed);

                sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)l1, BLOCK_SIZE, 0,
                                   blocksToRead, false, dev.Timeout, out cmdDuration);
                totalDuration += cmdDuration;

                if (!sense && !dev.Error)
                {
                    mhddLog.Write(currentSector, cmdDuration);
                    ibgLog.Write(currentSector, currentSpeed * 1024);
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(readBuffer, currentSector, blocksToRead);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    extents.Add(currentSector, blocksToRead, true);
                }
                else
                {
                    // TODO: Reset device after X errors
                    if (stopOnError)
                    {
                        return;             // TODO: Return more cleanly
                    }
                    // Write empty data
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(new byte[BLOCK_SIZE * skip], currentSector, skip);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;

                    // TODO: Handle errors in video partition
                    //errored += blocksToRead;
                    //resume.BadBlocks.Add(l1);
                    DicConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf));
                    mhddLog.Write(l1, cmdDuration < 500 ? 65535 : cmdDuration);

                    ibgLog.Write(l1, 0);
                    dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, l1);
                    l1 += skip - blocksToRead;
                    string[] senseLines = Sense.PrettifySense(senseBuf).Split(new[] { Environment.NewLine },
                                                                              StringSplitOptions.RemoveEmptyEntries);
                    foreach (string senseLine in senseLines)
                    {
                        dumpLog.WriteLine(senseLine);
                    }
                }

                double newSpeed =
                    (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000);
                if (!double.IsInfinity(newSpeed))
                {
                    currentSpeed = newSpeed;
                }
                currentSector   += blocksToRead;
                resume.NextBlock = currentSector;
            }

            dumpLog.WriteLine("Unlocking drive (Wxripper).");
            sense = dev.KreonUnlockWxripper(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            end = DateTime.UtcNow;
            DicConsole.WriteLine();
            mhddLog.Close();
            ibgLog.Close(dev, blocks, BLOCK_SIZE, (end - start).TotalSeconds, currentSpeed * 1024,
                         BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
                         devicePath);
            dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
            dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
                              (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
            dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
                              (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / imageWriteDuration);

            #region Trimming
            if (resume.BadBlocks.Count > 0 && !aborted && !notrim && newTrim)
            {
                start = DateTime.UtcNow;
                dumpLog.WriteLine("Trimming bad sectors");

                ulong[] tmpArray = resume.BadBlocks.ToArray();
                foreach (ulong badSector in tmpArray)
                {
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    DicConsole.Write("\rTrimming sector {0}", badSector);

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector,
                                       BLOCK_SIZE, 0, 1, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (sense || dev.Error)
                    {
                        continue;
                    }

                    resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    outputPlugin.WriteSector(readBuffer, badSector);
                }

                DicConsole.WriteLine();
                end = DateTime.UtcNow;
                dumpLog.WriteLine("Trimmming finished in {0} seconds.", (end - start).TotalSeconds);
            }
            #endregion Trimming

            #region Error handling
            if (resume.BadBlocks.Count > 0 && !aborted && retryPasses > 0)
            {
                List <ulong> tmpList = new List <ulong>();

                foreach (ulong ur in resume.BadBlocks)
                {
                    for (ulong i = ur; i < ur + blocksToRead; i++)
                    {
                        tmpList.Add(i);
                    }
                }

                tmpList.Sort();

                int  pass              = 1;
                bool forward           = true;
                bool runningPersistent = false;

                resume.BadBlocks = tmpList;
                Modes.ModePage?currentModePage = null;
                byte[]         md6;
                byte[]         md10;

                if (persistent)
                {
                    Modes.ModePage_01_MMC pgMmc;

                    sense = dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                           dev.Timeout, out _);
                    if (sense)
                    {
                        sense = dev.ModeSense10(out readBuffer, out _, false, ScsiModeSensePageControl.Current,
                                                0x01, dev.Timeout, out _);

                        if (!sense)
                        {
                            Modes.DecodedMode?dcMode10 =
                                Modes.DecodeMode10(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);

                            if (dcMode10.HasValue)
                            {
                                foreach (Modes.ModePage modePage in dcMode10.Value.Pages)
                                {
                                    if (modePage.Page == 0x01 && modePage.Subpage == 0x00)
                                    {
                                        currentModePage = modePage;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        Modes.DecodedMode?dcMode6 =
                            Modes.DecodeMode6(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);

                        if (dcMode6.HasValue)
                        {
                            foreach (Modes.ModePage modePage in dcMode6.Value.Pages)
                            {
                                if (modePage.Page == 0x01 && modePage.Subpage == 0x00)
                                {
                                    currentModePage = modePage;
                                }
                            }
                        }
                    }

                    if (currentModePage == null)
                    {
                        pgMmc =
                            new Modes.ModePage_01_MMC {
                            PS = false, ReadRetryCount = 0x20, Parameter = 0x00
                        };
                        currentModePage = new Modes.ModePage
                        {
                            Page         = 0x01,
                            Subpage      = 0x00,
                            PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        };
                    }

                    pgMmc =
                        new Modes.ModePage_01_MMC {
                        PS = false, ReadRetryCount = 255, Parameter = 0x20
                    };
                    Modes.DecodedMode md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[]
                        {
                            new Modes.ModePage
                            {
                                Page         = 0x01,
                                Subpage      = 0x00,
                                PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                            }
                        }
                    };
                    md6  = Modes.EncodeMode6(md, dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, dev.ScsiType);

                    dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
                    sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
                    if (sense)
                    {
                        sense = dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
                    }

                    if (sense)
                    {
                        DicConsole
                        .WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                        DicConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
                        dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                    }
                    else
                    {
                        runningPersistent = true;
                    }
                }

repeatRetry:
                ulong[] tmpArray = resume.BadBlocks.ToArray();
                foreach (ulong badSector in tmpArray)
                {
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass,
                                     forward ? "forward" : "reverse",
                                     runningPersistent ? "recovering partial data, " : "");

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector,
                                       BLOCK_SIZE, 0, 1, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (!sense && !dev.Error)
                    {
                        resume.BadBlocks.Remove(badSector);
                        extents.Add(badSector);
                        outputPlugin.WriteSector(readBuffer, badSector);
                        dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
                    }
                    else if (runningPersistent)
                    {
                        outputPlugin.WriteSector(readBuffer, badSector);
                    }
                }

                if (pass < retryPasses && !aborted && resume.BadBlocks.Count > 0)
                {
                    pass++;
                    forward = !forward;
                    resume.BadBlocks.Sort();
                    resume.BadBlocks.Reverse();
                    goto repeatRetry;
                }

                if (runningPersistent && currentModePage.HasValue)
                {
                    Modes.DecodedMode md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[] { currentModePage.Value }
                    };
                    md6  = Modes.EncodeMode6(md, dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, dev.ScsiType);

                    dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
                    sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
                    if (sense)
                    {
                        dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
                    }
                }

                DicConsole.WriteLine();
            }
            #endregion Error handling

            resume.BadBlocks.Sort();
            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

            foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
            {
                ret = outputPlugin.WriteMediaTag(tag.Value, tag.Key);
                if (ret || force)
                {
                    continue;
                }

                // Cannot write tag to image
                dumpLog.WriteLine($"Cannot write tag {tag.Key}.");
                throw new ArgumentException(outputPlugin.ErrorMessage);
            }

            resume.BadBlocks.Sort();
            foreach (ulong bad in resume.BadBlocks)
            {
                dumpLog.WriteLine("Sector {0} could not be read.", bad);
            }
            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

            outputPlugin.SetDumpHardware(resume.Tries);
            if (preSidecar != null)
            {
                outputPlugin.SetCicmMetadata(preSidecar);
            }
            dumpLog.WriteLine("Closing output file.");
            DicConsole.WriteLine("Closing output file.");
            DateTime closeStart = DateTime.Now;
            outputPlugin.Close();
            DateTime closeEnd = DateTime.Now;
            dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);

            if (aborted)
            {
                dumpLog.WriteLine("Aborted!");
                return;
            }

            double totalChkDuration = 0;
            if (!nometadata)
            {
                dumpLog.WriteLine("Creating sidecar.");
                FiltersList filters     = new FiltersList();
                IFilter     filter      = filters.GetFilter(outputPath);
                IMediaImage inputPlugin = ImageFormat.Detect(filter);
                if (!inputPlugin.Open(filter))
                {
                    throw new ArgumentException("Could not open created image.");
                }

                DateTime         chkStart = DateTime.UtcNow;
                CICMMetadataType sidecar  = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding);
                end = DateTime.UtcNow;

                if (preSidecar != null)
                {
                    preSidecar.OpticalDisc = sidecar.OpticalDisc;
                    sidecar = preSidecar;
                }

                totalChkDuration = (end - chkStart).TotalMilliseconds;
                dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);
                dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
                                  (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));

                foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
                {
                    Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar);
                }

                List <(ulong start, string type)> filesystems = new List <(ulong start, string type)>();
                if (sidecar.OpticalDisc[0].Track != null)
                {
                    filesystems.AddRange(from xmlTrack in sidecar.OpticalDisc[0].Track
                                         where xmlTrack.FileSystemInformation != null
                                         from partition in xmlTrack.FileSystemInformation
                                         where partition.FileSystems != null
                                         from fileSystem in partition.FileSystems
                                         select((ulong)partition.StartSector, fileSystem.Type));
                }

                if (filesystems.Count > 0)
                {
                    foreach (var filesystem in filesystems.Select(o => new { o.start, o.type }).Distinct())
                    {
                        dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
                    }
                }

                sidecar.OpticalDisc[0].Layers = new LayersType
                {
                    type          = LayersTypeType.OTP,
                    typeSpecified = true,
                    Sectors       = new SectorsType[1]
                };
                sidecar.OpticalDisc[0].Layers.Sectors[0] = new SectorsType {
                    Value = (long)layerBreak
                };
                sidecar.OpticalDisc[0].Sessions   = 1;
                sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
                Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp);
                sidecar.OpticalDisc[0].DiscType    = xmlDskTyp;
                sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp;

                foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
                {
                    if (outputPlugin.SupportedMediaTags.Contains(tag.Key))
                    {
                        Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar);
                    }
                }

                DicConsole.WriteLine("Writing metadata sidecar");

                FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);

                XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
                xmlSer.Serialize(xmlFs, sidecar);
                xmlFs.Close();
            }

            DicConsole.WriteLine();
            DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming, {3:F3} writing, {4:F3} closing).",
                                 (end - start).TotalSeconds, totalDuration / 1000,
                                 totalChkDuration / 1000,
                                 imageWriteDuration, (closeEnd - closeStart).TotalSeconds);
            DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.",
                                 (double)BLOCK_SIZE * (double)(blocks + 1) / 1048576 / (totalDuration / 1000));
            DicConsole.WriteLine("Fastest speed burst: {0:F3} MiB/sec.", maxSpeed);
            DicConsole.WriteLine("Slowest speed burst: {0:F3} MiB/sec.", minSpeed);
            DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count);
            DicConsole.WriteLine();

            Statistics.AddMedia(dskType, true);
        }
Ejemplo n.º 2
0
        void RetryCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry,
                             ExtentsULong extents, int offsetBytes, bool readcd, int sectorsForOffset, uint subSize,
                             MmcSubchannel supportedSubchannel, ref double totalDuration)
        {
            bool sense = true;            // Sense indicator

            byte[]     cmdBuf = null;     // Data buffer
            double     cmdDuration;       // Command execution time
            const uint sectorSize = 2352; // Full sector size

            byte[] tmpBuf;                // Temporary buffer
            byte[] senseBuf = null;       // Sense buffer

            if (_resume.BadBlocks.Count <= 0 ||
                _aborted ||
                _retryPasses <= 0)
            {
                return;
            }

            int  pass              = 1;
            bool forward           = true;
            bool runningPersistent = false;

            Modes.ModePage?currentModePage = null;
            byte[]         md6;
            byte[]         md10;

            if (_persistent)
            {
                Modes.ModePage_01_MMC pgMmc;

                sense = _dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
                                        out _);

                if (sense)
                {
                    sense = _dev.ModeSense10(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                             _dev.Timeout, out _);

                    if (!sense)
                    {
                        Modes.DecodedMode?dcMode10 =
                            Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);

                        if (dcMode10?.Pages != null)
                        {
                            foreach (Modes.ModePage modePage in dcMode10.Value.Pages)
                            {
                                if (modePage.Page == 0x01 &&
                                    modePage.Subpage == 0x00)
                                {
                                    currentModePage = modePage;
                                }
                            }
                        }
                    }
                }
                else
                {
                    Modes.DecodedMode?dcMode6 = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);

                    if (dcMode6?.Pages != null)
                    {
                        foreach (Modes.ModePage modePage in dcMode6.Value.Pages)
                        {
                            if (modePage.Page == 0x01 &&
                                modePage.Subpage == 0x00)
                            {
                                currentModePage = modePage;
                            }
                        }
                    }
                }

                if (currentModePage == null)
                {
                    pgMmc = new Modes.ModePage_01_MMC
                    {
                        PS = false, ReadRetryCount = 32, Parameter = 0x00
                    };

                    currentModePage = new Modes.ModePage
                    {
                        Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                    };
                }

                pgMmc = new Modes.ModePage_01_MMC
                {
                    PS = false, ReadRetryCount = 255, Parameter = 0x20
                };

                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        new Modes.ModePage
                        {
                            Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        }
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
                _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }

                if (sense)
                {
                    UpdateStatus?.
                    Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");

                    DicConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));

                    _dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                }
                else
                {
                    runningPersistent = true;
                }
            }

            InitProgress?.Invoke();
cdRepeatRetry:
            ulong[]      tmpArray = _resume.BadBlocks.ToArray();
            List <ulong> sectorsNotEvenPartial = new List <ulong>();

            foreach (ulong badSector in tmpArray)
            {
                if (_aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    _dumpLog.WriteLine("Aborted!");

                    break;
                }

                PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
                                                    forward ? "forward" : "reverse",
                                                    runningPersistent ? "recovering partial data, " : ""));

                byte sectorsToReRead   = 1;
                uint badSectorToReRead = (uint)badSector;

                if (_fixOffset &&
                    audioExtents.Contains(badSector) &&
                    offsetBytes != 0)
                {
                    if (offsetBytes > 0)
                    {
                        badSectorToReRead -= (uint)sectorsForOffset;
                    }

                    sectorsToReRead = (byte)(sectorsForOffset + 1);
                }

                if (readcd)
                {
                    sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToReRead, blockSize, sectorsToReRead,
                                        MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
                                        true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);

                    totalDuration += cmdDuration;
                }

                if (sense || _dev.Error)
                {
                    if (!runningPersistent)
                    {
                        continue;
                    }

                    FixedSense?decSense = Sense.DecodeFixed(senseBuf);

                    // MEDIUM ERROR, retry with ignore error below
                    if (decSense.HasValue &&
                        decSense.Value.ASC == 0x11)
                    {
                        if (!sectorsNotEvenPartial.Contains(badSector))
                        {
                            sectorsNotEvenPartial.Add(badSector);
                        }
                    }
                }

                // Because one block has been partially used to fix the offset
                if (_fixOffset &&
                    audioExtents.Contains(badSector) &&
                    offsetBytes != 0)
                {
                    int offsetFix = offsetBytes > 0 ? (int)(sectorSize - (offsetBytes * -1)) : offsetBytes;

                    if (supportedSubchannel != MmcSubchannel.None)
                    {
                        // De-interleave subchannel
                        byte[] data = new byte[sectorSize * sectorsToReRead];
                        byte[] sub  = new byte[subSize * sectorsToReRead];

                        for (int b = 0; b < sectorsToReRead; b++)
                        {
                            Array.Copy(cmdBuf, (int)(0 + (b * blockSize)), data, sectorSize * b, sectorSize);

                            Array.Copy(cmdBuf, (int)(sectorSize + (b * blockSize)), sub, subSize * b, subSize);
                        }

                        tmpBuf = new byte[sectorSize * (sectorsToReRead - sectorsForOffset)];
                        Array.Copy(data, offsetFix, tmpBuf, 0, tmpBuf.Length);
                        data = tmpBuf;

                        // Re-interleave subchannel
                        cmdBuf = new byte[blockSize * sectorsToReRead];

                        for (int b = 0; b < sectorsToReRead; b++)
                        {
                            Array.Copy(data, sectorSize * b, cmdBuf, (int)(0 + (b * blockSize)), sectorSize);

                            Array.Copy(sub, subSize * b, cmdBuf, (int)(sectorSize + (b * blockSize)), subSize);
                        }
                    }
                    else
                    {
                        tmpBuf = new byte[blockSize * (sectorsToReRead - sectorsForOffset)];
                        Array.Copy(cmdBuf, offsetFix, tmpBuf, 0, tmpBuf.Length);
                        cmdBuf = tmpBuf;
                    }
                }

                if (!sense &&
                    !_dev.Error)
                {
                    _resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    UpdateStatus?.Invoke($"Correctly retried sector {badSector} in pass {pass}.");
                    _dumpLog.WriteLine("Correctly retried sector {0} in pass {1}.", badSector, pass);
                    sectorsNotEvenPartial.Remove(badSector);
                }

                if (supportedSubchannel != MmcSubchannel.None)
                {
                    byte[] data = new byte[sectorSize];
                    byte[] sub  = new byte[subSize];
                    Array.Copy(cmdBuf, 0, data, 0, sectorSize);
                    Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
                    _outputPlugin.WriteSectorLong(data, badSector);
                    _outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel);
                }
                else
                {
                    _outputPlugin.WriteSectorLong(cmdBuf, badSector);
                }
            }

            if (pass < _retryPasses &&
                !_aborted &&
                _resume.BadBlocks.Count > 0)
            {
                pass++;
                forward = !forward;
                _resume.BadBlocks.Sort();
                _resume.BadBlocks.Reverse();

                goto cdRepeatRetry;
            }

            EndProgress?.Invoke();

            // TODO: Enable when underlying images support lead-outs

            /*
             *  RetryCdLeadOuts(blocks, blockSize, ref currentSpeed, currentTry, extents, ibgLog, ref imageWriteDuration,
             *         leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, read6, read10, read12, read16, readcd,
             *         supportedSubchannel, subSize, ref totalDuration);
             */

            // Try to ignore read errors, on some drives this allows to recover partial even if damaged data
            if (_persistent && sectorsNotEvenPartial.Count > 0)
            {
                var pgMmc = new Modes.ModePage_01_MMC
                {
                    PS = false, ReadRetryCount = 255, Parameter = 0x01
                };

                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        new Modes.ModePage
                        {
                            Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        }
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                _dumpLog.WriteLine("Sending MODE SELECT to drive (ignore error correction).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }

                if (!sense)
                {
                    runningPersistent = true;

                    InitProgress?.Invoke();

                    foreach (ulong badSector in sectorsNotEvenPartial)
                    {
                        if (_aborted)
                        {
                            currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                            _dumpLog.WriteLine("Aborted!");

                            break;
                        }

                        PulseProgress?.Invoke($"Trying to get partial data for sector {badSector}");

                        if (readcd)
                        {
                            sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)badSector, blockSize, 1,
                                                MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
                                                true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
                                                out cmdDuration);

                            totalDuration += cmdDuration;
                        }

                        if (sense || _dev.Error)
                        {
                            continue;
                        }

                        _dumpLog.WriteLine("Got partial data for sector {0} in pass {1}.", badSector, pass);

                        if (supportedSubchannel != MmcSubchannel.None)
                        {
                            byte[] data = new byte[sectorSize];
                            byte[] sub  = new byte[subSize];
                            Array.Copy(cmdBuf, 0, data, 0, sectorSize);
                            Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
                            _outputPlugin.WriteSectorLong(data, badSector);
                            _outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel);
                        }
                        else
                        {
                            _outputPlugin.WriteSectorLong(cmdBuf, badSector);
                        }
                    }

                    EndProgress?.Invoke();
                }
            }

            if (runningPersistent && currentModePage.HasValue)
            {
                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        currentModePage.Value
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }
            }

            EndProgress?.Invoke();
        }
Ejemplo n.º 3
0
        void RetryCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry,
                             ExtentsULong extents, int offsetBytes, bool readcd, int sectorsForOffset, uint subSize,
                             MmcSubchannel supportedSubchannel, ref double totalDuration, SubchannelLog subLog,
                             MmcSubchannel desiredSubchannel, Track[] tracks, Dictionary <byte, string> isrcs,
                             ref string mcn, HashSet <int> subchannelExtents)
        {
            bool sense = true;                   // Sense indicator

            byte[]     cmdBuf = null;            // Data buffer
            double     cmdDuration;              // Command execution time
            const uint sectorSize = 2352;        // Full sector size

            byte[]            senseBuf = null;   // Sense buffer
            PlextorSubchannel supportedPlextorSubchannel;

            switch (supportedSubchannel)
            {
            case MmcSubchannel.None:
                supportedPlextorSubchannel = PlextorSubchannel.None;

                break;

            case MmcSubchannel.Raw:
                supportedPlextorSubchannel = PlextorSubchannel.All;

                break;

            case MmcSubchannel.Q16:
                supportedPlextorSubchannel = PlextorSubchannel.Q16;

                break;

            case MmcSubchannel.Rw:
                supportedPlextorSubchannel = PlextorSubchannel.Pack;

                break;

            default:
                supportedPlextorSubchannel = PlextorSubchannel.None;

                break;
            }

            if (_resume.BadBlocks.Count <= 0 ||
                _aborted ||
                _retryPasses <= 0)
            {
                return;
            }

            int  pass              = 1;
            bool forward           = true;
            bool runningPersistent = false;

            Modes.ModePage?currentModePage = null;
            byte[]         md6;
            byte[]         md10;

            if (_persistent)
            {
                Modes.ModePage_01_MMC pgMmc;

                sense = _dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
                                        out _);

                if (sense)
                {
                    sense = _dev.ModeSense10(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                             _dev.Timeout, out _);

                    if (!sense)
                    {
                        Modes.DecodedMode?dcMode10 =
                            Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);

                        if (dcMode10?.Pages != null)
                        {
                            foreach (Modes.ModePage modePage in dcMode10.Value.Pages)
                            {
                                if (modePage.Page == 0x01 &&
                                    modePage.Subpage == 0x00)
                                {
                                    currentModePage = modePage;
                                }
                            }
                        }
                    }
                }
                else
                {
                    Modes.DecodedMode?dcMode6 = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);

                    if (dcMode6?.Pages != null)
                    {
                        foreach (Modes.ModePage modePage in dcMode6.Value.Pages)
                        {
                            if (modePage.Page == 0x01 &&
                                modePage.Subpage == 0x00)
                            {
                                currentModePage = modePage;
                            }
                        }
                    }
                }

                if (currentModePage == null)
                {
                    pgMmc = new Modes.ModePage_01_MMC
                    {
                        PS = false, ReadRetryCount = 32, Parameter = 0x00
                    };

                    currentModePage = new Modes.ModePage
                    {
                        Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                    };
                }

                pgMmc = new Modes.ModePage_01_MMC
                {
                    PS = false, ReadRetryCount = 255, Parameter = 0x20
                };

                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        new Modes.ModePage
                        {
                            Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        }
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
                _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }

                if (sense)
                {
                    UpdateStatus?.
                    Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");

                    AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));

                    _dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                }
                else
                {
                    runningPersistent = true;
                }
            }

            InitProgress?.Invoke();
cdRepeatRetry:
            ulong[]      tmpArray = _resume.BadBlocks.ToArray();
            List <ulong> sectorsNotEvenPartial = new List <ulong>();

            for (int i = 0; i < tmpArray.Length; i++)
            {
                ulong badSector = tmpArray[i];

                if (_aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    _dumpLog.WriteLine("Aborted!");

                    break;
                }

                PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
                                                    forward ? "forward" : "reverse",
                                                    runningPersistent ? "recovering partial data, " : ""));

                Track track = tracks.OrderBy(t => t.TrackStartSector).
                              LastOrDefault(t => badSector >= t.TrackStartSector);

                byte sectorsToReRead   = 1;
                uint badSectorToReRead = (uint)badSector;

                if (_fixOffset &&
                    audioExtents.Contains(badSector) &&
                    offsetBytes != 0)
                {
                    if (offsetBytes > 0)
                    {
                        badSectorToReRead -= (uint)sectorsForOffset;
                    }

                    sectorsToReRead = (byte)(sectorsForOffset + 1);
                }

                if (_supportsPlextorD8 && audioExtents.Contains(badSector))
                {
                    sense = ReadPlextorWithSubchannel(out cmdBuf, out senseBuf, badSectorToReRead, blockSize,
                                                      sectorsToReRead, supportedPlextorSubchannel, out cmdDuration);

                    totalDuration += cmdDuration;
                }
                else if (readcd)
                {
                    sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToReRead, blockSize, sectorsToReRead,
                                        MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
                                        true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);

                    totalDuration += cmdDuration;
                }

                if (sense || _dev.Error)
                {
                    if (!runningPersistent)
                    {
                        continue;
                    }

                    FixedSense?decSense = Sense.DecodeFixed(senseBuf);

                    // MEDIUM ERROR, retry with ignore error below
                    if (decSense.HasValue &&
                        decSense.Value.ASC == 0x11)
                    {
                        if (!sectorsNotEvenPartial.Contains(badSector))
                        {
                            sectorsNotEvenPartial.Add(badSector);
                        }
                    }
                }

                // Because one block has been partially used to fix the offset
                if (_fixOffset &&
                    audioExtents.Contains(badSector) &&
                    offsetBytes != 0)
                {
                    uint blocksToRead = sectorsToReRead;

                    FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead,
                                  subSize, ref cmdBuf, blockSize, false);
                }

                if (!sense &&
                    !_dev.Error)
                {
                    _resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    UpdateStatus?.Invoke($"Correctly retried sector {badSector} in pass {pass}.");
                    _dumpLog.WriteLine("Correctly retried sector {0} in pass {1}.", badSector, pass);
                    sectorsNotEvenPartial.Remove(badSector);
                }

                if (supportedSubchannel != MmcSubchannel.None)
                {
                    byte[] data = new byte[sectorSize];
                    byte[] sub  = new byte[subSize];
                    Array.Copy(cmdBuf, 0, data, 0, sectorSize);
                    Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
                    _outputPlugin.WriteSectorLong(data, badSector);

                    bool indexesChanged = WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub, badSector,
                                                                 1, subLog, isrcs, (byte)track.TrackSequence, ref mcn,
                                                                 tracks, subchannelExtents);

                    // Set tracks and go back
                    if (indexesChanged)
                    {
                        (_outputPlugin as IWritableOpticalImage).SetTracks(tracks.ToList());
                        i--;
                    }
                }
                else
                {
                    _outputPlugin.WriteSectorLong(cmdBuf, badSector);
                }
            }

            if (pass < _retryPasses &&
                !_aborted &&
                _resume.BadBlocks.Count > 0)
            {
                pass++;
                forward = !forward;
                _resume.BadBlocks.Sort();

                if (!forward)
                {
                    _resume.BadBlocks.Reverse();
                }

                goto cdRepeatRetry;
            }

            EndProgress?.Invoke();

            // TODO: Enable when underlying images support lead-outs

            /*
             *  RetryCdLeadOuts(blocks, blockSize, ref currentSpeed, currentTry, extents, ibgLog, ref imageWriteDuration,
             *         leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, read6, read10, read12, read16, readcd,
             *         supportedSubchannel, subSize, ref totalDuration);
             */

            // Try to ignore read errors, on some drives this allows to recover partial even if damaged data
            if (_persistent && sectorsNotEvenPartial.Count > 0)
            {
                var pgMmc = new Modes.ModePage_01_MMC
                {
                    PS = false, ReadRetryCount = 255, Parameter = 0x01
                };

                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        new Modes.ModePage
                        {
                            Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        }
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                _dumpLog.WriteLine("Sending MODE SELECT to drive (ignore error correction).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }

                if (!sense)
                {
                    runningPersistent = true;

                    InitProgress?.Invoke();

                    for (int i = 0; i < sectorsNotEvenPartial.Count; i++)
                    {
                        ulong badSector = sectorsNotEvenPartial[i];

                        if (_aborted)
                        {
                            currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                            _dumpLog.WriteLine("Aborted!");

                            break;
                        }

                        PulseProgress?.Invoke($"Trying to get partial data for sector {badSector}");

                        Track track = tracks.OrderBy(t => t.TrackStartSector).
                                      LastOrDefault(t => badSector >= t.TrackStartSector);

                        if (readcd)
                        {
                            sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)badSector, blockSize, 1,
                                                MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
                                                true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
                                                out cmdDuration);

                            totalDuration += cmdDuration;
                        }

                        if (sense || _dev.Error)
                        {
                            continue;
                        }

                        _dumpLog.WriteLine("Got partial data for sector {0} in pass {1}.", badSector, pass);

                        if (supportedSubchannel != MmcSubchannel.None)
                        {
                            byte[] data = new byte[sectorSize];
                            byte[] sub  = new byte[subSize];
                            Array.Copy(cmdBuf, 0, data, 0, sectorSize);
                            Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
                            _outputPlugin.WriteSectorLong(data, badSector);

                            bool indexesChanged = WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, sub,
                                                                         badSector, 1, subLog, isrcs,
                                                                         (byte)track.TrackSequence, ref mcn, tracks,
                                                                         subchannelExtents);

                            // Set tracks and go back
                            if (indexesChanged)
                            {
                                (_outputPlugin as IWritableOpticalImage).SetTracks(tracks.ToList());
                                i--;
                            }
                        }
                        else
                        {
                            _outputPlugin.WriteSectorLong(cmdBuf, badSector);
                        }
                    }

                    EndProgress?.Invoke();
                }
            }

            if (runningPersistent && currentModePage.HasValue)
            {
                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(), Pages = new[]
                    {
                        currentModePage.Value
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
                sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }
            }

            EndProgress?.Invoke();
        }
Ejemplo n.º 4
0
        /// <summary>Retries errored data when dumping from a SCSI Block Commands compliant device</summary>
        /// <param name="currentTry">Resume information</param>
        /// <param name="extents">Correctly dump extents</param>
        /// <param name="totalDuration">Total time spent in commands</param>
        /// <param name="scsiReader">SCSI reader</param>
        /// <param name="blankExtents">Blank extents</param>
        void RetrySbcData(Reader scsiReader, DumpHardwareType currentTry, ExtentsULong extents,
                          ref double totalDuration, ExtentsULong blankExtents)
        {
            int  pass              = 1;
            bool forward           = true;
            bool runningPersistent = false;
            bool sense;

            byte[] buffer;
            bool   recoveredError;

            Modes.ModePage?currentModePage = null;
            byte[]         md6;
            byte[]         md10;
            bool           blankCheck;
            bool           newBlank = false;

            if (_persistent)
            {
                Modes.ModePage_01_MMC pgMmc;
                Modes.ModePage_01     pg;

                sense = _dev.ModeSense6(out buffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
                                        out _);

                if (sense)
                {
                    sense = _dev.ModeSense10(out buffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                             _dev.Timeout, out _);

                    if (!sense)
                    {
                        Modes.DecodedMode?dcMode10 = Modes.DecodeMode10(buffer, _dev.ScsiType);

                        if (dcMode10?.Pages != null)
                        {
                            foreach (Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage =>
                                                                                           modePage.Page == 0x01 && modePage.Subpage == 0x00))
                            {
                                currentModePage = modePage;
                            }
                        }
                    }
                }
                else
                {
                    Modes.DecodedMode?dcMode6 = Modes.DecodeMode6(buffer, _dev.ScsiType);

                    if (dcMode6?.Pages != null)
                    {
                        foreach (Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage =>
                                                                                      modePage.Page == 0x01 && modePage.Subpage == 0x00))
                        {
                            currentModePage = modePage;
                        }
                    }
                }

                if (currentModePage == null)
                {
                    if (_dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice)
                    {
                        pgMmc = new Modes.ModePage_01_MMC
                        {
                            PS             = false,
                            ReadRetryCount = 32,
                            Parameter      = 0x00
                        };

                        currentModePage = new Modes.ModePage
                        {
                            Page         = 0x01,
                            Subpage      = 0x00,
                            PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        };
                    }
                    else
                    {
                        pg = new Modes.ModePage_01
                        {
                            PS             = false,
                            AWRE           = true,
                            ARRE           = true,
                            TB             = false,
                            RC             = false,
                            EER            = true,
                            PER            = false,
                            DTE            = true,
                            DCR            = false,
                            ReadRetryCount = 32
                        };

                        currentModePage = new Modes.ModePage
                        {
                            Page         = 0x01,
                            Subpage      = 0x00,
                            PageResponse = Modes.EncodeModePage_01(pg)
                        };
                    }
                }

                if (_dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice)
                {
                    pgMmc = new Modes.ModePage_01_MMC
                    {
                        PS             = false,
                        ReadRetryCount = 255,
                        Parameter      = 0x20
                    };

                    var md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[]
                        {
                            new Modes.ModePage
                            {
                                Page         = 0x01,
                                Subpage      = 0x00,
                                PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                            }
                        }
                    };

                    md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, _dev.ScsiType);
                }
                else
                {
                    pg = new Modes.ModePage_01
                    {
                        PS             = false,
                        AWRE           = false,
                        ARRE           = false,
                        TB             = true,
                        RC             = false,
                        EER            = true,
                        PER            = false,
                        DTE            = false,
                        DCR            = false,
                        ReadRetryCount = 255
                    };

                    var md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[]
                        {
                            new Modes.ModePage
                            {
                                Page         = 0x01,
                                Subpage      = 0x00,
                                PageResponse = Modes.EncodeModePage_01(pg)
                            }
                        }
                    };

                    md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, _dev.ScsiType);
                }

                UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
                _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
                sense = _dev.ModeSelect(md6, out byte[] senseBuf, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
                }

                if (sense)
                {
                    UpdateStatus?.
                    Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");

                    AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));

                    _dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                }
                else
                {
                    runningPersistent = true;
                }
            }

            InitProgress?.Invoke();
repeatRetry:
            ulong[] tmpArray = _resume.BadBlocks.ToArray();

            foreach (ulong badSector in tmpArray)
            {
                if (_aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    UpdateStatus?.Invoke("Aborted!");
                    _dumpLog.WriteLine("Aborted!");

                    break;
                }

                PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
                                                    forward ? "forward" : "reverse",
                                                    runningPersistent ? "recovering partial data, " : ""));

                sense = scsiReader.ReadBlock(out buffer, badSector, out double cmdDuration, out recoveredError,
                                             out blankCheck);

                totalDuration += cmdDuration;

                if (blankCheck)
                {
                    _resume.BadBlocks.Remove(badSector);
                    blankExtents.Add(badSector, badSector);
                    newBlank = true;

                    UpdateStatus?.Invoke($"Found blank block {badSector} in pass {pass}.");
                    _dumpLog.WriteLine("Found blank block {0} in pass {1}.", badSector, pass);

                    continue;
                }

                if ((!sense && !_dev.Error) || recoveredError)
                {
                    _resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    _outputPlugin.WriteSector(buffer, badSector);
                    UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}.");
                    _dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
                }
                else if (runningPersistent)
                {
                    _outputPlugin.WriteSector(buffer, badSector);
                }
            }

            if (pass < _retryPasses &&
                !_aborted &&
                _resume.BadBlocks.Count > 0)
            {
                pass++;
                forward = !forward;
                _resume.BadBlocks.Sort();

                if (!forward)
                {
                    _resume.BadBlocks.Reverse();
                }

                goto repeatRetry;
            }

            if (runningPersistent && currentModePage.HasValue)
            {
                var md = new Modes.DecodedMode
                {
                    Header = new Modes.ModeHeader(),
                    Pages  = new[]
                    {
                        currentModePage.Value
                    }
                };

                md6  = Modes.EncodeMode6(md, _dev.ScsiType);
                md10 = Modes.EncodeMode10(md, _dev.ScsiType);

                UpdateStatus?.Invoke("Sending MODE SELECT to drive (return device to previous status).");
                _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
                sense = _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _);

                if (sense)
                {
                    _dev.ModeSelect10(md10, out _, true, false, _dev.Timeout, out _);
                }
            }

            if (newBlank)
            {
                _resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents);
            }

            EndProgress?.Invoke();
        }