/// <summary> /// Starts dumping with the stablished fields and autodetecting the device type /// </summary> public void Start() { if (dev.IsUsb && dev.UsbVendorId == 0x054C && (dev.UsbProductId == 0x01C8 || dev.UsbProductId == 0x01C9 || dev.UsbProductId == 0x02D2)) { PlayStationPortable(); } else { switch (dev.Type) { case DeviceType.ATA: Ata(); break; case DeviceType.MMC: case DeviceType.SecureDigital: SecureDigital(); break; case DeviceType.NVMe: NVMe(); break; case DeviceType.ATAPI: case DeviceType.SCSI: Scsi(); break; default: dumpLog.WriteLine("Unknown device type."); dumpLog.Close(); StoppingErrorMessage?.Invoke("Unknown device type."); return; } } dumpLog.Close(); if (resume == null || !doResume) { return; } resume.LastWriteDate = DateTime.UtcNow; resume.BadBlocks.Sort(); if (File.Exists(outputPrefix + ".resume.xml")) { File.Delete(outputPrefix + ".resume.xml"); } FileStream fs = new FileStream(outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite); XmlSerializer xs = new XmlSerializer(resume.GetType()); xs.Serialize(fs, resume); fs.Close(); }
public static bool SupportsPqSubchannel(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus) { dumpLog?.WriteLine("Checking if drive supports PQ subchannel reading..."); updateStatus?.Invoke("Checking if drive supports PQ subchannel reading..."); return(!dev.ReadCd(out _, out _, 0, 2352 + 16, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout, out _)); }
/// <summary> /// Dumps an ATA device /// </summary> /// <param name="dev">Device</param> /// <param name="devicePath">Path to the device</param> /// <param name="outputPrefix">Prefix for output log 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 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="outputPath">Path to output file</param> /// <param name="formatOptions">Formats to pass to output file plugin</param> /// <exception cref="InvalidOperationException">If the resume file is invalid</exception> public static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, bool force, bool dumpRaw, bool persistent, bool stopOnError, 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 aborted; if (dumpRaw) { DicConsole.ErrorWriteLine("Raw dumping not yet supported in ATA devices."); if (force) { DicConsole.ErrorWriteLine("Continuing..."); } else { DicConsole.ErrorWriteLine("Aborting..."); return; } } bool sense; const ushort ATA_PROFILE = 0x0001; const uint TIMEOUT = 5; double imageWriteDuration = 0; dumpLog.WriteLine("Requesting ATA IDENTIFY DEVICE."); sense = dev.AtaIdentify(out byte[] cmdBuf, out _); if (!sense && Identify.Decode(cmdBuf).HasValue) { Identify.IdentifyDevice?ataIdNullable = Identify.Decode(cmdBuf); if (ataIdNullable != null) { Identify.IdentifyDevice ataId = ataIdNullable.Value; byte[] ataIdentify = cmdBuf; cmdBuf = new byte[0]; DateTime start; DateTime end; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; // Initializate reader dumpLog.WriteLine("Initializing reader."); Reader ataReader = new Reader(dev, TIMEOUT, ataIdentify); // Fill reader blocks ulong blocks = ataReader.GetDeviceBlocks(); // Check block sizes if (ataReader.GetBlockSize()) { dumpLog.WriteLine("ERROR: Cannot get block size: {0}.", ataReader.ErrorMessage); DicConsole.ErrorWriteLine(ataReader.ErrorMessage); return; } uint blockSize = ataReader.LogicalBlockSize; uint physicalsectorsize = ataReader.PhysicalBlockSize; if (ataReader.FindReadCommand()) { dumpLog.WriteLine("ERROR: Cannot find correct read command: {0}.", ataReader.ErrorMessage); DicConsole.ErrorWriteLine(ataReader.ErrorMessage); return; } // Check how many blocks to read, if error show and return if (ataReader.GetBlocksToRead()) { dumpLog.WriteLine("ERROR: Cannot get blocks to read: {0}.", ataReader.ErrorMessage); DicConsole.ErrorWriteLine(ataReader.ErrorMessage); return; } uint blocksToRead = ataReader.BlocksToRead; ushort cylinders = ataReader.Cylinders; byte heads = ataReader.Heads; byte sectors = ataReader.Sectors; dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize); dumpLog.WriteLine("Device reports {0} cylinders {1} heads {2} sectors per track.", cylinders, heads, sectors); dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead); dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize); dumpLog.WriteLine("Device reports {0} bytes per physical block.", physicalsectorsize); bool removable = !dev.IsCompactFlash && ataId.GeneralConfiguration.HasFlag(Identify.GeneralConfigurationBit.Removable); DumpHardwareType currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(ataReader.IsLba, removable, blocks, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId, ref resume, ref currentTry, ref extents); if (currentTry == null || extents == null) { throw new InvalidOperationException("Could not process resume file, not continuing..."); } MhddLog mhddLog; IbgLog ibgLog; double duration; bool ret = true; if (dev.IsUsb && dev.UsbDescriptors != null && !outputPlugin.SupportedMediaTags.Contains(MediaTagType.USB_Descriptors)) { ret = false; dumpLog.WriteLine("Output format does not support USB descriptors."); DicConsole.ErrorWriteLine("Output format does not support USB descriptors."); } if (dev.IsPcmcia && dev.Cis != null && !outputPlugin.SupportedMediaTags.Contains(MediaTagType.PCMCIA_CIS)) { ret = false; dumpLog.WriteLine("Output format does not support PCMCIA CIS descriptors."); DicConsole.ErrorWriteLine("Output format does not support PCMCIA CIS descriptors."); } if (!outputPlugin.SupportedMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) { ret = false; dumpLog.WriteLine("Output format does not support ATA IDENTIFY."); DicConsole.ErrorWriteLine("Output format does not support ATA IDENTIFY."); } 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; } } ret = outputPlugin.Create(outputPath, dev.IsCompactFlash ? MediaType.CompactFlash : MediaType.GENERIC_HDD, formatOptions, blocks, blockSize); // 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; } // Setting geometry outputPlugin.SetGeometry(cylinders, heads, sectors); if (ataReader.IsLba) { DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); if (skip < blocksToRead) { skip = blocksToRead; } mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead); ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); if (resume.NextBlock > 0) { dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); } bool newTrim = false; start = DateTime.UtcNow; for (ulong i = resume.NextBlock; i < blocks; i += blocksToRead) { if (aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); dumpLog.WriteLine("Aborted!"); break; } if (blocks - i < blocksToRead) { blocksToRead = (byte)(blocks - 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, blocks, currentSpeed); bool error = ataReader.ReadBlocks(out cmdBuf, i, blocksToRead, out duration); if (!error) { mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); DateTime writeStart = DateTime.Now; outputPlugin.WriteSectors(cmdBuf, i, blocksToRead); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; extents.Add(i, blocksToRead, true); } else { if (i + skip > blocks) { skip = (uint)(blocks - i); } for (ulong b = i; b < i + skip; b++) { resume.BadBlocks.Add(b); } mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); DateTime writeStart = DateTime.Now; outputPlugin.WriteSectors(new byte[blockSize * skip], i, skip); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i); i += skip - blocksToRead; newTrim = true; } double newSpeed = (double)blockSize * blocksToRead / 1048576 / (duration / 1000); if (!double.IsInfinity(newSpeed)) { currentSpeed = newSpeed; } resume.NextBlock = i + blocksToRead; } end = DateTime.Now; DicConsole.WriteLine(); mhddLog.Close(); ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (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)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000)); dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.", (double)blockSize * (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); bool error = ataReader.ReadBlock(out cmdBuf, badSector, out duration); totalDuration += duration; if (error) { continue; } resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputPlugin.WriteSector(cmdBuf, 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) { int pass = 1; bool forward = true; repeatRetryLba: 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", persistent ? "recovering partial data, " : ""); bool error = ataReader.ReadBlock(out cmdBuf, badSector, out duration); totalDuration += duration; if (!error) { resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputPlugin.WriteSector(cmdBuf, badSector); dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (persistent) { outputPlugin.WriteSector(cmdBuf, badSector); } } if (pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) { pass++; forward = !forward; resume.BadBlocks.Sort(); resume.BadBlocks.Reverse(); goto repeatRetryLba; } DicConsole.WriteLine(); } #endregion Error handling LBA currentTry.Extents = ExtentsConverter.ToMetadata(extents); } else { mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead); ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); ulong currentBlock = 0; blocks = (ulong)(cylinders * heads * sectors); start = DateTime.UtcNow; for (ushort cy = 0; cy < cylinders; cy++) { for (byte hd = 0; hd < heads; hd++) { for (byte sc = 1; sc < sectors; sc++) { if (aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); dumpLog.WriteLine("Aborted!"); break; } #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 cylinder {0} head {1} sector {2} ({3:F3} MiB/sec.)", cy, hd, sc, currentSpeed); bool error = ataReader.ReadChs(out cmdBuf, cy, hd, sc, out duration); totalDuration += duration; if (!error) { mhddLog.Write(currentBlock, duration); ibgLog.Write(currentBlock, currentSpeed * 1024); DateTime writeStart = DateTime.Now; outputPlugin.WriteSector(cmdBuf, (ulong)((cy * heads + hd) * sectors + (sc - 1))); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; extents.Add(currentBlock); dumpLog.WriteLine("Error reading cylinder {0} head {1} sector {2}.", cy, hd, sc); } else { resume.BadBlocks.Add(currentBlock); mhddLog.Write(currentBlock, duration < 500 ? 65535 : duration); ibgLog.Write(currentBlock, 0); DateTime writeStart = DateTime.Now; outputPlugin.WriteSector(new byte[blockSize], (ulong)((cy * heads + hd) * sectors + (sc - 1))); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; } double newSpeed = blockSize / (double)1048576 / (duration / 1000); if (!double.IsInfinity(newSpeed)) { currentSpeed = newSpeed; } currentBlock++; } } } end = DateTime.Now; DicConsole.WriteLine(); mhddLog.Close(); ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (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)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000)); dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.", (double)blockSize * (double)(blocks + 1) / 1024 / (imageWriteDuration / 1000)); } foreach (ulong bad in resume.BadBlocks) { dumpLog.WriteLine("Sector {0} could not be read.", bad); } 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); if (preSidecar != null) { preSidecar.BlockMedia = sidecar.BlockMedia; sidecar = preSidecar; } if (dev.IsUsb) { dumpLog.WriteLine("Reading USB descriptors."); ret = outputPlugin.WriteMediaTag(dev.UsbDescriptors, MediaTagType.USB_Descriptors); if (ret) { sidecar.BlockMedia[0].USB = new USBType { ProductID = dev.UsbProductId, VendorID = dev.UsbVendorId, Descriptors = new DumpType { Image = outputPath, Size = dev.UsbDescriptors.Length, Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray() } } } ; } if (dev.IsPcmcia) { dumpLog.WriteLine("Reading PCMCIA CIS."); ret = outputPlugin.WriteMediaTag(dev.Cis, MediaTagType.PCMCIA_CIS); if (ret) { sidecar.BlockMedia[0].PCMCIA = new PCMCIAType { CIS = new DumpType { Image = outputPath, Size = dev.Cis.Length, Checksums = Checksum.GetChecksums(dev.Cis).ToArray() } } } ; dumpLog.WriteLine("Decoding PCMCIA CIS."); Tuple[] tuples = CIS.GetTuples(dev.Cis); if (tuples != null) { foreach (Tuple tuple in tuples) { switch (tuple.Code) { case TupleCodes.CISTPL_MANFID: ManufacturerIdentificationTuple manfid = CIS.DecodeManufacturerIdentificationTuple(tuple); if (manfid != null) { sidecar.BlockMedia[0].PCMCIA.ManufacturerCode = manfid.ManufacturerID; sidecar.BlockMedia[0].PCMCIA.CardCode = manfid.CardID; sidecar.BlockMedia[0].PCMCIA.ManufacturerCodeSpecified = true; sidecar.BlockMedia[0].PCMCIA.CardCodeSpecified = true; } break; case TupleCodes.CISTPL_VERS_1: Level1VersionTuple vers = CIS.DecodeLevel1VersionTuple(tuple); if (vers != null) { sidecar.BlockMedia[0].PCMCIA.Manufacturer = vers.Manufacturer; sidecar.BlockMedia[0].PCMCIA.ProductName = vers.Product; sidecar.BlockMedia[0].PCMCIA.Compliance = $"{vers.MajorVersion}.{vers.MinorVersion}"; sidecar.BlockMedia[0].PCMCIA.AdditionalInformation = vers.AdditionalInformation; } break; } } } } ret = outputPlugin.WriteMediaTag(ataIdentify, MediaTagType.ATA_IDENTIFY); if (ret) { sidecar.BlockMedia[0].ATA = new ATAType { Identify = new DumpType { Image = outputPath, Size = cmdBuf.Length, Checksums = Checksum.GetChecksums(cmdBuf).ToArray() } } } ; DateTime chkEnd = DateTime.UtcNow; totalChkDuration = (chkEnd - chkStart).TotalMilliseconds; dumpLog.WriteLine("Sidecar created in {0} seconds.", (chkEnd - chkStart).TotalSeconds); dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); List <(ulong start, string type)> filesystems = new List <(ulong start, string type)>(); if (sidecar.BlockMedia[0].FileSystemInformation != null) { filesystems.AddRange(from partition in sidecar.BlockMedia[0].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); } } DicConsole.WriteLine(); string xmlDskTyp, xmlDskSubTyp; if (dev.IsCompactFlash) { Metadata.MediaType.MediaTypeToString(MediaType.CompactFlash, out xmlDskTyp, out xmlDskSubTyp); } else if (dev.IsPcmcia) { Metadata.MediaType.MediaTypeToString(MediaType.PCCardTypeI, out xmlDskTyp, out xmlDskSubTyp); } else { Metadata.MediaType.MediaTypeToString(MediaType.GENERIC_HDD, out xmlDskTyp, out xmlDskSubTyp); } sidecar.BlockMedia[0].DiskType = xmlDskTyp; sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; sidecar.BlockMedia[0].Interface = "ATA"; sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; sidecar.BlockMedia[0].PhysicalBlockSize = (int)physicalsectorsize; sidecar.BlockMedia[0].LogicalBlockSize = (int)blockSize; sidecar.BlockMedia[0].Manufacturer = dev.Manufacturer; sidecar.BlockMedia[0].Model = dev.Model; sidecar.BlockMedia[0].Serial = dev.Serial; sidecar.BlockMedia[0].Size = (long)(blocks * blockSize); if (cylinders > 0 && heads > 0 && sectors > 0) { sidecar.BlockMedia[0].Cylinders = cylinders; sidecar.BlockMedia[0].CylindersSpecified = true; sidecar.BlockMedia[0].Heads = heads; sidecar.BlockMedia[0].HeadsSpecified = true; sidecar.BlockMedia[0].SectorsPerTrack = sectors; sidecar.BlockMedia[0].SectorsPerTrackSpecified = true; } 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)blockSize * (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); if (resume.BadBlocks.Count > 0) { resume.BadBlocks.Sort(); } DicConsole.WriteLine(); } if (dev.IsCompactFlash) { Statistics.AddMedia(MediaType.CompactFlash, true); } else if (dev.IsPcmcia) { Statistics.AddMedia(MediaType.PCCardTypeI, true); } else { Statistics.AddMedia(MediaType.GENERIC_HDD, true); } } else { DicConsole.ErrorWriteLine("Unable to communicate with ATA device."); } } } }
void OnBtnDumpClick(object sender, EventArgs e) { txtLog.Text = ""; btnClose.Visible = false; btnStart.Visible = false; btnStop.Visible = true; btnStop.Enabled = true; stkProgress.Visible = true; btnDestination.Visible = false; stkOptions.Visible = false; UpdateStatus("Opening device..."); try { dev = new Device(devicePath); if (dev.Error) { StoppingErrorMessage($"Error {dev.LastError} opening device."); return; } } catch (Exception exception) { StoppingErrorMessage($"Exception {exception.Message} opening device."); return; } Statistics.AddDevice(dev); Statistics.AddCommand("dump-media"); if (!(cmbFormat.SelectedValue is IWritableImage outputFormat)) { StoppingErrorMessage("Cannot open output plugin."); return; } Encoding encoding = null; if (cmbEncoding.SelectedValue is CommonEncodingInfo encodingInfo) { try { encoding = Claunia.Encoding.Encoding.GetEncoding(encodingInfo.Name); } catch (ArgumentException) { StoppingErrorMessage("Specified encoding is not supported."); return; } } Dictionary <string, string> parsedOptions = new Dictionary <string, string>(); if (grpOptions.Content is StackLayout stkFormatOptions) { foreach (Control option in stkFormatOptions.Children) { string value; switch (option) { case CheckBox optBoolean: value = optBoolean.Checked?.ToString(); break; case NumericStepper optNumber: value = optNumber.Value.ToString(CultureInfo.CurrentCulture); break; case TextBox optString: value = optString.Text; break; default: continue; } string key = option.ID.Substring(3); parsedOptions.Add(key, value); } } DumpLog dumpLog = new DumpLog(outputPrefix + ".log", dev); dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); dumper = new Dump(chkResume.Checked == true, dev, devicePath, outputFormat, (ushort)stpRetries.Value, chkForce.Checked == true, false, chkPersistent.Checked == true, chkStopOnError.Checked == true, resume, dumpLog, encoding, outputPrefix, txtDestination.Text, parsedOptions, sidecar, (uint)stpSkipped.Value, chkExistingMetadata.Checked == false, chkTrim.Checked == false, chkTrack1Pregap.Checked == true); new Thread(DoWork).Start(); }
/// <summary> /// Dumps an optical disc /// </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="dskType">Disc type as detected in MMC layer</param> /// <param name="dumpLeadIn">Try to read and dump as much Lead-in as possible</param> /// <param name="outputPath">Path to output file</param> /// <param name="formatOptions">Formats to pass to output file plugin</param> /// <exception cref="NotImplementedException">If trying to dump GOD or WOD, or XGDs without a Kreon drive</exception> internal static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, bool force, bool dumpRaw, bool persistent, bool stopOnError, ref MediaType dskType, ref Resume resume, ref DumpLog dumpLog, bool dumpLeadIn, Encoding encoding, string outputPrefix, string outputPath, Dictionary <string, string> formatOptions, CICMMetadataType preSidecar, uint skip, bool nometadata, bool notrim) { bool sense; ulong blocks; byte[] tmpBuf; bool compactDisc = true; bool isXbox = false; // TODO: Log not only what is it reading, but if it was read correctly or not. sense = dev.GetConfiguration(out byte[] cmdBuf, out _, 0, MmcGetConfigurationRt.Current, dev.Timeout, out _); if (!sense) { Features.SeparatedFeatures ftr = Features.Separate(cmdBuf); dumpLog.WriteLine("Device reports current profile is 0x{0:X4}", ftr.CurrentProfile); switch (ftr.CurrentProfile) { case 0x0001: dskType = MediaType.GENERIC_HDD; goto default; case 0x0005: dskType = MediaType.CDMO; break; case 0x0008: dskType = MediaType.CD; break; case 0x0009: dskType = MediaType.CDR; break; case 0x000A: dskType = MediaType.CDRW; break; case 0x0010: dskType = MediaType.DVDROM; goto default; case 0x0011: dskType = MediaType.DVDR; goto default; case 0x0012: dskType = MediaType.DVDRAM; goto default; case 0x0013: case 0x0014: dskType = MediaType.DVDRW; goto default; case 0x0015: case 0x0016: dskType = MediaType.DVDRDL; goto default; case 0x0017: dskType = MediaType.DVDRWDL; goto default; case 0x0018: dskType = MediaType.DVDDownload; goto default; case 0x001A: dskType = MediaType.DVDPRW; goto default; case 0x001B: dskType = MediaType.DVDPR; goto default; case 0x0020: dskType = MediaType.DDCD; goto default; case 0x0021: dskType = MediaType.DDCDR; goto default; case 0x0022: dskType = MediaType.DDCDRW; goto default; case 0x002A: dskType = MediaType.DVDPRWDL; goto default; case 0x002B: dskType = MediaType.DVDPRDL; goto default; case 0x0040: dskType = MediaType.BDROM; goto default; case 0x0041: case 0x0042: dskType = MediaType.BDR; goto default; case 0x0043: dskType = MediaType.BDRE; goto default; case 0x0050: dskType = MediaType.HDDVDROM; goto default; case 0x0051: dskType = MediaType.HDDVDR; goto default; case 0x0052: dskType = MediaType.HDDVDRAM; goto default; case 0x0053: dskType = MediaType.HDDVDRW; goto default; case 0x0058: dskType = MediaType.HDDVDRDL; goto default; case 0x005A: dskType = MediaType.HDDVDRWDL; goto default; default: compactDisc = false; break; } } if (compactDisc) { CompactDisc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, ref dskType, ref resume, ref dumpLog, dumpLeadIn, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim); return; } Reader scsiReader = new Reader(dev, dev.Timeout, null, dumpRaw); blocks = scsiReader.GetDeviceBlocks(); dumpLog.WriteLine("Device reports disc has {0} blocks", blocks); Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); #region Nintendo switch (dskType) { case MediaType.Unknown when blocks > 0: dumpLog.WriteLine("Reading Physical Format Information"); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, dev.Timeout, out _); if (!sense) { PFI.PhysicalFormatInformation?nintendoPfi = PFI.Decode(cmdBuf); if (nintendoPfi != null) { if (nintendoPfi.Value.DiskCategory == DiskCategory.Nintendo && nintendoPfi.Value.PartVersion == 15) { dumpLog.WriteLine("Dumping Nintendo GameCube or Wii discs is not yet implemented."); throw new NotImplementedException("Dumping Nintendo GameCube or Wii discs is not yet implemented."); } } } break; case MediaType.DVDDownload: case MediaType.DVDPR: case MediaType.DVDPRDL: case MediaType.DVDPRW: case MediaType.DVDPRWDL: case MediaType.DVDR: case MediaType.DVDRAM: case MediaType.DVDRDL: case MediaType.DVDROM: case MediaType.DVDRW: case MediaType.DVDRWDL: case MediaType.HDDVDR: case MediaType.HDDVDRAM: case MediaType.HDDVDRDL: case MediaType.HDDVDROM: case MediaType.HDDVDRW: case MediaType.HDDVDRWDL: dumpLog.WriteLine("Reading Physical Format Information"); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, dev.Timeout, out _); if (!sense) { if (PFI.Decode(cmdBuf).HasValue) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf); PFI.PhysicalFormatInformation decPfi = PFI.Decode(cmdBuf).Value; DicConsole.WriteLine("PFI:\n{0}", PFI.Prettify(decPfi)); // False book types if (dskType == MediaType.DVDROM) { switch (decPfi.DiskCategory) { case DiskCategory.DVDPR: dskType = MediaType.DVDPR; break; case DiskCategory.DVDPRDL: dskType = MediaType.DVDPRDL; break; case DiskCategory.DVDPRW: dskType = MediaType.DVDPRW; break; case DiskCategory.DVDPRWDL: dskType = MediaType.DVDPRWDL; break; case DiskCategory.DVDR: dskType = decPfi.PartVersion == 6 ? MediaType.DVDRDL : MediaType.DVDR; break; case DiskCategory.DVDRAM: dskType = MediaType.DVDRAM; break; default: dskType = MediaType.DVDROM; break; case DiskCategory.DVDRW: dskType = decPfi.PartVersion == 3 ? MediaType.DVDRWDL : MediaType.DVDRW; break; case DiskCategory.HDDVDR: dskType = MediaType.HDDVDR; break; case DiskCategory.HDDVDRAM: dskType = MediaType.HDDVDRAM; break; case DiskCategory.HDDVDROM: dskType = MediaType.HDDVDROM; break; case DiskCategory.HDDVDRW: dskType = MediaType.HDDVDRW; break; case DiskCategory.Nintendo: dskType = decPfi.DiscSize == DVDSize.Eighty ? MediaType.GOD : MediaType.WOD; break; case DiskCategory.UMD: dskType = MediaType.UMD; break; } } } } dumpLog.WriteLine("Reading Disc Manufacturing Information"); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DiscManufacturingInformation, 0, dev.Timeout, out _); if (!sense) { if (DMI.IsXbox(cmdBuf) || DMI.IsXbox360(cmdBuf)) { if (DMI.IsXbox(cmdBuf)) { dskType = MediaType.XGD; } else if (DMI.IsXbox360(cmdBuf)) { dskType = MediaType.XGD2; // All XGD3 all have the same number of blocks if (blocks == 25063 || // Locked (or non compatible drive) blocks == 4229664 || // Xtreme unlock blocks == 4246304) // Wxripper unlock { dskType = MediaType.XGD3; } } sense = dev.ScsiInquiry(out byte[] inqBuf, out _); if (sense || !Inquiry.Decode(inqBuf).HasValue || Inquiry.Decode(inqBuf).HasValue&& !Inquiry.Decode(inqBuf).Value.KreonPresent) { dumpLog.WriteLine("Dumping Xbox Game Discs requires a drive with Kreon firmware."); throw new NotImplementedException("Dumping Xbox Game Discs requires a drive with Kreon firmware."); } if (dumpRaw && !force) { DicConsole .ErrorWriteLine("Not continuing. If you want to continue reading cooked data when raw is not available use the force option."); // TODO: Exit more gracefully return; } isXbox = true; } if (cmdBuf.Length == 2052) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf); } } break; } #endregion Nintendo #region All DVD and HD DVD types #endregion All DVD and HD DVD types #region DVD-ROM if (dskType == MediaType.DVDDownload || dskType == MediaType.DVDROM) { dumpLog.WriteLine("Reading Lead-in Copyright Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.CopyrightInformation, 0, dev.Timeout, out _); if (!sense) { if (CSS_CPRM.DecodeLeadInCopyright(cmdBuf).HasValue) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVD_CMI, tmpBuf); } } } #endregion DVD-ROM switch (dskType) { #region DVD-ROM and HD DVD-ROM case MediaType.DVDDownload: case MediaType.DVDROM: case MediaType.HDDVDROM: dumpLog.WriteLine("Reading Burst Cutting Area."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.BurstCuttingArea, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVD_BCA, tmpBuf); } break; #endregion DVD-ROM and HD DVD-ROM #region DVD-RAM and HD DVD-RAM case MediaType.DVDRAM: case MediaType.HDDVDRAM: dumpLog.WriteLine("Reading Disc Description Structure."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DvdramDds, 0, dev.Timeout, out _); if (!sense) { if (DDS.Decode(cmdBuf).HasValue) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVDRAM_DDS, tmpBuf); } } dumpLog.WriteLine("Reading Spare Area Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DvdramSpareAreaInformation, 0, dev.Timeout, out _); if (!sense) { if (Spare.Decode(cmdBuf).HasValue) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVDRAM_SpareArea, tmpBuf); } } break; #endregion DVD-RAM and HD DVD-RAM #region DVD-R and DVD-RW case MediaType.DVDR: case MediaType.DVDRW: dumpLog.WriteLine("Reading Pre-Recorded Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PreRecordedInfo, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVDR_PreRecordedInfo, tmpBuf); } break; #endregion DVD-R and DVD-RW } switch (dskType) { #region DVD-R, DVD-RW and HD DVD-R case MediaType.DVDR: case MediaType.DVDRW: case MediaType.HDDVDR: dumpLog.WriteLine("Reading Media Identifier."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DvdrMediaIdentifier, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVDR_MediaIdentifier, tmpBuf); } dumpLog.WriteLine("Reading Recordable Physical Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DvdrPhysicalInformation, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVDR_PFI, tmpBuf); } break; #endregion DVD-R, DVD-RW and HD DVD-R #region All DVD+ case MediaType.DVDPR: case MediaType.DVDPRDL: case MediaType.DVDPRW: case MediaType.DVDPRWDL: dumpLog.WriteLine("Reading ADdress In Pregroove."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.Adip, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DVD_ADIP, tmpBuf); } dumpLog.WriteLine("Reading Disc Control Blocks."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.Dcb, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.DCB, tmpBuf); } break; #endregion All DVD+ #region HD DVD-ROM case MediaType.HDDVDROM: dumpLog.WriteLine("Reading Lead-in Copyright Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.HddvdCopyrightInformation, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.HDDVD_CPI, tmpBuf); } break; #endregion HD DVD-ROM #region All Blu-ray case MediaType.BDR: case MediaType.BDRE: case MediaType.BDROM: case MediaType.BDRXL: case MediaType.BDREXL: dumpLog.WriteLine("Reading Disc Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, MmcDiscStructureFormat.DiscInformation, 0, dev.Timeout, out _); if (!sense) { if (DI.Decode(cmdBuf).HasValue) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.BD_DI, tmpBuf); } } // TODO: PAC /* * dumpLog.WriteLine("Reading PAC."); * sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, * MmcDiscStructureFormat.Pac, 0, dev.Timeout, out _); * if(!sense) * { * tmpBuf = new byte[cmdBuf.Length - 4]; * Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); * mediaTags.Add(MediaTagType.PAC, tmpBuf); * }*/ break; #endregion All Blu-ray } switch (dskType) { #region BD-ROM only case MediaType.BDROM: dumpLog.WriteLine("Reading Burst Cutting Area."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, MmcDiscStructureFormat.BdBurstCuttingArea, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.BD_BCA, tmpBuf); } break; #endregion BD-ROM only #region Writable Blu-ray only case MediaType.BDR: case MediaType.BDRE: case MediaType.BDRXL: case MediaType.BDREXL: dumpLog.WriteLine("Reading Disc Definition Structure."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, MmcDiscStructureFormat.BdDds, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.BD_DDS, tmpBuf); } dumpLog.WriteLine("Reading Spare Area Information."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, MmcDiscStructureFormat.BdSpareAreaInformation, 0, dev.Timeout, out _); if (!sense) { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); mediaTags.Add(MediaTagType.BD_SpareArea, tmpBuf); } break; #endregion Writable Blu-ray only } if (isXbox) { Xgd.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, mediaTags, ref dskType, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim); return; } Sbc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, mediaTags, ref dskType, true, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim); }
public static void GetOffset(CdOffset cdOffset, Device dbDev, bool debug, Aaru.Devices.Device dev, MediaType dskType, DumpLog dumpLog, Track[] tracks, UpdateStatusHandler updateStatus, out int?driveOffset, out int?combinedOffset, out bool supportsPlextorReadCdDa) { byte[] cmdBuf; bool sense; int minute; int second; int frame; byte[] sectorSync; byte[] tmpBuf; int lba; int diff; Track dataTrack = default; Track audioTrack = default; bool offsetFound = false; const uint sectorSize = 2352; driveOffset = cdOffset?.Offset * 4; combinedOffset = null; supportsPlextorReadCdDa = false; if (dskType != MediaType.VideoNowColor) { if (tracks.Any(t => t.TrackType != TrackType.Audio)) { dataTrack = tracks.FirstOrDefault(t => t.TrackType != TrackType.Audio); if (dataTrack != null) { // Build sync sectorSync = new byte[] { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; tmpBuf = new byte[sectorSync.Length]; // Ensure to be out of the pregap, or multi-session discs give funny values uint wantedLba = (uint)(dataTrack.TrackStartSector + 151); // Plextor READ CDDA if (dbDev?.ATAPI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true || dbDev?.SCSI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true || dev.Manufacturer.ToLowerInvariant() == "plextor") { sense = dev.PlextorReadCdDa(out cmdBuf, out _, wantedLba, sectorSize, 3, PlextorSubchannel.None, dev.Timeout, out _); if (!sense && !dev.Error) { supportsPlextorReadCdDa = true; for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } // De-scramble M and S minute = cmdBuf[i + 12] ^ 0x01; second = cmdBuf[i + 13] ^ 0x80; frame = cmdBuf[i + 14]; // Convert to binary minute = ((minute / 16) * 10) + (minute & 0x0F); second = ((second / 16) * 10) + (second & 0x0F); frame = ((frame / 16) * 10) + (frame & 0x0F); // Calculate the first found LBA lba = ((minute * 60 * 75) + (second * 75) + frame) - 150; // Calculate the difference between the found LBA and the requested one diff = (int)wantedLba - lba; combinedOffset = i + (2352 * diff); offsetFound = true; break; } } } if (!offsetFound && (debug || dbDev?.ATAPI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true || dbDev?.SCSI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true || dbDev?.SCSI?.MultiMediaDevice?.TestedMedia?.Any(d => d.CanReadCdScrambled == true) == true || dev.Manufacturer.ToLowerInvariant() == "hl-dt-st")) { sense = dev.ReadCd(out cmdBuf, out _, wantedLba, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (!sense && !dev.Error) { // Clear cache for (int i = 0; i < 63; i++) { sense = dev.ReadCd(out _, out _, (uint)(wantedLba + 3 + (16 * i)), sectorSize, 16, MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { break; } } dev.ReadCd(out cmdBuf, out _, wantedLba, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } // De-scramble M and S minute = cmdBuf[i + 12] ^ 0x01; second = cmdBuf[i + 13] ^ 0x80; frame = cmdBuf[i + 14]; // Convert to binary minute = ((minute / 16) * 10) + (minute & 0x0F); second = ((second / 16) * 10) + (second & 0x0F); frame = ((frame / 16) * 10) + (frame & 0x0F); // Calculate the first found LBA lba = ((minute * 60 * 75) + (second * 75) + frame) - 150; // Calculate the difference between the found LBA and the requested one diff = (int)wantedLba - lba; combinedOffset = i + (2352 * diff); offsetFound = true; break; } } } } } if (offsetFound) { return; } // Try to get another the offset some other way, we need an audio track just after a data track, same session for (int i = 1; i < tracks.Length; i++) { if (tracks[i - 1].TrackType == TrackType.Audio || tracks[i].TrackType != TrackType.Audio) { continue; } dataTrack = tracks[i - 1]; audioTrack = tracks[i]; break; } if (dataTrack is null || audioTrack is null) { return; } // Found them sense = dev.ReadCd(out cmdBuf, out _, (uint)audioTrack.TrackStartSector, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { return; } dataTrack.TrackEndSector += 150; // Calculate MSF minute = (int)dataTrack.TrackEndSector / 4500; second = ((int)dataTrack.TrackEndSector - (minute * 4500)) / 75; frame = (int)dataTrack.TrackEndSector - (minute * 4500) - (second * 75); dataTrack.TrackEndSector -= 150; // Convert to BCD minute = ((minute / 10) << 4) + (minute % 10); second = ((second / 10) << 4) + (second % 10); frame = ((frame / 10) << 4) + (frame % 10); // Scramble M and S minute ^= 0x01; second ^= 0x80; // Build sync sectorSync = new byte[] { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, (byte)minute, (byte)second, (byte)frame }; tmpBuf = new byte[sectorSync.Length]; for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } combinedOffset = i + 2352; offsetFound = true; break; } if (offsetFound || audioTrack.TrackPregap <= 0) { return; } sense = dev.ReadCd(out byte[] dataBuf, out _, (uint)dataTrack.TrackEndSector, sectorSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { return; } for (int i = 0; i < dataBuf.Length; i++) { dataBuf[i] ^= Sector.ScrambleTable[i]; } for (int i = 0; i < 2352; i++) { byte[] dataSide = new byte[2352 - i]; byte[] audioSide = new byte[2352 - i]; Array.Copy(dataBuf, i, dataSide, 0, dataSide.Length); Array.Copy(cmdBuf, 0, audioSide, 0, audioSide.Length); if (!dataSide.SequenceEqual(audioSide)) { continue; } combinedOffset = audioSide.Length; break; } } else { byte[] videoNowColorFrame = new byte[9 * sectorSize]; sense = dev.ReadCd(out cmdBuf, out _, 0, sectorSize, 9, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { sense = dev.ReadCd(out cmdBuf, out _, 0, sectorSize, 9, MmcSectorTypes.Cdda, false, false, true, MmcHeaderCodes.None, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { videoNowColorFrame = null; } } if (videoNowColorFrame is null) { dumpLog?.WriteLine("Could not find VideoNow Color frame offset, dump may not be correct."); updateStatus?.Invoke("Could not find VideoNow Color frame offset, dump may not be correct."); } else { combinedOffset = MMC.GetVideoNowColorOffset(videoNowColorFrame); dumpLog?.WriteLine($"VideoNow Color frame is offset {combinedOffset} bytes."); updateStatus?.Invoke($"VideoNow Color frame is offset {combinedOffset} bytes."); } } }
public static int Invoke(bool debug, bool verbose, string cicmXml, string devicePath, bool resume, string encoding, bool firstPregap, bool fixOffset, bool force, bool metadata, bool trim, string outputPath, string options, bool persistent, ushort retryPasses, uint skip, byte speed, bool stopOnError, string format, string subchannel, bool @private) { MainClass.PrintCopyright(); if (debug) { AaruConsole.DebugWriteLineEvent += System.Console.Error.WriteLine; } if (verbose) { AaruConsole.VerboseWriteLineEvent += System.Console.WriteLine; } Statistics.AddCommand("dump-media"); AaruConsole.DebugWriteLine("Dump-Media command", "--cicm-xml={0}", cicmXml); AaruConsole.DebugWriteLine("Dump-Media command", "--debug={0}", debug); AaruConsole.DebugWriteLine("Dump-Media command", "--device={0}", devicePath); AaruConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", encoding); AaruConsole.DebugWriteLine("Dump-Media command", "--first-pregap={0}", firstPregap); AaruConsole.DebugWriteLine("Dump-Media command", "--fix-offset={0}", fixOffset); AaruConsole.DebugWriteLine("Dump-Media command", "--force={0}", force); AaruConsole.DebugWriteLine("Dump-Media command", "--format={0}", format); AaruConsole.DebugWriteLine("Dump-Media command", "--metadata={0}", metadata); AaruConsole.DebugWriteLine("Dump-Media command", "--options={0}", options); AaruConsole.DebugWriteLine("Dump-Media command", "--output={0}", outputPath); AaruConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", persistent); AaruConsole.DebugWriteLine("Dump-Media command", "--resume={0}", resume); AaruConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", retryPasses); AaruConsole.DebugWriteLine("Dump-Media command", "--skip={0}", skip); AaruConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", stopOnError); AaruConsole.DebugWriteLine("Dump-Media command", "--trim={0}", trim); AaruConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", verbose); AaruConsole.DebugWriteLine("Dump-Media command", "--subchannel={0}", subchannel); AaruConsole.DebugWriteLine("Dump-Media command", "--private={0}", @private); // TODO: Disabled temporarily //AaruConsole.DebugWriteLine("Dump-Media command", "--raw={0}", raw); Dictionary <string, string> parsedOptions = Core.Options.Parse(options); AaruConsole.DebugWriteLine("Dump-Media command", "Parsed options:"); foreach (KeyValuePair <string, string> parsedOption in parsedOptions) { AaruConsole.DebugWriteLine("Dump-Media command", "{0} = {1}", parsedOption.Key, parsedOption.Value); } Encoding encodingClass = null; if (encoding != null) { try { encodingClass = Claunia.Encoding.Encoding.GetEncoding(encoding); if (verbose) { AaruConsole.VerboseWriteLine("Using encoding for {0}.", encodingClass.EncodingName); } } catch (ArgumentException) { AaruConsole.ErrorWriteLine("Specified encoding is not supported."); return((int)ErrorNumber.EncodingUnknown); } } DumpSubchannel wantedSubchannel = DumpSubchannel.Any; switch (subchannel?.ToLowerInvariant()) { case "any": case null: wantedSubchannel = DumpSubchannel.Any; break; case "rw": wantedSubchannel = DumpSubchannel.Rw; break; case "rw-or-pq": wantedSubchannel = DumpSubchannel.RwOrPq; break; case "pq": wantedSubchannel = DumpSubchannel.Pq; break; case "none": wantedSubchannel = DumpSubchannel.None; break; default: AaruConsole.WriteLine("Incorrect subchannel type \"{0}\" requested.", subchannel); break; } if (devicePath.Length == 2 && devicePath[1] == ':' && devicePath[0] != '/' && char.IsLetter(devicePath[0])) { devicePath = "\\\\.\\" + char.ToUpper(devicePath[0]) + ':'; } Devices.Device dev; try { dev = new Devices.Device(devicePath); if (dev.IsRemote) { Statistics.AddRemote(dev.RemoteApplication, dev.RemoteVersion, dev.RemoteOperatingSystem, dev.RemoteOperatingSystemVersion, dev.RemoteArchitecture); } if (dev.Error) { AaruConsole.ErrorWriteLine(Error.Print(dev.LastError)); return((int)ErrorNumber.CannotOpenDevice); } } catch (DeviceException e) { AaruConsole.ErrorWriteLine(e.Message ?? Error.Print(e.LastError)); return((int)ErrorNumber.CannotOpenDevice); } Statistics.AddDevice(dev); string outputPrefix = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)); Resume resumeClass = null; var xs = new XmlSerializer(typeof(Resume)); if (File.Exists(outputPrefix + ".resume.xml") && resume) { try { var sr = new StreamReader(outputPrefix + ".resume.xml"); resumeClass = (Resume)xs.Deserialize(sr); sr.Close(); } catch { AaruConsole.ErrorWriteLine("Incorrect resume file, not continuing..."); return((int)ErrorNumber.InvalidResume); } } if (resumeClass != null && resumeClass.NextBlock > resumeClass.LastBlock && resumeClass.BadBlocks.Count == 0 && !resumeClass.Tape) { AaruConsole.WriteLine("Media already dumped correctly, not continuing..."); return((int)ErrorNumber.AlreadyDumped); } CICMMetadataType sidecar = null; var sidecarXs = new XmlSerializer(typeof(CICMMetadataType)); if (cicmXml != null) { if (File.Exists(cicmXml)) { try { var sr = new StreamReader(cicmXml); sidecar = (CICMMetadataType)sidecarXs.Deserialize(sr); sr.Close(); } catch { AaruConsole.ErrorWriteLine("Incorrect metadata sidecar file, not continuing..."); return((int)ErrorNumber.InvalidSidecar); } } else { AaruConsole.ErrorWriteLine("Could not find metadata sidecar, not continuing..."); return((int)ErrorNumber.FileNotFound); } } PluginBase plugins = GetPluginBase.Instance; List <IWritableImage> candidates = new List <IWritableImage>(); // Try extension if (string.IsNullOrEmpty(format)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.KnownExtensions. Contains(Path.GetExtension(outputPath)))); } // Try Id else if (Guid.TryParse(format, out Guid outId)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); } // Try name else { candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, format, StringComparison. InvariantCultureIgnoreCase))); } if (candidates.Count == 0) { AaruConsole.WriteLine("No plugin supports requested extension."); return((int)ErrorNumber.FormatNotFound); } if (candidates.Count > 1) { AaruConsole.WriteLine("More than one plugin supports requested extension."); return((int)ErrorNumber.TooManyFormats); } IWritableImage outputFormat = candidates[0]; var dumpLog = new DumpLog(outputPrefix + ".log", dev, @private); if (verbose) { dumpLog.WriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); AaruConsole.VerboseWriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); } else { dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); AaruConsole.WriteLine("Output image format: {0}.", outputFormat.Name); } var dumper = new Dump(resume, dev, devicePath, outputFormat, retryPasses, force, false, persistent, stopOnError, resumeClass, dumpLog, encodingClass, outputPrefix, outputPath, parsedOptions, sidecar, skip, metadata, trim, firstPregap, fixOffset, debug, wantedSubchannel, speed, @private); dumper.UpdateStatus += Progress.UpdateStatus; dumper.ErrorMessage += Progress.ErrorMessage; dumper.StoppingErrorMessage += Progress.ErrorMessage; dumper.UpdateProgress += Progress.UpdateProgress; dumper.PulseProgress += Progress.PulseProgress; dumper.InitProgress += Progress.InitProgress; dumper.EndProgress += Progress.EndProgress; dumper.InitProgress2 += Progress.InitProgress2; dumper.EndProgress2 += Progress.EndProgress2; dumper.UpdateProgress2 += Progress.UpdateProgress2; System.Console.CancelKeyPress += (sender, e) => { e.Cancel = true; dumper.Abort(); }; dumper.Start(); dev.Close(); return((int)ErrorNumber.NoError); }
/// <summary>Reads the TOC, processes it, returns the track list and last sector</summary> /// <param name="dev">Device</param> /// <param name="dumpLog">Dump log</param> /// <param name="force">Force dump enabled</param> /// <param name="lastSector">Last sector number</param> /// <param name="leadOutStarts">Lead-out starts</param> /// <param name="mediaTags">Media tags</param> /// <param name="stoppingErrorMessage">Stopping error message handler</param> /// <param name="toc">Full CD TOC</param> /// <param name="trackFlags">Track flags</param> /// <param name="updateStatus">Update status handler</param> /// <returns>List of tracks</returns> public static Track[] GetCdTracks(Device dev, DumpLog dumpLog, bool force, out long lastSector, Dictionary <int, long> leadOutStarts, Dictionary <MediaTagType, byte[]> mediaTags, ErrorMessageHandler stoppingErrorMessage, out FullTOC.CDFullTOC?toc, Dictionary <byte, byte> trackFlags, UpdateStatusHandler updateStatus) { byte[] cmdBuf; // Data buffer const uint sectorSize = 2352; // Full sector size bool sense; // Sense indicator List <Track> trackList = new List <Track>(); // Tracks in disc byte[] tmpBuf; // Temporary buffer toc = null; lastSector = 0; TrackType leadoutTrackType = TrackType.Audio; // We discarded all discs that falsify a TOC before requesting a real TOC // No TOC, no CD (or an empty one) dumpLog?.WriteLine("Reading full TOC"); updateStatus?.Invoke("Reading full TOC"); sense = dev.ReadRawToc(out cmdBuf, out _, 0, dev.Timeout, out _); if (!sense) { toc = FullTOC.Decode(cmdBuf); if (toc.HasValue) { tmpBuf = new byte[cmdBuf.Length - 2]; Array.Copy(cmdBuf, 2, tmpBuf, 0, cmdBuf.Length - 2); mediaTags?.Add(MediaTagType.CD_FullTOC, tmpBuf); } } updateStatus?.Invoke("Building track map..."); dumpLog?.WriteLine("Building track map..."); if (toc.HasValue) { FullTOC.TrackDataDescriptor[] sortedTracks = toc.Value.TrackDescriptors.OrderBy(track => track.POINT).ToArray(); foreach (FullTOC.TrackDataDescriptor trk in sortedTracks.Where(trk => trk.ADR == 1 || trk.ADR == 4)) { if (trk.POINT >= 0x01 && trk.POINT <= 0x63) { trackList.Add(new Track { TrackSequence = trk.POINT, TrackSession = trk.SessionNumber, TrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio, TrackStartSector = (ulong)((trk.PHOUR * 3600 * 75) + (trk.PMIN * 60 * 75) + (trk.PSEC * 75) + trk.PFRAME - 150), TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(trk.POINT, trk.CONTROL); } else if (trk.POINT == 0xA2) { int phour, pmin, psec, pframe; if (trk.PFRAME == 0) { pframe = 74; if (trk.PSEC == 0) { psec = 59; if (trk.PMIN == 0) { pmin = 59; phour = trk.PHOUR - 1; } else { pmin = trk.PMIN - 1; phour = trk.PHOUR; } } else { psec = trk.PSEC - 1; pmin = trk.PMIN; phour = trk.PHOUR; } } else { pframe = trk.PFRAME - 1; psec = trk.PSEC; pmin = trk.PMIN; phour = trk.PHOUR; } lastSector = (phour * 3600 * 75) + (pmin * 60 * 75) + (psec * 75) + pframe - 150; leadOutStarts?.Add(trk.SessionNumber, lastSector + 1); } else if (trk.POINT == 0xA0 && trk.ADR == 1) { leadoutTrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio; } } } else { updateStatus?.Invoke("Cannot read RAW TOC, requesting processed one..."); dumpLog?.WriteLine("Cannot read RAW TOC, requesting processed one..."); sense = dev.ReadToc(out cmdBuf, out _, false, 0, dev.Timeout, out _); TOC.CDTOC?oldToc = TOC.Decode(cmdBuf); if ((sense || !oldToc.HasValue) && !force) { dumpLog?.WriteLine("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000..."); stoppingErrorMessage?. Invoke("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000..."); return(null); } if (oldToc.HasValue) { foreach (TOC.CDTOCTrackDataDescriptor trk in oldToc.Value.TrackDescriptors. OrderBy(t => t.TrackNumber). Where(trk => trk.ADR == 1 || trk.ADR == 4)) { if (trk.TrackNumber >= 0x01 && trk.TrackNumber <= 0x63) { trackList.Add(new Track { TrackSequence = trk.TrackNumber, TrackSession = 1, TrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio, TrackStartSector = trk.TrackStartAddress, TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(trk.TrackNumber, trk.CONTROL); } else if (trk.TrackNumber == 0xAA) { leadoutTrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio; lastSector = trk.TrackStartAddress - 1; } } } } if (trackList.Count == 0) { updateStatus?.Invoke("No tracks found, adding a single track from 0 to Lead-Out"); dumpLog?.WriteLine("No tracks found, adding a single track from 0 to Lead-Out"); trackList.Add(new Track { TrackSequence = 1, TrackSession = 1, TrackType = leadoutTrackType, TrackStartSector = 0, TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(1, (byte)(leadoutTrackType == TrackType.Audio ? 0 : 4)); } if (lastSector != 0) { return(trackList.ToArray()); } sense = dev.ReadCapacity16(out cmdBuf, out _, dev.Timeout, out _); if (!sense) { byte[] temp = new byte[8]; Array.Copy(cmdBuf, 0, temp, 0, 8); Array.Reverse(temp); lastSector = (long)BitConverter.ToUInt64(temp, 0); } else { sense = dev.ReadCapacity(out cmdBuf, out _, dev.Timeout, out _); if (!sense) { lastSector = (cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + cmdBuf[3]; } } if (lastSector > 0) { return(trackList.ToArray()); } if (!force) { stoppingErrorMessage?. Invoke("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors..."); dumpLog?.WriteLine("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors..."); return(null); } updateStatus?. Invoke("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before..."); dumpLog?.WriteLine("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before..."); lastSector = 360000; return(trackList.ToArray()); }
// TODO: Get cartridge serial number from Certance vendor EVPD /// <summary> /// Dumps a SCSI Block Commands device or a Reduced Block Commands devices /// </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 long or scrambled 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="dumpLeadIn">Try to read and dump as much Lead-in as possible</param> /// <param name="outputPath">Path to output file</param> /// <param name="formatOptions">Formats to pass to output file plugin</param> /// <exception cref="ArgumentException">If you asked to dump long sectors from a SCSI Streaming device</exception> public static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, bool force, bool dumpRaw, bool persistent, bool stopOnError, ref Resume resume, ref DumpLog dumpLog, bool dumpLeadIn, Encoding encoding, string outputPrefix, string outputPath, Dictionary <string, string> formatOptions, CICMMetadataType preSidecar, uint skip, bool nometadata, bool notrim) { MediaType dskType = MediaType.Unknown; int resets = 0; if (dev.IsRemovable) { deviceGotReset: bool sense = dev.ScsiTestUnitReady(out byte[] senseBuf, dev.Timeout, out _); if (sense) { FixedSense?decSense = Sense.DecodeFixed(senseBuf); if (decSense.HasValue) { dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ); // Just retry, for 5 times if (decSense.Value.ASC == 0x29) { resets++; if (resets < 5) { goto deviceGotReset; } } if (decSense.Value.ASC == 0x3A) { int leftRetries = 5; while (leftRetries > 0) { DicConsole.WriteLine("\rWaiting for drive to become ready"); Thread.Sleep(2000); sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out _); if (!sense) { break; } decSense = Sense.DecodeFixed(senseBuf); if (decSense.HasValue) { dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ); } leftRetries--; } if (sense) { DicConsole.ErrorWriteLine("Please insert media in drive"); return; } } else if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) { int leftRetries = 10; while (leftRetries > 0) { DicConsole.WriteLine("\rWaiting for drive to become ready"); Thread.Sleep(2000); sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out _); if (!sense) { break; } decSense = Sense.DecodeFixed(senseBuf); if (decSense.HasValue) { dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ); } leftRetries--; } if (sense) { DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Sense.PrettifySense(senseBuf)); return; } } /*else if (decSense.Value.ASC == 0x29 && decSense.Value.ASCQ == 0x00) * { * if (!deviceReset) * { * deviceReset = true; * DicConsole.ErrorWriteLine("Device did reset, retrying..."); * goto retryTestReady; * } * * DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Decoders.SCSI.Sense.PrettifySense(senseBuf)); * return; * }*/ // These should be trapped by the OS but seems in some cases they're not else if (decSense.Value.ASC == 0x28) { int leftRetries = 10; while (leftRetries > 0) { DicConsole.WriteLine("\rWaiting for drive to become ready"); Thread.Sleep(2000); sense = dev.ScsiTestUnitReady(out senseBuf, dev.Timeout, out _); if (!sense) { break; } decSense = Sense.DecodeFixed(senseBuf); if (decSense.HasValue) { dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ); } leftRetries--; } if (sense) { DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Sense.PrettifySense(senseBuf)); return; } } else { DicConsole.ErrorWriteLine("Error testing unit was ready:\n{0}", Sense.PrettifySense(senseBuf)); return; } } else { DicConsole.ErrorWriteLine("Unknown testing unit was ready."); return; } } } switch (dev.ScsiType) { case PeripheralDeviceTypes.SequentialAccess: if (dumpRaw) { throw new ArgumentException("Tapes cannot be dumped raw."); } Ssc.Dump(dev, outputPrefix, devicePath, ref resume, ref dumpLog, preSidecar); return; case PeripheralDeviceTypes.MultiMediaDevice: Mmc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, ref dskType, ref resume, ref dumpLog, dumpLeadIn, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim); return; default: Sbc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, null, ref dskType, false, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim); break; } }
/// <summary> /// Dumps the tape from a SCSI Streaming device /// </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="resume">Information for dump resuming</param> /// <param name="dumpLog">Dump logger</param> internal static void Dump(Device dev, string outputPrefix, string devicePath, ref Resume resume, ref DumpLog dumpLog, CICMMetadataType preSidecar) { FixedSense? fxSense; bool aborted; bool sense; ulong blocks = 0; uint blockSize; MediaType dskType = MediaType.Unknown; DateTime start; DateTime end; double totalDuration = 0; double totalChkDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; CICMMetadataType sidecar = preSidecar ?? new CICMMetadataType(); dev.RequestSense(out byte[] senseBuf, dev.Timeout, out double duration); fxSense = Sense.DecodeFixed(senseBuf, out string strSense); if (fxSense.HasValue && fxSense.Value.SenseKey != SenseKeys.NoSense) { dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); DicConsole.ErrorWriteLine("Drive has status error, please correct. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); return; } // Not in BOM/P if (fxSense.HasValue && fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x00 && fxSense.Value.ASCQ != 0x04 && fxSense.Value.SenseKey != SenseKeys.IllegalRequest) { dumpLog.WriteLine("Rewinding, please wait..."); DicConsole.Write("Rewinding, please wait..."); // Rewind, let timeout apply dev.Rewind(out senseBuf, dev.Timeout, out duration); // Still rewinding? // TODO: Pause? do { DicConsole.Write("\rRewinding, please wait..."); dev.RequestSense(out senseBuf, dev.Timeout, out duration); fxSense = Sense.DecodeFixed(senseBuf, out strSense); }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 && (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ != 0x04)); dev.RequestSense(out senseBuf, dev.Timeout, out duration); fxSense = Sense.DecodeFixed(senseBuf, out strSense); // And yet, did not rewind! if (fxSense.HasValue && (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 || fxSense.Value.ASC != 0x00)) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } DicConsole.WriteLine(); } // Check position sense = dev.ReadPosition(out byte[] cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout, out duration); if (sense) { // READ POSITION is mandatory starting SCSI-2, so do not cry if the drive does not recognize the command (SCSI-1 or earlier) // Anyway, <=SCSI-1 tapes do not support partitions fxSense = Sense.DecodeFixed(senseBuf, out strSense); if (fxSense.HasValue && (fxSense.Value.ASC == 0x20 && fxSense.Value.ASCQ != 0x00 || fxSense.Value.ASC != 0x20 && fxSense.Value.SenseKey != SenseKeys.IllegalRequest)) { DicConsole.ErrorWriteLine("Could not get position. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Could not get position. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } } else { // Not in partition 0 if (cmdBuf[1] != 0) { DicConsole.Write("Drive not in partition 0. Rewinding, please wait..."); dumpLog.WriteLine("Drive not in partition 0. Rewinding, please wait..."); // Rewind, let timeout apply sense = dev.Locate(out senseBuf, false, 0, 0, dev.Timeout, out duration); if (sense) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } // Still rewinding? // TODO: Pause? do { Thread.Sleep(1000); DicConsole.Write("\rRewinding, please wait..."); dev.RequestSense(out senseBuf, dev.Timeout, out duration); fxSense = Sense.DecodeFixed(senseBuf, out strSense); }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 && (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ == 0x19)); // And yet, did not rewind! if (fxSense.HasValue && (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 || fxSense.Value.ASC != 0x00)) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } sense = dev.ReadPosition(out cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout, out duration); if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } // Still not in partition 0!!!? if (cmdBuf[1] != 0) { DicConsole.ErrorWriteLine("Drive could not rewind to partition 0 but no error occurred..."); dumpLog.WriteLine("Drive could not rewind to partition 0 but no error occurred..."); return; } DicConsole.WriteLine(); } } sidecar.BlockMedia = new BlockMediaType[1]; sidecar.BlockMedia[0] = new BlockMediaType { SCSI = new SCSIType() }; byte scsiMediumTypeTape = 0; byte scsiDensityCodeTape = 0; dumpLog.WriteLine("Requesting MODE SENSE (10)."); sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F, 0xFF, 5, out duration); if (!sense || dev.Error) { sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5, out duration); } Modes.DecodedMode?decMode = null; if (!sense && !dev.Error) { if (Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue) { decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType); sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType { Image = outputPrefix + ".modesense10.bin", Size = cmdBuf.Length, Checksums = Checksum.GetChecksums(cmdBuf).ToArray() }; DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense10.Image, cmdBuf); } } dumpLog.WriteLine("Requesting MODE SENSE (6)."); sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5, out duration); if (sense || dev.Error) { sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5, out duration); } if (sense || dev.Error) { sense = dev.ModeSense(out cmdBuf, out senseBuf, 5, out duration); } if (!sense && !dev.Error) { if (Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue) { decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType); sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType { Image = outputPrefix + ".modesense.bin", Size = cmdBuf.Length, Checksums = Checksum.GetChecksums(cmdBuf).ToArray() }; DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense.Image, cmdBuf); } } // TODO: Check partitions page if (decMode.HasValue) { scsiMediumTypeTape = (byte)decMode.Value.Header.MediumType; if (decMode.Value.Header.BlockDescriptors != null && decMode.Value.Header.BlockDescriptors.Length >= 1) { scsiDensityCodeTape = (byte)decMode.Value.Header.BlockDescriptors[0].Density; } blockSize = decMode.Value.Header.BlockDescriptors[0].BlockLength; dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize); } else { blockSize = 1; } if (dskType == MediaType.Unknown) { dskType = MediaTypeFromScsi.Get((byte)dev.ScsiType, dev.Manufacturer, dev.Model, scsiMediumTypeTape, scsiDensityCodeTape, blocks, blockSize); } DicConsole.WriteLine("Media identified as {0}", dskType); dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType); dumpLog.WriteLine("SCSI medium type: {0}.", scsiMediumTypeTape); dumpLog.WriteLine("SCSI density type: {0}.", scsiDensityCodeTape); dumpLog.WriteLine("Media identified as {0}.", dskType); bool endOfMedia = false; ulong currentBlock = 0; ulong currentFile = 0; byte currentPartition = 0; byte totalPartitions = 1; // TODO: Handle partitions. ulong currentSize = 0; ulong currentPartitionSize = 0; ulong currentFileSize = 0; bool fixedLen = false; uint transferLen = blockSize; sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout, out duration); if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); if (fxSense.HasValue) { if (fxSense.Value.SenseKey == SenseKeys.IllegalRequest) { sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration); if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); if (!fxSense.HasValue || !fxSense.Value.EOM) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not return back. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not return back. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } } fixedLen = true; transferLen = 1; sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout, out duration); if (sense) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not read. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not read. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } } else { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not read. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not read. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } } else { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Cannot read device, don't know why, exiting..."); dumpLog.WriteLine("Cannot read device, don't know why, exiting..."); return; } } sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration); if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); if (!fxSense.HasValue || !fxSense.Value.EOM) { DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not return back. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpLog.WriteLine("Drive could not return back. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } } List <TapePartitionType> partitions = new List <TapePartitionType>(); List <TapeFileType> files = new List <TapeFileType>(); DicConsole.WriteLine(); DataFile dumpFile = new DataFile(outputPrefix + ".bin"); Checksum dataChk = new Checksum(); start = DateTime.UtcNow; MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, 1); IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", 0x0008); TapeFileType currentTapeFile = new TapeFileType { Image = new ImageType { format = "BINARY", offset = (long)currentSize, offsetSpecified = true, Value = outputPrefix + ".bin" }, Sequence = (long)currentFile, StartBlock = (long)currentBlock, BlockSize = blockSize }; Checksum fileChk = new Checksum(); TapePartitionType currentTapePartition = new TapePartitionType { Image = new ImageType { format = "BINARY", offset = (long)currentSize, offsetSpecified = true, Value = outputPrefix + ".bin" }, Sequence = currentPartition, StartBlock = (long)currentBlock }; Checksum partitionChk = new Checksum(); aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; while (currentPartition < totalPartitions) { if (aborted) { dumpLog.WriteLine("Aborted!"); break; } if (endOfMedia) { DicConsole.WriteLine(); DicConsole.WriteLine("Finished partition {0}", currentPartition); dumpLog.WriteLine("Finished partition {0}", currentPartition); currentTapePartition.File = files.ToArray(); currentTapePartition.Checksums = partitionChk.End().ToArray(); currentTapePartition.EndBlock = (long)(currentBlock - 1); currentTapePartition.Size = (long)currentPartitionSize; partitions.Add(currentTapePartition); currentPartition++; if (currentPartition < totalPartitions) { currentFile++; currentTapeFile = new TapeFileType { Image = new ImageType { format = "BINARY", offset = (long)currentSize, offsetSpecified = true, Value = outputPrefix + ".bin" }, Sequence = (long)currentFile, StartBlock = (long)currentBlock, BlockSize = blockSize }; currentFileSize = 0; fileChk = new Checksum(); files = new List <TapeFileType>(); currentTapePartition = new TapePartitionType { Image = new ImageType { format = "BINARY", offset = (long)currentSize, offsetSpecified = true, Value = outputPrefix + ".bin" }, Sequence = currentPartition, StartBlock = (long)currentBlock }; currentPartitionSize = 0; partitionChk = new Checksum(); DicConsole.WriteLine("Seeking to partition {0}", currentPartition); dev.Locate(out senseBuf, false, currentPartition, 0, dev.Timeout, out duration); totalDuration += duration; } continue; } #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 block {0} ({1:F3} MiB/sec.)", currentBlock, currentSpeed); sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout, out duration); totalDuration += duration; if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); if (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ == 0x00 && fxSense.Value.ILI && fxSense.Value.InformationValid) { blockSize = (uint)((int)blockSize - BitConverter.ToInt32(BitConverter.GetBytes(fxSense.Value.Information), 0)); currentTapeFile.BlockSize = blockSize; DicConsole.WriteLine(); DicConsole.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock); dumpLog.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock); sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration); totalDuration += duration; if (sense) { fxSense = Sense.DecodeFixed(senseBuf, out strSense); DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Drive could not go back one block. Sense follows..."); DicConsole.ErrorWriteLine("{0}", strSense); dumpFile.Close(); dumpLog.WriteLine("Drive could not go back one block. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } continue; } switch (fxSense.Value.SenseKey) { case SenseKeys.BlankCheck when currentBlock == 0: DicConsole.WriteLine(); DicConsole.ErrorWriteLine("Cannot dump a blank tape..."); dumpFile.Close(); dumpLog.WriteLine("Cannot dump a blank tape..."); return; // For sure this is an end-of-tape/partition case SenseKeys.BlankCheck when fxSense.Value.ASC == 0x00 && (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 || fxSense.Value.EOM): // TODO: Detect end of partition endOfMedia = true; dumpLog.WriteLine("Found end-of-tape/partition..."); continue; case SenseKeys.BlankCheck: DicConsole.WriteLine(); DicConsole.WriteLine("Blank block found, end of tape?"); endOfMedia = true; dumpLog.WriteLine("Blank block found, end of tape?..."); continue; } if ((fxSense.Value.SenseKey == SenseKeys.NoSense || fxSense.Value.SenseKey == SenseKeys.RecoveredError) && (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 || fxSense.Value.EOM)) { // TODO: Detect end of partition endOfMedia = true; dumpLog.WriteLine("Found end-of-tape/partition..."); continue; } if ((fxSense.Value.SenseKey == SenseKeys.NoSense || fxSense.Value.SenseKey == SenseKeys.RecoveredError) && (fxSense.Value.ASCQ == 0x01 || fxSense.Value.Filemark)) { currentTapeFile.Checksums = fileChk.End().ToArray(); currentTapeFile.EndBlock = (long)(currentBlock - 1); currentTapeFile.Size = (long)currentFileSize; files.Add(currentTapeFile); currentFile++; currentTapeFile = new TapeFileType { Image = new ImageType { format = "BINARY", offset = (long)currentSize, offsetSpecified = true, Value = outputPrefix + ".bin" }, Sequence = (long)currentFile, StartBlock = (long)currentBlock, BlockSize = blockSize }; currentFileSize = 0; fileChk = new Checksum(); DicConsole.WriteLine(); DicConsole.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock); dumpLog.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock); continue; } // TODO: Add error recovering for tapes fxSense = Sense.DecodeFixed(senseBuf, out strSense); DicConsole.ErrorWriteLine("Drive could not read block. Sense follows..."); DicConsole.ErrorWriteLine("{0} {1}", fxSense.Value.SenseKey, strSense); dumpLog.WriteLine("Drive could not read block. Sense follows..."); dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ); return; } mhddLog.Write(currentBlock, duration); ibgLog.Write(currentBlock, currentSpeed * 1024); dumpFile.Write(cmdBuf); DateTime chkStart = DateTime.UtcNow; dataChk.Update(cmdBuf); fileChk.Update(cmdBuf); partitionChk.Update(cmdBuf); DateTime chkEnd = DateTime.UtcNow; double chkDuration = (chkEnd - chkStart).TotalMilliseconds; totalChkDuration += chkDuration; if (currentBlock % 10 == 0) { double newSpeed = blockSize / (double)1048576 / (duration / 1000); if (!double.IsInfinity(newSpeed)) { currentSpeed = newSpeed; } } currentBlock++; currentSize += blockSize; currentFileSize += blockSize; currentPartitionSize += blockSize; } blocks = currentBlock + 1; DicConsole.WriteLine(); end = DateTime.UtcNow; mhddLog.Close(); ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (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)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000)); dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming).", (end - start).TotalSeconds, totalDuration / 1000, totalChkDuration / 1000); DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.", (double)blockSize * (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); sidecar.BlockMedia[0].Checksums = dataChk.End().ToArray(); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); CommonTypes.Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp); sidecar.BlockMedia[0].DiskType = xmlDskTyp; sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; // TODO: Implement device firmware revision sidecar.BlockMedia[0].Image = new ImageType { format = "Raw disk image (sector by sector copy)", Value = outputPrefix + ".bin" }; sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; sidecar.BlockMedia[0].Size = (long)currentSize; sidecar.BlockMedia[0].DumpHardwareArray = new DumpHardwareType[1]; sidecar.BlockMedia[0].DumpHardwareArray[0] = new DumpHardwareType { Extents = new ExtentType[1] }; sidecar.BlockMedia[0].DumpHardwareArray[0].Extents[0] = new ExtentType { Start = 0, End = blocks - 1 }; sidecar.BlockMedia[0].DumpHardwareArray[0].Manufacturer = dev.Manufacturer; sidecar.BlockMedia[0].DumpHardwareArray[0].Model = dev.Model; sidecar.BlockMedia[0].DumpHardwareArray[0].Revision = dev.Revision; sidecar.BlockMedia[0].DumpHardwareArray[0].Serial = dev.Serial; sidecar.BlockMedia[0].DumpHardwareArray[0].Software = Version.GetSoftwareType(); sidecar.BlockMedia[0].TapeInformation = partitions.ToArray(); if (!aborted) { 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(); } Statistics.AddMedia(dskType, true); }
internal static void DoDumpMedia(DumpMediaOptions options) { // TODO: Be able to cancel hashing Sidecar.InitProgressEvent += Progress.InitProgress; Sidecar.UpdateProgressEvent += Progress.UpdateProgress; Sidecar.EndProgressEvent += Progress.EndProgress; Sidecar.InitProgressEvent2 += Progress.InitProgress2; Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2; Sidecar.EndProgressEvent2 += Progress.EndProgress2; Sidecar.UpdateStatusEvent += Progress.UpdateStatus; DicConsole.DebugWriteLine("Dump-Media command", "--debug={0}", options.Debug); DicConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", options.Verbose); DicConsole.DebugWriteLine("Dump-Media command", "--device={0}", options.DevicePath); DicConsole.DebugWriteLine("Dump-Media command", "--raw={0}", options.Raw); DicConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", options.StopOnError); DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", options.Force); DicConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", options.RetryPasses); DicConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", options.Persistent); DicConsole.DebugWriteLine("Dump-Media command", "--resume={0}", options.Resume); DicConsole.DebugWriteLine("Dump-Media command", "--lead-in={0}", options.LeadIn); DicConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", options.EncodingName); DicConsole.DebugWriteLine("Dump-Media command", "--output={0}", options.OutputFile); DicConsole.DebugWriteLine("Dump-Media command", "--format={0}", options.OutputFormat); DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", options.Force); DicConsole.DebugWriteLine("Dump-Media command", "--options={0}", options.Options); DicConsole.DebugWriteLine("Dump-Media command", "--cicm-xml={0}", options.CicmXml); DicConsole.DebugWriteLine("Dump-Media command", "--skip={0}", options.Skip); DicConsole.DebugWriteLine("Dump-Media command", "--no-metadata={0}", options.NoMetadata); Dictionary <string, string> parsedOptions = Options.Parse(options.Options); DicConsole.DebugWriteLine("Dump-Media command", "Parsed options:"); foreach (KeyValuePair <string, string> parsedOption in parsedOptions) { DicConsole.DebugWriteLine("Dump-Media command", "{0} = {1}", parsedOption.Key, parsedOption.Value); } Encoding encoding = null; if (options.EncodingName != null) { try { encoding = Claunia.Encoding.Encoding.GetEncoding(options.EncodingName); if (options.Verbose) { DicConsole.VerboseWriteLine("Using encoding for {0}.", encoding.EncodingName); } } catch (ArgumentException) { DicConsole.ErrorWriteLine("Specified encoding is not supported."); return; } } if (options.DevicePath.Length == 2 && options.DevicePath[1] == ':' && options.DevicePath[0] != '/' && char.IsLetter(options.DevicePath[0])) { options.DevicePath = "\\\\.\\" + char.ToUpper(options.DevicePath[0]) + ':'; } Device dev = new Device(options.DevicePath); if (dev.Error) { DicConsole.ErrorWriteLine("Error {0} opening device.", dev.LastError); return; } Core.Statistics.AddDevice(dev); string outputPrefix = Path.Combine(Path.GetDirectoryName(options.OutputFile), Path.GetFileNameWithoutExtension(options.OutputFile)); Resume resume = null; XmlSerializer xs = new XmlSerializer(typeof(Resume)); if (File.Exists(outputPrefix + ".resume.xml") && options.Resume) { try { StreamReader sr = new StreamReader(outputPrefix + ".resume.xml"); resume = (Resume)xs.Deserialize(sr); sr.Close(); } catch { DicConsole.ErrorWriteLine("Incorrect resume file, not continuing..."); return; } } if (resume != null && resume.NextBlock > resume.LastBlock && resume.BadBlocks.Count == 0) { DicConsole.WriteLine("Media already dumped correctly, not continuing..."); return; } CICMMetadataType sidecar = null; XmlSerializer sidecarXs = new XmlSerializer(typeof(CICMMetadataType)); if (options.CicmXml != null) { if (File.Exists(options.CicmXml)) { try { StreamReader sr = new StreamReader(options.CicmXml); sidecar = (CICMMetadataType)sidecarXs.Deserialize(sr); sr.Close(); } catch { DicConsole.ErrorWriteLine("Incorrect metadata sidecar file, not continuing..."); return; } } else { DicConsole.ErrorWriteLine("Could not find metadata sidecar, not continuing..."); return; } } PluginBase plugins = new PluginBase(); List <IWritableImage> candidates = new List <IWritableImage>(); // Try extension if (string.IsNullOrEmpty(options.OutputFormat)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.KnownExtensions .Contains(Path.GetExtension(options .OutputFile)))); } // Try Id else if (Guid.TryParse(options.OutputFormat, out Guid outId)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); } // Try name else { candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, options.OutputFormat, StringComparison .InvariantCultureIgnoreCase))); } if (candidates.Count == 0) { DicConsole.WriteLine("No plugin supports requested extension."); return; } if (candidates.Count > 1) { DicConsole.WriteLine("More than one plugin supports requested extension."); return; } IWritableImage outputFormat = candidates[0]; DumpLog dumpLog = new DumpLog(outputPrefix + ".log", dev); if (options.Verbose) { dumpLog.WriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); DicConsole.VerboseWriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); } else { dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); DicConsole.WriteLine("Output image format: {0}.", outputFormat.Name); } switch (dev.Type) { case DeviceType.ATA: Ata.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding, outputPrefix, options.OutputFile, parsedOptions, sidecar, (uint)options.Skip, options.NoMetadata, options.NoTrim); break; case DeviceType.MMC: case DeviceType.SecureDigital: SecureDigital.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding, outputPrefix, options.OutputFile, parsedOptions, sidecar, (uint)options.Skip, options.NoMetadata, options.NoTrim); break; case DeviceType.NVMe: NvMe.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding, outputPrefix, options.OutputFile, parsedOptions, sidecar, (uint)options.Skip, options.NoMetadata, options.NoTrim); break; case DeviceType.ATAPI: case DeviceType.SCSI: Scsi.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, options.LeadIn, encoding, outputPrefix, options.OutputFile, parsedOptions, sidecar, (uint)options.Skip, options.NoMetadata, options.NoTrim); break; default: dumpLog.WriteLine("Unknown device type."); dumpLog.Close(); throw new NotSupportedException("Unknown device type."); } if (resume != null && options.Resume) { resume.LastWriteDate = DateTime.UtcNow; resume.BadBlocks.Sort(); if (File.Exists(outputPrefix + ".resume.xml")) { File.Delete(outputPrefix + ".resume.xml"); } FileStream fs = new FileStream(outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite); xs = new XmlSerializer(resume.GetType()); xs.Serialize(fs, resume); fs.Close(); } dumpLog.Close(); Core.Statistics.AddCommand("dump-media"); }
/// <summary> /// Dumps a MultiMediaCard or SecureDigital flash card /// </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 long or scrambled 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="outputPath">Path to output file</param> /// <param name="formatOptions">Formats to pass to output file plugin</param> /// <exception cref="ArgumentException">If you asked to dump long sectors from a SCSI Streaming device</exception> public static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, bool force, bool dumpRaw, bool persistent, bool stopOnError, 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 aborted; if (dumpRaw) { DicConsole.ErrorWriteLine("Raw dumping is not supported in MultiMediaCard or SecureDigital devices."); if (force) { DicConsole.ErrorWriteLine("Continuing..."); } else { DicConsole.ErrorWriteLine("Aborting..."); return; } } bool sense; const ushort SD_PROFILE = 0x0001; const uint TIMEOUT = 5; double duration; uint blocksToRead = 128; uint blockSize = 512; ulong blocks = 0; byte[] csd = null; byte[] ocr = null; byte[] ecsd = null; byte[] scr = null; int physicalBlockSize = 0; bool byteAddressed = true; Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); switch (dev.Type) { case DeviceType.MMC: { dumpLog.WriteLine("Reading Extended CSD"); sense = dev.ReadExtendedCsd(out ecsd, out _, TIMEOUT, out duration); if (!sense) { ExtendedCSD ecsdDecoded = Decoders.MMC.Decoders.DecodeExtendedCSD(ecsd); blocksToRead = ecsdDecoded.OptimalReadSize; blocks = ecsdDecoded.SectorCount; blockSize = (uint)(ecsdDecoded.SectorSize == 1 ? 4096 : 512); if (ecsdDecoded.NativeSectorSize == 0) { physicalBlockSize = 512; } else if (ecsdDecoded.NativeSectorSize == 1) { physicalBlockSize = 4096; } // Supposing it's high-capacity MMC if it has Extended CSD... byteAddressed = false; mediaTags.Add(MediaTagType.MMC_ExtendedCSD, null); } else { ecsd = null; } dumpLog.WriteLine("Reading CSD"); sense = dev.ReadCsd(out csd, out _, TIMEOUT, out duration); if (!sense) { if (blocks == 0) { CSD csdDecoded = Decoders.MMC.Decoders.DecodeCSD(csd); blocks = (ulong)((csdDecoded.Size + 1) * Math.Pow(2, csdDecoded.SizeMultiplier + 2)); blockSize = (uint)Math.Pow(2, csdDecoded.ReadBlockLength); } mediaTags.Add(MediaTagType.MMC_CSD, null); } else { csd = null; } dumpLog.WriteLine("Reading OCR"); sense = dev.ReadOcr(out ocr, out _, TIMEOUT, out duration); if (sense) { ocr = null; } else { mediaTags.Add(MediaTagType.MMC_OCR, null); } break; } case DeviceType.SecureDigital: { dumpLog.WriteLine("Reading CSD"); sense = dev.ReadCsd(out csd, out _, TIMEOUT, out duration); if (!sense) { Decoders.SecureDigital.CSD csdDecoded = Decoders.SecureDigital.Decoders.DecodeCSD(csd); blocks = (ulong)(csdDecoded.Structure == 0 ? (csdDecoded.Size + 1) * Math.Pow(2, csdDecoded.SizeMultiplier + 2) : (csdDecoded.Size + 1) * 1024); blockSize = (uint)Math.Pow(2, csdDecoded.ReadBlockLength); // Structure >=1 for SDHC/SDXC, so that's block addressed byteAddressed = csdDecoded.Structure == 0; mediaTags.Add(MediaTagType.SD_CSD, null); } else { csd = null; } dumpLog.WriteLine("Reading OCR"); sense = dev.ReadSdocr(out ocr, out _, TIMEOUT, out duration); if (sense) { ocr = null; } else { mediaTags.Add(MediaTagType.SD_OCR, null); } dumpLog.WriteLine("Reading SCR"); sense = dev.ReadScr(out scr, out _, TIMEOUT, out duration); if (sense) { scr = null; } else { mediaTags.Add(MediaTagType.SD_SCR, null); } break; } } dumpLog.WriteLine("Reading CID"); sense = dev.ReadCid(out byte[] cid, out _, TIMEOUT, out duration); if (sense) { cid = null; } else { mediaTags.Add(dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_CID : MediaTagType.MMC_CID, null); } DateTime start; DateTime end; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; if (blocks == 0) { dumpLog.WriteLine("Cannot get device size."); DicConsole.ErrorWriteLine("Unable to get device size."); return; } dumpLog.WriteLine("Device reports {0} blocks.", blocks); byte[] cmdBuf; bool error; while (true) { error = dev.Read(out cmdBuf, out _, 0, blockSize, blocksToRead, byteAddressed, TIMEOUT, out duration); if (error) { blocksToRead /= 2; } if (!error || blocksToRead == 1) { break; } } if (error) { dumpLog.WriteLine("ERROR: Cannot get blocks to read, device error {0}.", dev.LastError); DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError); return; } dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead); if (skip < blocksToRead) { skip = blocksToRead; } DumpHardwareType currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(true, false, blocks, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId, ref resume, ref currentTry, ref extents); if (currentTry == null || extents == null) { throw new InvalidOperationException("Could not process resume file, not continuing..."); } 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; } } DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead); IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", SD_PROFILE); ret = outputPlugin.Create(outputPath, dev.Type == DeviceType.SecureDigital ? MediaType.SecureDigital : MediaType.MMC, formatOptions, blocks, blockSize); // 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; } if (resume.NextBlock > 0) { dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); } start = DateTime.UtcNow; double imageWriteDuration = 0; bool newTrim = false; for (ulong i = resume.NextBlock; i < blocks; i += blocksToRead) { if (aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); dumpLog.WriteLine("Aborted!"); break; } if (blocks - i < blocksToRead) { blocksToRead = (byte)(blocks - 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, blocks, currentSpeed); error = dev.Read(out cmdBuf, out _, (uint)i, blockSize, blocksToRead, byteAddressed, TIMEOUT, out duration); if (!error) { mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); DateTime writeStart = DateTime.Now; outputPlugin.WriteSectors(cmdBuf, i, blocksToRead); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; extents.Add(i, blocksToRead, true); } else { if (i + skip > blocks) { skip = (uint)(blocks - i); } for (ulong b = i; b < i + skip; b++) { resume.BadBlocks.Add(b); } mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); DateTime writeStart = DateTime.Now; outputPlugin.WriteSectors(new byte[blockSize * skip], i, skip); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i); i += skip - blocksToRead; newTrim = true; } double newSpeed = (double)blockSize * blocksToRead / 1048576 / (duration / 1000); if (!double.IsInfinity(newSpeed)) { currentSpeed = newSpeed; } resume.NextBlock = i + blocksToRead; } end = DateTime.Now; DicConsole.WriteLine(); mhddLog.Close(); ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (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)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000)); dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.", (double)blockSize * (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); error = dev.Read(out cmdBuf, out _, (uint)badSector, blockSize, 1, byteAddressed, TIMEOUT, out duration); totalDuration += duration; if (error) { continue; } resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputPlugin.WriteSector(cmdBuf, 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) { int pass = 1; bool forward = true; bool runningPersistent = false; repeatRetryLba: 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, " : ""); error = dev.Read(out cmdBuf, out _, (uint)badSector, blockSize, 1, byteAddressed, TIMEOUT, out duration); totalDuration += duration; if (!error) { resume.BadBlocks.Remove(badSector); extents.Add(badSector); outputPlugin.WriteSector(cmdBuf, badSector); dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (runningPersistent) { outputPlugin.WriteSector(cmdBuf, badSector); } } if (pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) { pass++; forward = !forward; resume.BadBlocks.Sort(); resume.BadBlocks.Reverse(); goto repeatRetryLba; } DicConsole.WriteLine(); } #endregion Error handling 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); if (preSidecar != null) { preSidecar.BlockMedia = sidecar.BlockMedia; sidecar = preSidecar; } switch (dev.Type) { case DeviceType.MMC: sidecar.BlockMedia[0].MultiMediaCard = new MultiMediaCardType(); break; case DeviceType.SecureDigital: sidecar.BlockMedia[0].SecureDigital = new SecureDigitalType(); break; } DumpType cidDump = null; DumpType csdDump = null; DumpType ocrDump = null; if (cid != null) { cidDump = new DumpType { Image = outputPath, Size = cid.Length, Checksums = Checksum.GetChecksums(cid).ToArray() }; ret = outputPlugin.WriteMediaTag(cid, dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_CID : MediaTagType.MMC_CID); // Cannot write CID to image if (!ret && !force) { dumpLog.WriteLine("Cannot write CID to output image."); throw new ArgumentException(outputPlugin.ErrorMessage); } } if (csd != null) { csdDump = new DumpType { Image = outputPath, Size = csd.Length, Checksums = Checksum.GetChecksums(csd).ToArray() }; ret = outputPlugin.WriteMediaTag(csd, dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_CSD : MediaTagType.MMC_CSD); // Cannot write CSD to image if (!ret && !force) { dumpLog.WriteLine("Cannot write CSD to output image."); throw new ArgumentException(outputPlugin.ErrorMessage); } } if (ecsd != null) { sidecar.BlockMedia[0].MultiMediaCard.ExtendedCSD = new DumpType { Image = outputPath, Size = ecsd.Length, Checksums = Checksum.GetChecksums(ecsd).ToArray() }; ret = outputPlugin.WriteMediaTag(ecsd, MediaTagType.MMC_ExtendedCSD); // Cannot write Extended CSD to image if (!ret && !force) { dumpLog.WriteLine("Cannot write Extended CSD to output image."); throw new ArgumentException(outputPlugin.ErrorMessage); } } if (ocr != null) { ocrDump = new DumpType { Image = outputPath, Size = ocr.Length, Checksums = Checksum.GetChecksums(ocr).ToArray() }; ret = outputPlugin.WriteMediaTag(ocr, dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_OCR : MediaTagType.MMC_OCR); // Cannot write OCR to image if (!ret && !force) { dumpLog.WriteLine("Cannot write OCR to output image."); throw new ArgumentException(outputPlugin.ErrorMessage); } } if (scr != null) { sidecar.BlockMedia[0].SecureDigital.SCR = new DumpType { Image = outputPath, Size = scr.Length, Checksums = Checksum.GetChecksums(scr).ToArray() }; ret = outputPlugin.WriteMediaTag(scr, MediaTagType.SD_SCR); // Cannot write SCR to image if (!ret && !force) { dumpLog.WriteLine("Cannot write SCR to output image."); throw new ArgumentException(outputPlugin.ErrorMessage); } } switch (dev.Type) { case DeviceType.MMC: sidecar.BlockMedia[0].MultiMediaCard.CID = cidDump; sidecar.BlockMedia[0].MultiMediaCard.CSD = csdDump; sidecar.BlockMedia[0].MultiMediaCard.OCR = ocrDump; break; case DeviceType.SecureDigital: sidecar.BlockMedia[0].SecureDigital.CID = cidDump; sidecar.BlockMedia[0].SecureDigital.CSD = csdDump; sidecar.BlockMedia[0].SecureDigital.OCR = ocrDump; break; } end = DateTime.UtcNow; totalChkDuration = (end - chkStart).TotalMilliseconds; dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds); dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); string xmlDskTyp = null, xmlDskSubTyp = null; switch (dev.Type) { case DeviceType.MMC: CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.MMC, out xmlDskTyp, out xmlDskSubTyp); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.MMC); break; case DeviceType.SecureDigital: CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.SecureDigital, out xmlDskTyp, out xmlDskSubTyp); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.SecureDigital); break; } sidecar.BlockMedia[0].DiskType = xmlDskTyp; sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; // TODO: Implement device firmware revision sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; sidecar.BlockMedia[0].PhysicalBlockSize = physicalBlockSize > 0 ? physicalBlockSize : (int)blockSize; sidecar.BlockMedia[0].LogicalBlockSize = (int)blockSize; sidecar.BlockMedia[0].Manufacturer = dev.Manufacturer; sidecar.BlockMedia[0].Model = dev.Model; sidecar.BlockMedia[0].Serial = dev.Serial; sidecar.BlockMedia[0].Size = (long)(blocks * blockSize); 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)blockSize * (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); if (resume.BadBlocks.Count > 0) { resume.BadBlocks.Sort(); } DicConsole.WriteLine(); switch (dev.Type) { case DeviceType.MMC: Statistics.AddMedia(MediaType.MMC, true); break; case DeviceType.SecureDigital: Statistics.AddMedia(MediaType.SecureDigital, true); break; } }
/// <summary>Starts dumping with the stablished fields and autodetecting the device type</summary> public void Start() { // Open master database _ctx = DicContext.Create(Settings.Settings.MasterDbPath); // Search for device in master database _dbDev = _ctx.Devices.FirstOrDefault(d => d.Manufacturer == _dev.Manufacturer && d.Model == _dev.Model && d.Revision == _dev.Revision); if (_dbDev is null) { _dumpLog.WriteLine("Device not in database, please create a device report and attach it to a Github issue."); UpdateStatus?. Invoke("Device not in database, please create a device report and attach it to a Github issue."); } else { _dumpLog.WriteLine($"Device in database since {_dbDev.LastSynchronized}."); UpdateStatus?.Invoke($"Device in database since {_dbDev.LastSynchronized}."); if (_dbDev.OptimalMultipleSectorsRead > 0) { _maximumReadable = (uint)_dbDev.OptimalMultipleSectorsRead; } } if (_dev.IsUsb && _dev.UsbVendorId == 0x054C && (_dev.UsbProductId == 0x01C8 || _dev.UsbProductId == 0x01C9 || _dev.UsbProductId == 0x02D2)) { PlayStationPortable(); } else { switch (_dev.Type) { case DeviceType.ATA: Ata(); break; case DeviceType.MMC: case DeviceType.SecureDigital: SecureDigital(); break; case DeviceType.NVMe: NVMe(); break; case DeviceType.ATAPI: case DeviceType.SCSI: Scsi(); break; default: _dumpLog.WriteLine("Unknown device type."); _dumpLog.Close(); StoppingErrorMessage?.Invoke("Unknown device type."); return; } } _dumpLog.Close(); if (_resume == null || !_doResume) { return; } _resume.LastWriteDate = DateTime.UtcNow; _resume.BadBlocks.Sort(); if (File.Exists(_outputPrefix + ".resume.xml")) { File.Delete(_outputPrefix + ".resume.xml"); } var fs = new FileStream(_outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite); var xs = new XmlSerializer(_resume.GetType()); xs.Serialize(fs, _resume); fs.Close(); }
public override int Invoke(IEnumerable <string> arguments) { List <string> extra = Options.Parse(arguments); if (showHelp) { Options.WriteOptionDescriptions(CommandSet.Out); return((int)ErrorNumber.HelpRequested); } MainClass.PrintCopyright(); if (MainClass.Debug) { DicConsole.DebugWriteLineEvent += System.Console.Error.WriteLine; } if (MainClass.Verbose) { DicConsole.VerboseWriteLineEvent += System.Console.WriteLine; } Statistics.AddCommand("dump-media"); if (extra.Count > 2) { DicConsole.ErrorWriteLine("Too many arguments."); return((int)ErrorNumber.UnexpectedArgumentCount); } if (extra.Count <= 1) { DicConsole.ErrorWriteLine("Missing paths."); return((int)ErrorNumber.MissingArgument); } devicePath = extra[0]; outputFile = extra[1]; DicConsole.DebugWriteLine("Dump-Media command", "--cicm-xml={0}", cicmXml); DicConsole.DebugWriteLine("Dump-Media command", "--debug={0}", MainClass.Debug); DicConsole.DebugWriteLine("Dump-Media command", "--device={0}", devicePath); DicConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", encodingName); DicConsole.DebugWriteLine("Dump-Media command", "--first-pregap={0}", firstTrackPregap); DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", force); DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", force); DicConsole.DebugWriteLine("Dump-Media command", "--format={0}", wantedOutputFormat); DicConsole.DebugWriteLine("Dump-Media command", "--no-metadata={0}", noMetadata); DicConsole.DebugWriteLine("Dump-Media command", "--options={0}", Options); DicConsole.DebugWriteLine("Dump-Media command", "--output={0}", outputFile); DicConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", persistent); // TODO: Disabled temporarily //DicConsole.DebugWriteLine("Dump-Media command", "--raw={0}", raw); DicConsole.DebugWriteLine("Dump-Media command", "--resume={0}", doResume); DicConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", retryPasses); DicConsole.DebugWriteLine("Dump-Media command", "--skip={0}", skip); DicConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", stopOnError); DicConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", MainClass.Verbose); Dictionary <string, string> parsedOptions = Core.Options.Parse(outputOptions); DicConsole.DebugWriteLine("Dump-Media command", "Parsed options:"); foreach (KeyValuePair <string, string> parsedOption in parsedOptions) { DicConsole.DebugWriteLine("Dump-Media command", "{0} = {1}", parsedOption.Key, parsedOption.Value); } Encoding encoding = null; if (encodingName != null) { try { encoding = Claunia.Encoding.Encoding.GetEncoding(encodingName); if (MainClass.Verbose) { DicConsole.VerboseWriteLine("Using encoding for {0}.", encoding.EncodingName); } } catch (ArgumentException) { DicConsole.ErrorWriteLine("Specified encoding is not supported."); return((int)ErrorNumber.EncodingUnknown); } } if (devicePath.Length == 2 && devicePath[1] == ':' && devicePath[0] != '/' && char.IsLetter(devicePath[0])) { devicePath = "\\\\.\\" + char.ToUpper(devicePath[0]) + ':'; } Device dev; try { dev = new Device(devicePath); if (dev.IsRemote) { Statistics.AddRemote(dev.RemoteApplication, dev.RemoteVersion, dev.RemoteOperatingSystem, dev.RemoteOperatingSystemVersion, dev.RemoteArchitecture); } if (dev.Error) { DicConsole.ErrorWriteLine(Error.Print(dev.LastError)); return((int)ErrorNumber.CannotOpenDevice); } } catch (DeviceException e) { DicConsole.ErrorWriteLine(e.Message ?? Error.Print(e.LastError)); return((int)ErrorNumber.CannotOpenDevice); } Statistics.AddDevice(dev); string outputPrefix = Path.Combine(Path.GetDirectoryName(outputFile), Path.GetFileNameWithoutExtension(outputFile)); Resume resume = null; var xs = new XmlSerializer(typeof(Resume)); if (File.Exists(outputPrefix + ".resume.xml") && doResume) { try { var sr = new StreamReader(outputPrefix + ".resume.xml"); resume = (Resume)xs.Deserialize(sr); sr.Close(); } catch { DicConsole.ErrorWriteLine("Incorrect resume file, not continuing..."); return((int)ErrorNumber.InvalidResume); } } if (resume != null && resume.NextBlock > resume.LastBlock && resume.BadBlocks.Count == 0 && !resume.Tape) { DicConsole.WriteLine("Media already dumped correctly, not continuing..."); return((int)ErrorNumber.AlreadyDumped); } CICMMetadataType sidecar = null; var sidecarXs = new XmlSerializer(typeof(CICMMetadataType)); if (cicmXml != null) { if (File.Exists(cicmXml)) { try { var sr = new StreamReader(cicmXml); sidecar = (CICMMetadataType)sidecarXs.Deserialize(sr); sr.Close(); } catch { DicConsole.ErrorWriteLine("Incorrect metadata sidecar file, not continuing..."); return((int)ErrorNumber.InvalidSidecar); } } else { DicConsole.ErrorWriteLine("Could not find metadata sidecar, not continuing..."); return((int)ErrorNumber.FileNotFound); } } PluginBase plugins = GetPluginBase.Instance; List <IWritableImage> candidates = new List <IWritableImage>(); // Try extension if (string.IsNullOrEmpty(wantedOutputFormat)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.KnownExtensions. Contains(Path.GetExtension(outputFile)))); } // Try Id else if (Guid.TryParse(wantedOutputFormat, out Guid outId)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); } // Try name else { candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, wantedOutputFormat, StringComparison. InvariantCultureIgnoreCase))); } if (candidates.Count == 0) { DicConsole.WriteLine("No plugin supports requested extension."); return((int)ErrorNumber.FormatNotFound); } if (candidates.Count > 1) { DicConsole.WriteLine("More than one plugin supports requested extension."); return((int)ErrorNumber.TooManyFormats); } IWritableImage outputFormat = candidates[0]; var dumpLog = new DumpLog(outputPrefix + ".log", dev); if (MainClass.Verbose) { dumpLog.WriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); DicConsole.VerboseWriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); } else { dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); DicConsole.WriteLine("Output image format: {0}.", outputFormat.Name); } var dumper = new Dump(doResume, dev, devicePath, outputFormat, retryPasses, force, false, persistent, stopOnError, resume, dumpLog, encoding, outputPrefix, outputFile, parsedOptions, sidecar, (uint)skip, noMetadata, noTrim, firstTrackPregap); dumper.UpdateStatus += Progress.UpdateStatus; dumper.ErrorMessage += Progress.ErrorMessage; dumper.StoppingErrorMessage += Progress.ErrorMessage; dumper.UpdateProgress += Progress.UpdateProgress; dumper.PulseProgress += Progress.PulseProgress; dumper.InitProgress += Progress.InitProgress; dumper.EndProgress += Progress.EndProgress; dumper.InitProgress2 += Progress.InitProgress2; dumper.EndProgress2 += Progress.EndProgress2; dumper.UpdateProgress2 += Progress.UpdateProgress2; System.Console.CancelKeyPress += (sender, e) => { e.Cancel = true; dumper.Abort(); }; dumper.Start(); dev.Close(); return((int)ErrorNumber.NoError); }
public static int Invoke(bool debug, bool verbose, string cicmXml, string devicePath, bool resume, string encoding, bool firstPregap, bool fixOffset, bool force, bool metadata, bool trim, string outputPath, string options, bool persistent, ushort retryPasses, uint skip, byte speed, bool stopOnError, string format, string subchannel, bool @private, bool fixSubchannelPosition, bool retrySubchannel, bool fixSubchannel, bool fixSubchannelCrc, bool generateSubchannels, bool skipCdiReadyHole, bool eject) { MainClass.PrintCopyright(); if (debug) { AaruConsole.DebugWriteLineEvent += System.Console.Error.WriteLine; } if (verbose) { AaruConsole.VerboseWriteLineEvent += System.Console.WriteLine; } if (fixSubchannelCrc) { fixSubchannel = true; } if (retrySubchannel || fixSubchannel) { fixSubchannelPosition = true; } Statistics.AddCommand("dump-media"); AaruConsole.DebugWriteLine("Dump-Media command", "--cicm-xml={0}", cicmXml); AaruConsole.DebugWriteLine("Dump-Media command", "--debug={0}", debug); AaruConsole.DebugWriteLine("Dump-Media command", "--device={0}", devicePath); AaruConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", encoding); AaruConsole.DebugWriteLine("Dump-Media command", "--first-pregap={0}", firstPregap); AaruConsole.DebugWriteLine("Dump-Media command", "--fix-offset={0}", fixOffset); AaruConsole.DebugWriteLine("Dump-Media command", "--force={0}", force); AaruConsole.DebugWriteLine("Dump-Media command", "--format={0}", format); AaruConsole.DebugWriteLine("Dump-Media command", "--metadata={0}", metadata); AaruConsole.DebugWriteLine("Dump-Media command", "--options={0}", options); AaruConsole.DebugWriteLine("Dump-Media command", "--output={0}", outputPath); AaruConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", persistent); AaruConsole.DebugWriteLine("Dump-Media command", "--resume={0}", resume); AaruConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", retryPasses); AaruConsole.DebugWriteLine("Dump-Media command", "--skip={0}", skip); AaruConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", stopOnError); AaruConsole.DebugWriteLine("Dump-Media command", "--trim={0}", trim); AaruConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", verbose); AaruConsole.DebugWriteLine("Dump-Media command", "--subchannel={0}", subchannel); AaruConsole.DebugWriteLine("Dump-Media command", "--private={0}", @private); AaruConsole.DebugWriteLine("Dump-Media command", "--fix-subchannel-position={0}", fixSubchannelPosition); AaruConsole.DebugWriteLine("Dump-Media command", "--retry-subchannel={0}", retrySubchannel); AaruConsole.DebugWriteLine("Dump-Media command", "--fix-subchannel={0}", fixSubchannel); AaruConsole.DebugWriteLine("Dump-Media command", "--fix-subchannel-crc={0}", fixSubchannelCrc); AaruConsole.DebugWriteLine("Dump-Media command", "--generate-subchannels={0}", generateSubchannels); AaruConsole.DebugWriteLine("Dump-Media command", "--skip-cdiready-hole={0}", skipCdiReadyHole); AaruConsole.DebugWriteLine("Dump-Media command", "--eject={0}", eject); // TODO: Disabled temporarily //AaruConsole.DebugWriteLine("Dump-Media command", "--raw={0}", raw); Dictionary <string, string> parsedOptions = Core.Options.Parse(options); AaruConsole.DebugWriteLine("Dump-Media command", "Parsed options:"); foreach (KeyValuePair <string, string> parsedOption in parsedOptions) { AaruConsole.DebugWriteLine("Dump-Media command", "{0} = {1}", parsedOption.Key, parsedOption.Value); } Encoding encodingClass = null; if (encoding != null) { try { encodingClass = Claunia.Encoding.Encoding.GetEncoding(encoding); if (verbose) { AaruConsole.VerboseWriteLine("Using encoding for {0}.", encodingClass.EncodingName); } } catch (ArgumentException) { AaruConsole.ErrorWriteLine("Specified encoding is not supported."); return((int)ErrorNumber.EncodingUnknown); } } DumpSubchannel wantedSubchannel = DumpSubchannel.Any; switch (subchannel?.ToLowerInvariant()) { case "any": case null: wantedSubchannel = DumpSubchannel.Any; break; case "rw": wantedSubchannel = DumpSubchannel.Rw; break; case "rw-or-pq": wantedSubchannel = DumpSubchannel.RwOrPq; break; case "pq": wantedSubchannel = DumpSubchannel.Pq; break; case "none": wantedSubchannel = DumpSubchannel.None; break; default: AaruConsole.WriteLine("Incorrect subchannel type \"{0}\" requested.", subchannel); break; } string filename = Path.GetFileNameWithoutExtension(outputPath); bool isResponse = filename.StartsWith("#", StringComparison.OrdinalIgnoreCase) && File.Exists(Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath))); TextReader resReader; if (isResponse) { resReader = new StreamReader(Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath))); } else { resReader = new StringReader(Path.GetFileNameWithoutExtension(outputPath)); } if (isResponse) { eject = true; } PluginBase plugins = GetPluginBase.Instance; List <IWritableImage> candidates = new List <IWritableImage>(); string extension = Path.GetExtension(outputPath); // Try extension if (string.IsNullOrEmpty(format)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.KnownExtensions.Contains(extension))); } // Try Id else if (Guid.TryParse(format, out Guid outId)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); } // Try name else { candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, format, StringComparison. InvariantCultureIgnoreCase))); } if (candidates.Count == 0) { AaruConsole.WriteLine("No plugin supports requested extension."); return((int)ErrorNumber.FormatNotFound); } if (candidates.Count > 1) { AaruConsole.WriteLine("More than one plugin supports requested extension."); return((int)ErrorNumber.TooManyFormats); } while (true) { string responseLine = resReader.ReadLine(); if (responseLine is null) { break; } if (responseLine.Any(c => c < 0x20)) { AaruConsole.ErrorWriteLine("Invalid characters found in list of files, exiting..."); return((int)ErrorNumber.InvalidArgument); } if (isResponse) { AaruConsole.WriteLine("Please insert media with title {0} and press any key to continue...", responseLine); System.Console.ReadKey(); Thread.Sleep(1000); } responseLine = responseLine.Replace('/', '/'); // Replace Windows forbidden filename characters with Japanese equivalents that are visually the same, but bigger. if (DetectOS.IsWindows) { responseLine = responseLine.Replace('<', '\uFF1C').Replace('>', '\uFF1E').Replace(':', '\uFF1A'). Replace('"', '\u2033').Replace('\\', '\').Replace('|', '|'). Replace('?', '?').Replace('*', '*'); } if (devicePath.Length == 2 && devicePath[1] == ':' && devicePath[0] != '/' && char.IsLetter(devicePath[0])) { devicePath = "\\\\.\\" + char.ToUpper(devicePath[0]) + ':'; } Devices.Device dev; try { dev = new Devices.Device(devicePath); if (dev.IsRemote) { Statistics.AddRemote(dev.RemoteApplication, dev.RemoteVersion, dev.RemoteOperatingSystem, dev.RemoteOperatingSystemVersion, dev.RemoteArchitecture); } if (dev.Error) { AaruConsole.ErrorWriteLine(Error.Print(dev.LastError)); if (isResponse) { continue; } return((int)ErrorNumber.CannotOpenDevice); } } catch (DeviceException e) { AaruConsole.ErrorWriteLine(e.Message ?? Error.Print(e.LastError)); if (isResponse) { continue; } return((int)ErrorNumber.CannotOpenDevice); } Statistics.AddDevice(dev); string outputPrefix = Path.Combine(Path.GetDirectoryName(outputPath), responseLine); Resume resumeClass = null; var xs = new XmlSerializer(typeof(Resume)); if (File.Exists(outputPrefix + ".resume.xml") && resume) { try { var sr = new StreamReader(outputPrefix + ".resume.xml"); resumeClass = (Resume)xs.Deserialize(sr); sr.Close(); } catch { AaruConsole.ErrorWriteLine("Incorrect resume file, not continuing..."); if (isResponse) { continue; } return((int)ErrorNumber.InvalidResume); } } if (resumeClass != null && resumeClass.NextBlock > resumeClass.LastBlock && resumeClass.BadBlocks.Count == 0 && !resumeClass.Tape && (resumeClass.BadSubchannels is null || resumeClass.BadSubchannels.Count == 0)) { AaruConsole.WriteLine("Media already dumped correctly, not continuing..."); if (isResponse) { continue; } return((int)ErrorNumber.AlreadyDumped); } CICMMetadataType sidecar = null; var sidecarXs = new XmlSerializer(typeof(CICMMetadataType)); if (cicmXml != null) { if (File.Exists(cicmXml)) { try { var sr = new StreamReader(cicmXml); sidecar = (CICMMetadataType)sidecarXs.Deserialize(sr); sr.Close(); } catch { AaruConsole.ErrorWriteLine("Incorrect metadata sidecar file, not continuing..."); if (isResponse) { continue; } return((int)ErrorNumber.InvalidSidecar); } } else { AaruConsole.ErrorWriteLine("Could not find metadata sidecar, not continuing..."); if (isResponse) { continue; } return((int)ErrorNumber.FileNotFound); } } plugins = GetPluginBase.Instance; candidates = new List <IWritableImage>(); // Try extension if (string.IsNullOrEmpty(format)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.KnownExtensions. Contains(Path. GetExtension(outputPath)))); } // Try Id else if (Guid.TryParse(format, out Guid outId)) { candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); } // Try name else { candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, format, StringComparison. InvariantCultureIgnoreCase))); } IWritableImage outputFormat = candidates[0]; var dumpLog = new DumpLog(outputPrefix + ".log", dev, @private); if (verbose) { dumpLog.WriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); AaruConsole.VerboseWriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); } else { dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); AaruConsole.WriteLine("Output image format: {0}.", outputFormat.Name); } var errorLog = new ErrorLog(outputPrefix + ".error.log"); var dumper = new Dump(resume, dev, devicePath, outputFormat, retryPasses, force, false, persistent, stopOnError, resumeClass, dumpLog, encodingClass, outputPrefix, outputPrefix + extension, parsedOptions, sidecar, skip, metadata, trim, firstPregap, fixOffset, debug, wantedSubchannel, speed, @private, fixSubchannelPosition, retrySubchannel, fixSubchannel, fixSubchannelCrc, skipCdiReadyHole, errorLog, generateSubchannels); dumper.UpdateStatus += Progress.UpdateStatus; dumper.ErrorMessage += Progress.ErrorMessage; dumper.StoppingErrorMessage += Progress.ErrorMessage; dumper.UpdateProgress += Progress.UpdateProgress; dumper.PulseProgress += Progress.PulseProgress; dumper.InitProgress += Progress.InitProgress; dumper.EndProgress += Progress.EndProgress; dumper.InitProgress2 += Progress.InitProgress2; dumper.EndProgress2 += Progress.EndProgress2; dumper.UpdateProgress2 += Progress.UpdateProgress2; System.Console.CancelKeyPress += (sender, e) => { e.Cancel = true; dumper.Abort(); }; dumper.Start(); if (eject && dev.IsRemovable) { switch (dev.Type) { case DeviceType.ATA: dev.DoorUnlock(out _, dev.Timeout, out _); dev.MediaEject(out _, dev.Timeout, out _); break; case DeviceType.ATAPI: case DeviceType.SCSI: switch (dev.ScsiType) { case PeripheralDeviceTypes.DirectAccess: case PeripheralDeviceTypes.SimplifiedDevice: case PeripheralDeviceTypes.SCSIZonedBlockDevice: case PeripheralDeviceTypes.WriteOnceDevice: case PeripheralDeviceTypes.OpticalDevice: case PeripheralDeviceTypes.OCRWDevice: dev.SpcAllowMediumRemoval(out _, dev.Timeout, out _); dev.EjectTray(out _, dev.Timeout, out _); break; case PeripheralDeviceTypes.MultiMediaDevice: dev.AllowMediumRemoval(out _, dev.Timeout, out _); dev.EjectTray(out _, dev.Timeout, out _); break; case PeripheralDeviceTypes.SequentialAccess: dev.SpcAllowMediumRemoval(out _, dev.Timeout, out _); dev.LoadUnload(out _, true, false, false, false, false, dev.Timeout, out _); break; } break; } } dev.Close(); } return((int)ErrorNumber.NoError); }
/// <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); }
public static void SolveTrackPregaps(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, Track[] tracks, bool supportsPqSubchannel, bool supportsRwSubchannel, Database.Models.Device dbDev, out bool inexactPositioning) { bool sense = true; // Sense indicator byte[] subBuf = null; int posQ; uint retries; bool? bcd = null; byte[] crc; Dictionary<uint, int> pregaps = new Dictionary<uint, int>(); inexactPositioning = false; if(!supportsPqSubchannel && !supportsRwSubchannel) return; // Check if subchannel is BCD for(retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, 11, dbDev, out subBuf, false) : GetSectorForPregapQ16(dev, 11, dbDev, out subBuf, false); if(sense) continue; bcd = (subBuf[9] & 0x10) > 0; break; } AaruConsole.DebugWriteLine("Pregap calculator", bcd == true ? "Subchannel is BCD" : bcd == false ? "Subchannel is not BCD" : "Could not detect drive subchannel BCD"); if(bcd is null) { dumpLog?.WriteLine("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); updateStatus?. Invoke("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); return; } // Initialize the dictionary for(int i = 0; i < tracks.Length; i++) pregaps[tracks[i].TrackSequence] = 0; for(int t = 0; t < tracks.Length; t++) { Track track = tracks[t]; int trackRetries = 0; // First track of each session has at least 150 sectors of pregap and is not readable always if(tracks.Where(t => t.TrackSession == track.TrackSession).OrderBy(t => t.TrackSequence). FirstOrDefault().TrackSequence == track.TrackSequence) { AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence); if(track.TrackSequence > 1) pregaps[track.TrackSequence] = 150; continue; } if(t > 0 && tracks[t - 1].TrackType == tracks[t].TrackType) { AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence); continue; } AaruConsole.DebugWriteLine("Pregap calculator", "Track {0}", track.TrackSequence); int lba = (int)track.TrackStartSector - 1; bool pregapFound = false; Track previousTrack = tracks.FirstOrDefault(t => t.TrackSequence == track.TrackSequence - 1); bool goneBack = false; bool goFront = false; bool forward = false; bool crcOk = false; bool previousPregapIsPreviousTrack = false; // Check if pregap is 0 for(retries = 0; retries < 10 && !pregapFound; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio); if(sense) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}", lba, retries + 1, sense); continue; } if(bcd == false) BinaryToBcdQ(subBuf); CRC16CCITTContext.Data(subBuf, 10, out crc); AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; // Try to do a simple correction if(!crcOk) { // Data track cannot have 11xxb in CONTROL if((subBuf[0] & 0x40) > 0) subBuf[0] &= 0x7F; // ADR only uses two bits subBuf[0] &= 0xF3; // Don't care about other Q modes if((subBuf[0] & 0xF) == 1) { // ZERO only used in DDCD subBuf[6] = 0; // Fix BCD numbering for(int i = 1; i < 10; i++) { if((subBuf[i] & 0xF0) > 0xA0) subBuf[i] &= 0x7F; if((subBuf[i] & 0x0F) > 0x0A) subBuf[i] &= 0xF7; } } CRC16CCITTContext.Data(subBuf, 10, out crc); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; if(crcOk) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); } else continue; } BcdToBinaryQ(subBuf); // Q position if((subBuf[0] & 0xF) != 1) continue; posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; if(subBuf[1] != track.TrackSequence - 1 || subBuf[2] == 0 || posQ != lba) break; pregaps[track.TrackSequence] = 0; pregapFound = true; } if(pregapFound) continue; // Calculate pregap lba = (int)track.TrackStartSector - 150; while(lba > (int)previousTrack.TrackStartSector && lba <= (int)track.TrackStartSector) { // Some drives crash if you try to read just before the previous read, so seek away first if(!forward) sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba - 10, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba - 10, dbDev, out subBuf, track.TrackType == TrackType.Audio); for(retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio); if(sense) continue; if(bcd == false) BinaryToBcdQ(subBuf); CRC16CCITTContext.Data(subBuf, 10, out crc); AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; // Try to do a simple correction if(!crcOk) { // Data track cannot have 11xxb in CONTROL if((subBuf[0] & 0x40) > 0) subBuf[0] &= 0x7F; // ADR only uses two bits subBuf[0] &= 0xF3; // Don't care about other Q modes if((subBuf[0] & 0xF) == 1) { // ZERO only used in DDCD subBuf[6] = 0; // Fix BCD numbering for(int i = 1; i < 10; i++) { if((subBuf[i] & 0xF0) > 0xA0) subBuf[i] &= 0x7F; if((subBuf[i] & 0x0F) > 0x0A) subBuf[i] &= 0xF7; } } CRC16CCITTContext.Data(subBuf, 10, out crc); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; if(crcOk) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); break; } } if(crcOk) break; } if(retries == 10) { if(sense) { trackRetries++; if(trackRetries >= 10) { if(pregaps[track.TrackSequence] == 0) { if((previousTrack.TrackType == TrackType.Audio && track.TrackType != TrackType.Audio) || (previousTrack.TrackType != TrackType.Audio && track.TrackType == TrackType.Audio)) { dumpLog?. WriteLine("Could not read subchannel for this track, supposing 150 sectors."); updateStatus?. Invoke("Could not read subchannel for this track, supposing 150 sectors."); } else { dumpLog?. WriteLine("Could not read subchannel for this track, supposing 0 sectors."); updateStatus?. Invoke("Could not read subchannel for this track, supposing 0 sectors."); } } else { dumpLog?. WriteLine($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors."); updateStatus?. Invoke($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors."); } break; } dumpLog?.WriteLine($"Could not read subchannel for sector {lba}"); updateStatus?.Invoke($"Could not read subchannel for sector {lba}"); lba++; forward = true; continue; } dumpLog?.WriteLine($"Could not get correct subchannel for sector {lba}"); updateStatus?.Invoke($"Could not get correct subchannel for sector {lba}"); } if(subBuf.All(b => b == 0)) { inexactPositioning = true; AaruConsole.DebugWriteLine("Pregap calculator", "All Q empty for LBA {0}", lba); break; } BcdToBinaryQ(subBuf); // If it's not Q position if((subBuf[0] & 0xF) != 1) { // This means we already searched back, so search forward if(goFront) { lba++; forward = true; if(lba == (int)previousTrack.TrackStartSector) break; continue; } // Search back goneBack = true; lba--; forward = false; continue; } // Previous track if(subBuf[1] < track.TrackSequence) { lba++; forward = true; previousPregapIsPreviousTrack = true; // Already gone back, so go forward if(goneBack) goFront = true; continue; } // Same track, but not pregap if(subBuf[1] == track.TrackSequence && subBuf[2] > 0) { lba--; forward = false; if(previousPregapIsPreviousTrack) break; continue; } previousPregapIsPreviousTrack = false; // Pregap according to Q position posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; int diff = posQ - lba; int pregapQ = (int)track.TrackStartSector - lba; if(diff != 0) { AaruConsole.DebugWriteLine("Pregap calculator", "Invalid Q position for LBA {0}, got {1}", lba, posQ); inexactPositioning = true; } // Received a Q post the LBA we wanted, just go back. If we are already going forward, break if(posQ > lba) { if(forward) break; lba--; continue; } // Bigger than known change, otherwise we found it if(pregapQ > pregaps[track.TrackSequence]) { // If CRC is not OK, only accept pregaps less than 10 sectors longer than previously now if(crcOk || pregapQ - pregaps[track.TrackSequence] < 10) { AaruConsole.DebugWriteLine("Pregap calculator", "Pregap for track {0}: {1}", track.TrackSequence, pregapQ); pregaps[track.TrackSequence] = pregapQ; } // We are going forward, so we have already been in the previous track, so add 1 to pregap and get out of here else if(forward) { pregaps[track.TrackSequence]++; break; } } else if(pregapQ == pregaps[track.TrackSequence]) break; lba--; forward = false; } } for(int i = 0; i < tracks.Length; i++) { tracks[i].TrackPregap = (ulong)pregaps[tracks[i].TrackSequence]; tracks[i].TrackStartSector -= tracks[i].TrackPregap; #if DEBUG dumpLog?.WriteLine($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); updateStatus?.Invoke($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); #endif } }
public static void SolveTrackPregaps(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, Track[] tracks, bool supportsPqSubchannel, bool supportsRwSubchannel, Database.Models.Device dbDev, out bool inexactPositioning) { bool sense; // Sense indicator byte[] subBuf; int posQ; uint retries; bool? bcd = null; byte[] crc; Dictionary <uint, int> pregaps = new Dictionary <uint, int>(); inexactPositioning = false; if (!supportsPqSubchannel && !supportsRwSubchannel) { return; } // Check if subchannel is BCD for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, 11, dbDev, out subBuf) : GetSectorForPregapQ16(dev, 11, dbDev, out subBuf); if (sense) { continue; } bcd = (subBuf[9] & 0x10) > 0; break; } if (bcd is null) { dumpLog?.WriteLine("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); updateStatus?. Invoke("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); return; } // Initialize the dictionary for (int i = 0; i < tracks.Length; i++) { pregaps[tracks[i].TrackSequence] = 0; } foreach (Track track in tracks) { if (track.TrackSequence <= 1) { continue; } int lba = (int)track.TrackStartSector - 1; bool pregapFound = false; Track previousTrack = tracks.FirstOrDefault(t => t.TrackSequence == track.TrackSequence - 1); bool goneBack = false; bool goFront = false; // Check if pregap is 0 for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf); if (sense) { continue; } if (bcd == false) { BinaryToBcdQ(subBuf); } CRC16CCITTContext.Data(subBuf, 10, out crc); if (crc[0] != subBuf[10] || crc[1] != subBuf[11]) { continue; } BcdToBinaryQ(subBuf); // Q position if ((subBuf[0] & 0xF) != 1) { continue; } posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; if (subBuf[1] != track.TrackSequence - 1 || subBuf[2] == 0 || posQ != lba) { break; } pregaps[track.TrackSequence] = 0; pregapFound = true; } if (pregapFound) { continue; } // Calculate pregap lba = (int)track.TrackStartSector - 150; while (lba > (int)previousTrack.TrackStartSector) { // Some drives crash if you try to read just before the previous read, so seek away first sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba - 10, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba - 10, dbDev, out subBuf); for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf); if (sense) { continue; } if (bcd == false) { BinaryToBcdQ(subBuf); } CRC16CCITTContext.Data(subBuf, 10, out crc); if (crc[0] == subBuf[10] && crc[1] == subBuf[11]) { break; } } if (retries == 10) { dumpLog?.WriteLine($"Could not get correct subchannel for sector {lba}"); updateStatus?.Invoke($"Could not get correct subchannel for sector {lba}"); } BcdToBinaryQ(subBuf); // If it's not Q position if ((subBuf[0] & 0xF) != 1) { // This means we already searched back, so search forward if (goFront) { lba++; if (lba == (int)previousTrack.TrackStartSector) { break; } continue; } // Search back goneBack = true; lba--; continue; } // Previous track if (subBuf[1] < track.TrackSequence) { lba++; // Already gone back, so go forward if (goneBack) { goFront = true; } continue; } // Same track, but not pregap if (subBuf[1] == track.TrackSequence && subBuf[2] > 0) { lba--; continue; } // Pregap according to Q position int pregapQ = (subBuf[3] * 60 * 75) + (subBuf[4] * 75) + subBuf[5] + 1; posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; int diff = posQ - lba; if (diff != 0) { inexactPositioning = true; } // Bigger than known change, otherwise we found it if (pregapQ > pregaps[track.TrackSequence]) { pregaps[track.TrackSequence] = pregapQ; } else if (pregapQ == pregaps[track.TrackSequence]) { break; } lba--; } } for (int i = 0; i < tracks.Length; i++) { tracks[i].TrackPregap = (ulong)pregaps[tracks[i].TrackSequence]; tracks[i].TrackStartSector -= tracks[i].TrackPregap; #if DEBUG dumpLog?.WriteLine($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); updateStatus?.Invoke($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); #endif } }