/// <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); }
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(); }
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(); }
/// <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(); }