public void NVMe() { StoppingErrorMessage?.Invoke("NVMe devices not yet supported."); }
ScanResults Scsi() { var results = new ScanResults(); MhddLog mhddLog; IbgLog ibgLog; byte[] senseBuf; bool sense = false; results.Blocks = 0; uint blockSize = 0; ushort currentProfile = 0x0001; if (_dev.IsRemovable) { sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (sense) { InitProgress?.Invoke(); FixedSense?decSense = Sense.DecodeFixed(senseBuf); if (decSense.HasValue) { if (decSense.Value.ASC == 0x3A) { int leftRetries = 5; while (leftRetries > 0) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } leftRetries--; } if (sense) { StoppingErrorMessage?.Invoke("Please insert media in drive"); return(results); } } else if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) { int leftRetries = 10; while (leftRetries > 0) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } leftRetries--; } if (sense) { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return(results); } } // 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) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } leftRetries--; } if (sense) { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return(results); } } else { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return(results); } } else { StoppingErrorMessage?.Invoke("Unknown testing unit was ready."); return(results); } EndProgress?.Invoke(); } } Reader scsiReader = null; switch (_dev.ScsiType) { case PeripheralDeviceTypes.DirectAccess: case PeripheralDeviceTypes.MultiMediaDevice: case PeripheralDeviceTypes.OCRWDevice: case PeripheralDeviceTypes.OpticalDevice: case PeripheralDeviceTypes.SimplifiedDevice: case PeripheralDeviceTypes.WriteOnceDevice: scsiReader = new Reader(_dev, _dev.Timeout, null); results.Blocks = scsiReader.GetDeviceBlocks(); if (scsiReader.FindReadCommand()) { StoppingErrorMessage?.Invoke("Unable to read medium."); return(results); } blockSize = scsiReader.LogicalBlockSize; if (results.Blocks != 0 && blockSize != 0) { results.Blocks++; UpdateStatus?. Invoke($"Media has {results.Blocks} blocks of {blockSize} bytes/each. (for a total of {results.Blocks * (ulong)blockSize} bytes)"); } break; case PeripheralDeviceTypes.SequentialAccess: StoppingErrorMessage?.Invoke("Scanning will never be supported on SCSI Streaming Devices." + Environment.NewLine + "It has no sense to do it, and it will put too much strain on the tape."); return(results); } if (results.Blocks == 0) { StoppingErrorMessage?.Invoke("Unable to read medium or empty medium present..."); return(results); } bool compactDisc = true; FullTOC.CDFullTOC?toc = null; if (_dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice) { sense = _dev.GetConfiguration(out byte[] cmdBuf, out senseBuf, 0, MmcGetConfigurationRt.Current, _dev.Timeout, out _); if (!sense) { Features.SeparatedFeatures ftr = Features.Separate(cmdBuf); currentProfile = ftr.CurrentProfile; switch (ftr.CurrentProfile) { case 0x0005: case 0x0008: case 0x0009: case 0x000A: case 0x0020: case 0x0021: case 0x0022: break; default: compactDisc = false; break; } } if (compactDisc) { currentProfile = 0x0008; // We discarded all discs that falsify a TOC before requesting a real TOC // No TOC, no CD (or an empty one) bool tocSense = _dev.ReadRawToc(out cmdBuf, out senseBuf, 1, _dev.Timeout, out _); if (!tocSense) { toc = FullTOC.Decode(cmdBuf); } } } else { compactDisc = false; } uint blocksToRead = 64; results.A = 0; // <3ms results.B = 0; // >=3ms, <10ms results.C = 0; // >=10ms, <50ms results.D = 0; // >=50ms, <150ms results.E = 0; // >=150ms, <500ms results.F = 0; // >=500ms results.Errored = 0; DateTime start; DateTime end; results.ProcessingTime = 0; results.TotalTime = 0; double currentSpeed = 0; results.MaxSpeed = double.MinValue; results.MinSpeed = double.MaxValue; results.UnreadableSectors = new List <ulong>(); if (compactDisc) { if (toc == null) { StoppingErrorMessage?.Invoke("Error trying to decode TOC..."); return(results); } bool readcd = !_dev.ReadCd(out _, out senseBuf, 0, 2352, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, _dev.Timeout, out _); if (readcd) { UpdateStatus?.Invoke("Using MMC READ CD command."); } start = DateTime.UtcNow; while (true) { if (readcd) { sense = _dev.ReadCd(out _, out senseBuf, 0, 2352, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, _dev.Timeout, out _); if (_dev.Error) { blocksToRead /= 2; } } if (!_dev.Error || blocksToRead == 1) { break; } } if (_dev.Error) { StoppingErrorMessage?. Invoke($"Device error {_dev.LastError} trying to guess ideal transfer length."); return(results); } UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, currentProfile); mhddLog = new MhddLog(_mhddLogPath, _dev, results.Blocks, blockSize, blocksToRead, false); ibgLog = new IbgLog(_ibgLogPath, currentProfile); DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = 0; i < results.Blocks; i += blocksToRead) { if (_aborted) { break; } double cmdDuration = 0; if (results.Blocks - i < blocksToRead) { blocksToRead = (uint)(results.Blocks - i); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (currentSpeed > results.MaxSpeed && currentSpeed != 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed != 0) { results.MinSpeed = currentSpeed; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator UpdateProgress?.Invoke($"Reading sector {i} of {results.Blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)results.Blocks); if (readcd) { sense = _dev.ReadCd(out _, out senseBuf, (uint)i, 2352, blocksToRead, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, _dev.Timeout, out cmdDuration); results.ProcessingTime += cmdDuration; } if (!sense) { if (cmdDuration >= 500) { results.F += blocksToRead; } else if (cmdDuration >= 150) { results.E += blocksToRead; } else if (cmdDuration >= 50) { results.D += blocksToRead; } else if (cmdDuration >= 10) { results.C += blocksToRead; } else if (cmdDuration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(i, cmdDuration); mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); } else { AaruConsole.DebugWriteLine("Media-Scan", "READ CD error:\n{0}", Sense.PrettifySense(senseBuf)); FixedSense?senseDecoded = Sense.DecodeFixed(senseBuf); if (senseDecoded.HasValue) { // TODO: This error happens when changing from track type afaik. Need to solve that more cleanly // LOGICAL BLOCK ADDRESS OUT OF RANGE if ((senseDecoded.Value.ASC != 0x21 || senseDecoded.Value.ASCQ != 0x00) && // ILLEGAL MODE FOR THIS TRACK (requesting sectors as-is, this is a firmware misconception when audio sectors // are in a track where subchannel indicates data) (senseDecoded.Value.ASC != 0x64 || senseDecoded.Value.ASCQ != 0x00)) { results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } ScanUnreadable?.Invoke(i); mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i, 0); } } else { ScanUnreadable?.Invoke(i); results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i, 0); } } sectorSpeedStart += blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); ScanSpeed?.Invoke(i, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(results.Blocks + 1)) / 1024 / (results.ProcessingTime / 1000), _devicePath); } else { start = DateTime.UtcNow; UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, currentProfile); mhddLog = new MhddLog(_mhddLogPath, _dev, results.Blocks, blockSize, blocksToRead, false); ibgLog = new IbgLog(_ibgLogPath, currentProfile); DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = 0; i < results.Blocks; i += blocksToRead) { if (_aborted) { break; } if (results.Blocks - i < blocksToRead) { blocksToRead = (uint)(results.Blocks - i); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (currentSpeed > results.MaxSpeed && currentSpeed != 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed != 0) { results.MinSpeed = currentSpeed; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator UpdateProgress?.Invoke($"Reading sector {i} of {results.Blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)results.Blocks); sense = scsiReader.ReadBlocks(out _, i, blocksToRead, out double cmdDuration); results.ProcessingTime += cmdDuration; if (!sense && !_dev.Error) { if (cmdDuration >= 500) { results.F += blocksToRead; } else if (cmdDuration >= 150) { results.E += blocksToRead; } else if (cmdDuration >= 50) { results.D += blocksToRead; } else if (cmdDuration >= 10) { results.C += blocksToRead; } else if (cmdDuration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(i, cmdDuration); mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); } // TODO: Separate errors on kind of errors. else { ScanUnreadable?.Invoke(i); results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); ibgLog.Write(i, 0); } sectorSpeedStart += blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); ScanSpeed?.Invoke(i, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(results.Blocks + 1)) / 1024 / (results.ProcessingTime / 1000), _devicePath); } results.SeekMax = double.MinValue; results.SeekMin = double.MaxValue; results.SeekTotal = 0; const int SEEK_TIMES = 1000; var rnd = new Random(); InitProgress?.Invoke(); for (int i = 0; i < SEEK_TIMES; i++) { if (_aborted || !_seekTest) { break; } uint seekPos = (uint)rnd.Next((int)results.Blocks); PulseProgress?.Invoke($"Seeking to sector {seekPos}...\t\t"); double seekCur; if (scsiReader.CanSeek) { scsiReader.Seek(seekPos, out seekCur); } else { scsiReader.ReadBlock(out _, seekPos, out seekCur); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (seekCur > results.SeekMax && seekCur != 0) { results.SeekMax = seekCur; } if (seekCur < results.SeekMin && seekCur != 0) { results.SeekMin = seekCur; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator results.SeekTotal += seekCur; GC.Collect(); } EndProgress?.Invoke(); results.ProcessingTime /= 1000; results.TotalTime = (end - start).TotalSeconds; results.AvgSpeed = (blockSize * (double)(results.Blocks + 1)) / 1048576 / results.ProcessingTime; results.SeekTimes = SEEK_TIMES; return(results); }
/// <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(); }
// TODO: Get cartridge serial number from Certance vendor EVPD /// <summary>Dumps a SCSI Block Commands device or a Reduced Block Commands devices</summary> void Scsi() { int resets = 0; if (_dev.IsRemovable) { InitProgress?.Invoke(); deviceGotReset: bool sense = _dev.ScsiTestUnitReady(out byte[] senseBuf, _dev.Timeout, out _); if (sense) { var decSense = Sense.Decode(senseBuf); if (decSense.HasValue) { ErrorMessage?. Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h"); _dumpLog.WriteLine("Device not ready. Sense {0} 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) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } decSense = Sense.Decode(senseBuf); if (decSense.HasValue) { ErrorMessage?. Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h"); _dumpLog.WriteLine("Device not ready. Sense {0} ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ); } leftRetries--; } if (sense) { StoppingErrorMessage?.Invoke("Please insert media in drive"); return; } } else if (decSense.Value.ASC == 0x04 && decSense.Value.ASCQ == 0x01) { int leftRetries = 50; while (leftRetries > 0) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } decSense = Sense.Decode(senseBuf); if (decSense.HasValue) { ErrorMessage?. Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h"); _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) { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return; } } /*else if (decSense.Value.ASC == 0x29 && decSense.Value.ASCQ == 0x00) * { * if (!deviceReset) * { * deviceReset = true; * ErrorMessage?.Invoke("Device did reset, retrying..."); * goto retryTestReady; * } * * StoppingErrorMessage?.Invoke(string.Format("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) { PulseProgress?.Invoke("Waiting for drive to become ready"); Thread.Sleep(2000); sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _); if (!sense) { break; } decSense = Sense.Decode(senseBuf); if (decSense.HasValue) { ErrorMessage?. Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h"); _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) { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return; } } else { StoppingErrorMessage?. Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}"); return; } } else { StoppingErrorMessage?.Invoke("Unknown testing unit was ready."); return; } } EndProgress?.Invoke(); } switch (_dev.ScsiType) { case PeripheralDeviceTypes.SequentialAccess: if (_dumpRaw) { StoppingErrorMessage?.Invoke("Tapes cannot be dumped raw."); return; } if (_outputPlugin is IWritableTapeImage) { Ssc(); } else { StoppingErrorMessage?. Invoke("The specified plugin does not support storing streaming tape images."); } return; case PeripheralDeviceTypes.MultiMediaDevice: if (_outputPlugin is IWritableOpticalImage) { Mmc(); } else { StoppingErrorMessage?. Invoke("The specified plugin does not support storing optical disc images."); } return; case PeripheralDeviceTypes.BridgingExpander when _dev.Model.StartsWith("MDM", StringComparison.InvariantCulture) || _dev.Model.StartsWith("MDH", StringComparison.InvariantCulture): MiniDisc(); break; default: Sbc(null, MediaType.Unknown, false); break; } }
/// <summary>Dumps a MiniDisc Data device</summary> internal void MiniDisc() { bool sense; byte scsiMediumType = 0; const ushort sbcProfile = 0x0001; DateTime start; DateTime end; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; byte[] readBuffer; Modes.DecodedMode?decMode = null; Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); byte[] cmdBuf; bool ret; _dumpLog.WriteLine("Initializing reader."); var scsiReader = new Reader(_dev, _dev.Timeout, null); ulong blocks = scsiReader.GetDeviceBlocks(); uint blockSize = scsiReader.LogicalBlockSize; _dumpLog.WriteLine("Requesting MODE SENSE (6)."); UpdateStatus?.Invoke("Requesting MODE SENSE (6)."); sense = _dev.ModeSense6(out cmdBuf, out _, true, ScsiModeSensePageControl.Current, 0x3F, 5, out _); if (!sense && !_dev.Error && Modes.DecodeMode6(cmdBuf, _dev.ScsiType).HasValue) { decMode = Modes.DecodeMode6(cmdBuf, _dev.ScsiType); } if (decMode.HasValue) { scsiMediumType = (byte)decMode.Value.Header.MediumType; } if (blockSize != 2048) { _dumpLog.WriteLine("MiniDisc albums, NetMD discs or user-written audio MiniDisc cannot be dumped."); StoppingErrorMessage?. Invoke("MiniDisc albums, NetMD discs or user-written audio MiniDisc cannot be dumped."); return; } MediaType dskType = MediaType.MDData; if (scsiReader.FindReadCommand()) { _dumpLog.WriteLine("ERROR: Cannot find correct read command: {0}.", scsiReader.ErrorMessage); StoppingErrorMessage?.Invoke("Unable to read medium."); return; } if (blocks != 0 && blockSize != 0) { blocks++; UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {blocks * (ulong)blockSize} bytes)"); } // Check how many blocks to read, if error show and return // 64 works, gets maximum speed (150KiB/s), slow I know... if (scsiReader.GetBlocksToRead()) { _dumpLog.WriteLine("ERROR: Cannot get blocks to read: {0}.", scsiReader.ErrorMessage); StoppingErrorMessage?.Invoke(scsiReader.ErrorMessage); return; } uint blocksToRead = scsiReader.BlocksToRead; uint logicalBlockSize = blockSize; uint physicalBlockSize = scsiReader.PhysicalBlockSize; if (blocks == 0) { _dumpLog.WriteLine("ERROR: Unable to read medium or empty medium present..."); StoppingErrorMessage?.Invoke("Unable to read medium or empty medium present..."); return; } UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * blockSize} bytes)."); UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time."); UpdateStatus?.Invoke($"Device reports {blockSize} bytes per logical block."); UpdateStatus?.Invoke($"Device reports {scsiReader.LongBlockSize} bytes per physical block."); UpdateStatus?.Invoke($"SCSI device type: {_dev.ScsiType}."); UpdateStatus?.Invoke($"SCSI medium type: {scsiMediumType}."); UpdateStatus?.Invoke($"Media identified as {dskType}"); _dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize); _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.", scsiReader.LongBlockSize); _dumpLog.WriteLine("SCSI device type: {0}.", _dev.ScsiType); _dumpLog.WriteLine("SCSI medium type: {0}.", scsiMediumType); _dumpLog.WriteLine("Media identified as {0}.", dskType); sense = _dev.MiniDiscGetType(out cmdBuf, out _, _dev.Timeout, out _); if (!sense && !_dev.Error) { mediaTags.Add(MediaTagType.MiniDiscType, cmdBuf); } sense = _dev.MiniDiscD5(out cmdBuf, out _, _dev.Timeout, out _); if (!sense && !_dev.Error) { mediaTags.Add(MediaTagType.MiniDiscD5, cmdBuf); } sense = _dev.MiniDiscReadDataTOC(out cmdBuf, out _, _dev.Timeout, out _); if (!sense && !_dev.Error) { mediaTags.Add(MediaTagType.MiniDiscDTOC, cmdBuf); } var utocMs = new MemoryStream(); for (uint i = 0; i < 3; i++) { sense = _dev.MiniDiscReadUserTOC(out cmdBuf, out _, i, _dev.Timeout, out _); if (sense || _dev.Error) { break; } utocMs.Write(cmdBuf, 0, 2336); } if (utocMs.Length > 0) { mediaTags.Add(MediaTagType.MiniDiscUTOC, utocMs.ToArray()); } ret = true; foreach (MediaTagType tag in mediaTags.Keys) { if (_outputPlugin.SupportedMediaTags.Contains(tag)) { continue; } ret = false; _dumpLog.WriteLine($"Output format does not support {tag}."); ErrorMessage?.Invoke($"Output format does not support {tag}."); } if (!ret) { if (_force) { _dumpLog.WriteLine("Several media tags not supported, continuing..."); ErrorMessage?.Invoke("Several media tags not supported, continuing..."); } else { _dumpLog.WriteLine("Several media tags not supported, not continuing..."); StoppingErrorMessage?.Invoke("Several media tags not supported, not continuing..."); return; } } UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); _dumpLog.WriteLine("Reading {0} sectors at a time.", blocksToRead); var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private); var ibgLog = new IbgLog(_outputPrefix + ".ibg", sbcProfile); ret = _outputPlugin.Create(_outputPath, dskType, _formatOptions, blocks, blockSize); // Cannot create image if (!ret) { _dumpLog.WriteLine("Error creating output image, not continuing."); _dumpLog.WriteLine(_outputPlugin.ErrorMessage); StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } start = DateTime.UtcNow; double imageWriteDuration = 0; if (decMode?.Pages != null) { bool setGeometry = false; foreach (Modes.ModePage page in decMode.Value.Pages) { if (page.Page == 0x04 && page.Subpage == 0x00) { Modes.ModePage_04?rigidPage = Modes.DecodeModePage_04(page.PageResponse); if (!rigidPage.HasValue || setGeometry) { continue; } _dumpLog.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", rigidPage.Value.Cylinders, rigidPage.Value.Heads, (uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))); UpdateStatus?. Invoke($"Setting geometry to {rigidPage.Value.Cylinders} cylinders, {rigidPage.Value.Heads} heads, {(uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))} sectors per track"); _outputPlugin.SetGeometry(rigidPage.Value.Cylinders, rigidPage.Value.Heads, (uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))); setGeometry = true; } else if (page.Page == 0x05 && page.Subpage == 0x00) { Modes.ModePage_05?flexiblePage = Modes.DecodeModePage_05(page.PageResponse); if (!flexiblePage.HasValue) { continue; } _dumpLog.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", flexiblePage.Value.Cylinders, flexiblePage.Value.Heads, flexiblePage.Value.SectorsPerTrack); UpdateStatus?. Invoke($"Setting geometry to {flexiblePage.Value.Cylinders} cylinders, {flexiblePage.Value.Heads} heads, {flexiblePage.Value.SectorsPerTrack} sectors per track"); _outputPlugin.SetGeometry(flexiblePage.Value.Cylinders, flexiblePage.Value.Heads, flexiblePage.Value.SectorsPerTrack); setGeometry = true; } } } DumpHardwareType currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(true, _dev.IsRemovable, blocks, _dev.Manufacturer, _dev.Model, _dev.Serial, _dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision, _private); if (currentTry == null || extents == null) { StoppingErrorMessage?.Invoke("Could not process resume file, not continuing..."); return; } if (_resume.NextBlock > 0) { UpdateStatus?.Invoke($"Resuming from block {_resume.NextBlock}."); _dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock); } bool newTrim = false; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = _resume.NextBlock; i < blocks; i += blocksToRead) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } if (blocks - i < blocksToRead) { blocksToRead = (uint)(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 UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); sense = _dev.Read6(out readBuffer, out _, (uint)i, blockSize, (byte)blocksToRead, _dev.Timeout, out double 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[blockSize * _skip], i, _skip); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; for (ulong b = i; b < i + _skip; b++) { _resume.BadBlocks.Add(b); } 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; newTrim = true; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)blockSize * (double)(blocks + 1)) / 1024 / imageWriteDuration:F3} KiB/sec."); _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 && _trim && newTrim) { start = DateTime.UtcNow; UpdateStatus?.Invoke("Trimming skipped sectors"); _dumpLog.WriteLine("Trimming skipped sectors"); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke($"Trimming sector {badSector}"); sense = _dev.Read6(out readBuffer, out _, (uint)badSector, blockSize, 1, _dev.Timeout, out double cmdDuration); if (sense || _dev.Error) { continue; } _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); } EndProgress?.Invoke(); end = DateTime.UtcNow; UpdateStatus?.Invoke($"Trimming finished in {(end - start).TotalSeconds} seconds."); _dumpLog.WriteLine("Trimming 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; Modes.ModePage?currentModePage = null; byte[] md6; byte[] md10; if (_persistent) { Modes.ModePage_01_MMC pgMmc; Modes.ModePage_01 pg; sense = _dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout, out _); if (!sense) { Modes.DecodedMode?dcMode6 = Modes.DecodeMode6(readBuffer, _dev.ScsiType); if (dcMode6.HasValue && dcMode6.Value.Pages != null) { foreach (Modes.ModePage modePage in dcMode6.Value.Pages) { if (modePage.Page == 0x01 && modePage.Subpage == 0x00) { currentModePage = modePage; } } } } if (currentModePage == null) { pg = new Modes.ModePage_01 { PS = false, AWRE = true, ARRE = true, TB = false, RC = false, EER = true, PER = false, DTE = true, DCR = false, ReadRetryCount = 32 }; currentModePage = new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) }; } pg = new Modes.ModePage_01 { PS = false, AWRE = false, ARRE = false, TB = true, RC = false, EER = true, PER = false, DTE = false, DCR = false, ReadRetryCount = 255 }; var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) } } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks)."); _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks)."); sense = _dev.ModeSelect(md6, out byte[] senseBuf, true, false, _dev.Timeout, out _); if (sense) { UpdateStatus?. Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf)); _dumpLog. WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); } else { runningPersistent = true; } } InitProgress?.Invoke(); repeatRetry: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass, forward ? "forward" : "reverse", runningPersistent ? "recovering partial data, " : "")); sense = _dev.Read6(out readBuffer, out _, (uint)badSector, blockSize, 1, _dev.Timeout, out double cmdDuration); totalDuration += cmdDuration; if (!sense && !_dev.Error) { _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}."); _dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (runningPersistent) { _outputPlugin.WriteSector(readBuffer, badSector); } } if (pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0) { pass++; forward = !forward; _resume.BadBlocks.Sort(); if (!forward) { _resume.BadBlocks.Reverse(); } goto repeatRetry; } if (runningPersistent && currentModePage.HasValue) { var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { currentModePage.Value } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); UpdateStatus?.Invoke("Sending MODE SELECT to drive (return device to previous status)."); _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status)."); _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _); } EndProgress?.Invoke(); } #endregion Error handling _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); var metadata = new CommonTypes.Structs.ImageInfo { Application = "Aaru", ApplicationVersion = Version.GetVersion() }; if (!_outputPlugin.SetMetadata(metadata)) { ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine + _outputPlugin.ErrorMessage); } if (_preSidecar != null) { _outputPlugin.SetCicmMetadata(_preSidecar); } _dumpLog.WriteLine("Closing output file."); UpdateStatus?.Invoke("Closing output file."); DateTime closeStart = DateTime.Now; _outputPlugin.Close(); DateTime closeEnd = DateTime.Now; UpdateStatus?.Invoke($"Closed in {(closeEnd - closeStart).TotalSeconds} seconds."); _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds); if (_aborted) { UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); return; } double totalChkDuration = 0; if (_metadata) { UpdateStatus?.Invoke("Creating sidecar."); _dumpLog.WriteLine("Creating sidecar."); var filters = new FiltersList(); IFilter filter = filters.GetFilter(_outputPath); IMediaImage inputPlugin = ImageFormat.Detect(filter); if (!inputPlugin.Open(filter)) { StoppingErrorMessage?.Invoke("Could not open created image."); return; } DateTime chkStart = DateTime.UtcNow; _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding); _sidecarClass.InitProgressEvent += InitProgress; _sidecarClass.UpdateProgressEvent += UpdateProgress; _sidecarClass.EndProgressEvent += EndProgress; _sidecarClass.InitProgressEvent2 += InitProgress2; _sidecarClass.UpdateProgressEvent2 += UpdateProgress2; _sidecarClass.EndProgressEvent2 += EndProgress2; _sidecarClass.UpdateStatusEvent += UpdateStatus; CICMMetadataType sidecar = _sidecarClass.Create(); end = DateTime.UtcNow; totalChkDuration = (end - chkStart).TotalMilliseconds; UpdateStatus?.Invoke($"Sidecar created in {(end - chkStart).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average checksum speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalChkDuration / 1000):F3} KiB/sec."); _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)); if (_preSidecar != null) { _preSidecar.BlockMedia = sidecar.BlockMedia; sidecar = _preSidecar; } 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(partition.StartSector, fileSystem.Type)); } if (filesystems.Count > 0) { foreach (var filesystem in filesystems.Select(o => new { o.start, o.type }).Distinct()) { UpdateStatus?.Invoke($"Found filesystem {filesystem.type} at sector {filesystem.start}"); _dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start); } } sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); (string type, string subType)xmlType = CommonTypes.Metadata.MediaType.MediaTypeToString(dskType); sidecar.BlockMedia[0].DiskType = xmlType.type; sidecar.BlockMedia[0].DiskSubType = xmlType.subType; if (!_dev.IsRemovable || _dev.IsUsb) { if (_dev.Type == DeviceType.ATAPI) { sidecar.BlockMedia[0].Interface = "ATAPI"; } else if (_dev.IsUsb) { sidecar.BlockMedia[0].Interface = "USB"; } else if (_dev.IsFireWire) { sidecar.BlockMedia[0].Interface = "FireWire"; } else { sidecar.BlockMedia[0].Interface = "SCSI"; } } sidecar.BlockMedia[0].LogicalBlocks = blocks; sidecar.BlockMedia[0].PhysicalBlockSize = physicalBlockSize; sidecar.BlockMedia[0].LogicalBlockSize = logicalBlockSize; sidecar.BlockMedia[0].Manufacturer = _dev.Manufacturer; sidecar.BlockMedia[0].Model = _dev.Model; if (!_private) { sidecar.BlockMedia[0].Serial = _dev.Serial; } sidecar.BlockMedia[0].Size = blocks * blockSize; if (_dev.IsRemovable) { sidecar.BlockMedia[0].DumpHardwareArray = _resume.Tries.ToArray(); } UpdateStatus?.Invoke("Writing metadata sidecar"); var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); } UpdateStatus?.Invoke(""); UpdateStatus?. Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing)."); UpdateStatus?. Invoke($"Average speed: {((double)blockSize * (double)(blocks + 1)) / 1048576 / (totalDuration / 1000):F3} MiB/sec."); if (maxSpeed > 0) { UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec."); } if (minSpeed > 0 && minSpeed < double.MaxValue) { UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec."); } UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read."); UpdateStatus?.Invoke(""); Statistics.AddMedia(dskType, true); }
/// <summary>Dumps an ATA device</summary> public void Ata() { if (_dumpRaw) { if (_force) { ErrorMessage?.Invoke("Raw dumping not yet supported in ATA devices, continuing..."); } else { StoppingErrorMessage?.Invoke("Raw dumping not yet supported in ATA devices, aborting..."); return; } } const ushort ATA_PROFILE = 0x0001; const uint TIMEOUT = 5; double imageWriteDuration = 0; MediaType mediaType = MediaType.Unknown; UpdateStatus?.Invoke("Requesting ATA IDENTIFY DEVICE."); _dumpLog.WriteLine("Requesting ATA IDENTIFY DEVICE."); bool 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; // Initializate reader UpdateStatus?.Invoke("Initializing reader."); _dumpLog.WriteLine("Initializing reader."); var 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); ErrorMessage(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); ErrorMessage(ataReader.ErrorMessage); return; } // Check how many blocks to read, if error show and return if (ataReader.GetBlocksToRead(_maximumReadable)) { _dumpLog.WriteLine("ERROR: Cannot get blocks to read: {0}.", ataReader.ErrorMessage); ErrorMessage(ataReader.ErrorMessage); return; } uint blocksToRead = ataReader.BlocksToRead; ushort cylinders = ataReader.Cylinders; byte heads = ataReader.Heads; byte sectors = ataReader.Sectors; UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * blockSize} bytes)."); UpdateStatus?. Invoke($"Device reports {cylinders} cylinders {heads} heads {sectors} sectors per track."); UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time."); UpdateStatus?.Invoke($"Device reports {blockSize} bytes per logical block."); UpdateStatus?.Invoke($"Device reports {physicalsectorsize} bytes per physical block."); _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, _dev.FirmwareRevision, _private); if (currentTry == null || extents == null) { StoppingErrorMessage?.Invoke("Could not process resume file, not continuing..."); return; } 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."); ErrorMessage("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."); ErrorMessage("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."); ErrorMessage("Output format does not support ATA IDENTIFY."); } if (!ret) { _dumpLog.WriteLine("Several media tags not supported, {0}continuing...", _force ? "" : "not "); if (_force) { ErrorMessage("Several media tags not supported, continuing..."); } else { StoppingErrorMessage?.Invoke("Several media tags not supported, not continuing..."); return; } } mediaType = MediaTypeFromDevice.GetFromAta(_dev.Manufacturer, _dev.Model, _dev.IsRemovable, _dev.IsCompactFlash, _dev.IsPcmcia, blocks, blockSize); ret = _outputPlugin.Create(_outputPath, mediaType, _formatOptions, blocks, blockSize); // Cannot create image if (!ret) { _dumpLog.WriteLine("Error creating output image, not continuing."); _dumpLog.WriteLine(_outputPlugin.ErrorMessage); StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } // Setting geometry _outputPlugin.SetGeometry(cylinders, heads, sectors); if (ataReader.IsLba) { UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); if (_skip < blocksToRead) { _skip = blocksToRead; } mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private); ibgLog = new IbgLog(_outputPrefix + ".ibg", ATA_PROFILE); if (_resume.NextBlock > 0) { UpdateStatus?.Invoke($"Resuming from block {_resume.NextBlock}."); _dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock); } bool newTrim = false; start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = _resume.NextBlock; i < blocks; i += blocksToRead) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _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 UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); 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; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.Now; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)blockSize * (double)(blocks + 1)) / 1024 / imageWriteDuration:F3} KiB/sec."); _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 && _trim && newTrim) { start = DateTime.UtcNow; UpdateStatus?.Invoke("Trimming skipped sectors"); _dumpLog.WriteLine("Trimming skipped sectors"); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke($"Trimming sector {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); } EndProgress?.Invoke(); end = DateTime.UtcNow; UpdateStatus?.Invoke($"Trimmming finished in {(end - start).TotalSeconds} seconds."); _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; InitProgress?.Invoke(); repeatRetryLba: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass, forward ? "forward" : "reverse", _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); UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}."); _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(); if (!forward) { _resume.BadBlocks.Reverse(); } goto repeatRetryLba; } EndProgress?.Invoke(); } #endregion Error handling LBA currentTry.Extents = ExtentsConverter.ToMetadata(extents); } else { mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private); ibgLog = new IbgLog(_outputPrefix + ".ibg", ATA_PROFILE); ulong currentBlock = 0; blocks = (ulong)(cylinders * heads * sectors); start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); 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); UpdateStatus?.Invoke("Aborted!"); _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 PulseProgress?. Invoke($"Reading cylinder {cy} head {hd} sector {sc} ({currentSpeed:F3} MiB/sec.)"); 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; } sectorSpeedStart++; currentBlock++; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } } } end = DateTime.Now; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (imageWriteDuration / 1000):F3} KiB/sec."); _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); // TODO: Non-removable var metadata = new CommonTypes.Structs.ImageInfo { Application = "Aaru", ApplicationVersion = Version.GetVersion() }; if (!_outputPlugin.SetMetadata(metadata)) { ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine + _outputPlugin.ErrorMessage); } if (_preSidecar != null) { _outputPlugin.SetCicmMetadata(_preSidecar); } _dumpLog.WriteLine("Closing output file."); UpdateStatus?.Invoke("Closing output file."); DateTime closeStart = DateTime.Now; _outputPlugin.Close(); DateTime closeEnd = DateTime.Now; UpdateStatus?.Invoke($"Closed in {(closeEnd - closeStart).TotalSeconds} seconds."); _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds); if (_aborted) { _dumpLog.WriteLine("Aborted!"); UpdateStatus?.Invoke("Aborted!"); return; } double totalChkDuration = 0; if (_metadata) { _dumpLog.WriteLine("Creating sidecar."); UpdateStatus?.Invoke("Creating sidecar."); var filters = new FiltersList(); IFilter filter = filters.GetFilter(_outputPath); IMediaImage inputPlugin = ImageFormat.Detect(filter); if (!inputPlugin.Open(filter)) { StoppingErrorMessage?.Invoke("Could not open created image."); return; } DateTime chkStart = DateTime.UtcNow; _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding); _sidecarClass.InitProgressEvent += InitProgress; _sidecarClass.UpdateProgressEvent += UpdateProgress; _sidecarClass.EndProgressEvent += EndProgress; _sidecarClass.InitProgressEvent2 += InitProgress2; _sidecarClass.UpdateProgressEvent2 += UpdateProgress2; _sidecarClass.EndProgressEvent2 += EndProgress2; _sidecarClass.UpdateStatusEvent += UpdateStatus; CICMMetadataType sidecar = _sidecarClass.Create(); if (_preSidecar != null) { _preSidecar.BlockMedia = sidecar.BlockMedia; sidecar = _preSidecar; } if (_dev.IsUsb && _dev.UsbDescriptors != null) { _dumpLog.WriteLine("Reading USB descriptors."); UpdateStatus?.Invoke("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 = (ulong)_dev.UsbDescriptors.Length, Checksums = Checksum.GetChecksums(_dev.UsbDescriptors).ToArray() } } } ; } if (_dev.IsPcmcia && _dev.Cis != null) { _dumpLog.WriteLine("Reading PCMCIA CIS."); UpdateStatus?.Invoke("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 = (ulong)_dev.Cis.Length, Checksums = Checksum.GetChecksums(_dev.Cis).ToArray() } } } ; _dumpLog.WriteLine("Decoding PCMCIA CIS."); UpdateStatus?.Invoke("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; } } } } if (!_private) { DeviceReport.ClearIdentify(ataIdentify); } ret = _outputPlugin.WriteMediaTag(ataIdentify, MediaTagType.ATA_IDENTIFY); if (ret) { sidecar.BlockMedia[0].ATA = new ATAType { Identify = new DumpType { Image = _outputPath, Size = (ulong)cmdBuf.Length, Checksums = Checksum.GetChecksums(cmdBuf).ToArray() } } } ; DateTime chkEnd = DateTime.UtcNow; totalChkDuration = (chkEnd - chkStart).TotalMilliseconds; UpdateStatus?.Invoke($"Sidecar created in {(chkEnd - chkStart).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average checksum speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalChkDuration / 1000):F3} KiB/sec."); _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(partition.StartSector, fileSystem.Type)); } if (filesystems.Count > 0) { foreach (var filesystem in filesystems.Select(o => new { o.start, o.type }).Distinct()) { UpdateStatus?. Invoke($"Found filesystem {filesystem.type} at sector {filesystem.start}"); _dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start); } } (string type, string subType) = CommonTypes.Metadata.MediaType.MediaTypeToString(mediaType); sidecar.BlockMedia[0].DiskType = type; sidecar.BlockMedia[0].DiskSubType = subType; sidecar.BlockMedia[0].Interface = "ATA"; sidecar.BlockMedia[0].LogicalBlocks = blocks; sidecar.BlockMedia[0].PhysicalBlockSize = physicalsectorsize; sidecar.BlockMedia[0].LogicalBlockSize = blockSize; sidecar.BlockMedia[0].Manufacturer = _dev.Manufacturer; sidecar.BlockMedia[0].Model = _dev.Model; if (!_private) { sidecar.BlockMedia[0].Serial = _dev.Serial; } sidecar.BlockMedia[0].Size = 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; } UpdateStatus?.Invoke("Writing metadata sidecar"); var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); } UpdateStatus?.Invoke(""); UpdateStatus?. Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing)."); UpdateStatus?. Invoke($"Average speed: {((double)blockSize * (double)(blocks + 1)) / 1048576 / (totalDuration / 1000):F3} MiB/sec."); if (maxSpeed > 0) { UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec."); } if (minSpeed > 0 && minSpeed < double.MaxValue) { UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec."); } UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read."); if (_resume.BadBlocks.Count > 0) { _resume.BadBlocks.Sort(); } UpdateStatus?.Invoke(""); } Statistics.AddMedia(mediaType, true); } else { StoppingErrorMessage?.Invoke("Unable to communicate with ATA device."); } } } }
/// <summary> /// Scans the media from an ATA device /// </summary> /// <returns>Scanning results</returns> ScanResults Ata() { ScanResults results = new ScanResults(); bool sense; results.Blocks = 0; const ushort ATA_PROFILE = 0x0001; const uint TIMEOUT = 5; sense = dev.AtaIdentify(out byte[] cmdBuf, out _); if (!sense && Identify.Decode(cmdBuf).HasValue) { // Initializate reader Reader ataReader = new Reader(dev, TIMEOUT, cmdBuf); // Fill reader blocks results.Blocks = ataReader.GetDeviceBlocks(); if (ataReader.FindReadCommand()) { StoppingErrorMessage?.Invoke(ataReader.ErrorMessage); return(results); } // Check block sizes if (ataReader.GetBlockSize()) { StoppingErrorMessage?.Invoke(ataReader.ErrorMessage); return(results); } uint blockSize = ataReader.LogicalBlockSize; // Check how many blocks to read, if error show and return if (ataReader.GetBlocksToRead()) { StoppingErrorMessage?.Invoke(ataReader.ErrorMessage); return(results); } uint blocksToRead = ataReader.BlocksToRead; ushort cylinders = ataReader.Cylinders; byte heads = ataReader.Heads; byte sectors = ataReader.Sectors; results.A = 0; // <3ms results.B = 0; // >=3ms, <10ms results.C = 0; // >=10ms, <50ms results.D = 0; // >=50ms, <150ms results.E = 0; // >=150ms, <500ms results.F = 0; // >=500ms results.Errored = 0; DateTime start; DateTime end; results.ProcessingTime = 0; double currentSpeed = 0; results.MaxSpeed = double.MinValue; results.MinSpeed = double.MaxValue; results.UnreadableSectors = new List <ulong>(); results.SeekMax = double.MinValue; results.SeekMin = double.MaxValue; results.SeekTotal = 0; const int SEEK_TIMES = 1000; double seekCur; Random rnd = new Random(); MhddLog mhddLog; IbgLog ibgLog; double duration; if (ataReader.IsLba) { UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, ATA_PROFILE); mhddLog = new MhddLog(mhddLogPath, dev, results.Blocks, blockSize, blocksToRead); ibgLog = new IbgLog(ibgLogPath, ATA_PROFILE); start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = 0; i < results.Blocks; i += blocksToRead) { if (aborted) { break; } if (results.Blocks - i < blocksToRead) { blocksToRead = (byte)(results.Blocks - i); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (currentSpeed > results.MaxSpeed && currentSpeed != 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed != 0) { results.MinSpeed = currentSpeed; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator UpdateProgress?.Invoke($"Reading sector {i} of {results.Blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)results.Blocks); bool error = ataReader.ReadBlocks(out cmdBuf, i, blocksToRead, out duration); if (!error) { if (duration >= 500) { results.F += blocksToRead; } else if (duration >= 150) { results.E += blocksToRead; } else if (duration >= 50) { results.D += blocksToRead; } else if (duration >= 10) { results.C += blocksToRead; } else if (duration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(i, duration); mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); } else { ScanUnreadable?.Invoke(i); results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); } sectorSpeedStart += blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); ScanSpeed?.Invoke(i, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (double)(results.Blocks + 1) / 1024 / (results.ProcessingTime / 1000), devicePath); InitProgress?.Invoke(); if (ataReader.CanSeekLba) { for (int i = 0; i < SEEK_TIMES; i++) { if (aborted) { break; } uint seekPos = (uint)rnd.Next((int)results.Blocks); PulseProgress?.Invoke($"Seeking to sector {seekPos}...\t\t"); ataReader.Seek(seekPos, out seekCur); #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (seekCur > results.SeekMax && seekCur != 0) { results.SeekMax = seekCur; } if (seekCur < results.SeekMin && seekCur != 0) { results.SeekMin = seekCur; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator results.SeekTotal += seekCur; GC.Collect(); } } EndProgress?.Invoke(); } else { InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, ATA_PROFILE); mhddLog = new MhddLog(mhddLogPath, dev, results.Blocks, blockSize, blocksToRead); ibgLog = new IbgLog(ibgLogPath, ATA_PROFILE); ulong currentBlock = 0; results.Blocks = (ulong)(cylinders * heads * sectors); start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ushort cy = 0; cy < cylinders; cy++) { for (byte hd = 0; hd < heads; hd++) { for (byte sc = 1; sc < sectors; sc++) { if (aborted) { break; } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (currentSpeed > results.MaxSpeed && currentSpeed != 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed != 0) { results.MinSpeed = currentSpeed; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator PulseProgress ?.Invoke($"Reading cylinder {cy} head {hd} sector {sc} ({currentSpeed:F3} MiB/sec.)"); bool error = ataReader.ReadChs(out cmdBuf, cy, hd, sc, out duration); if (!error) { if (duration >= 500) { results.F += blocksToRead; } else if (duration >= 150) { results.E += blocksToRead; } else if (duration >= 50) { results.D += blocksToRead; } else if (duration >= 10) { results.C += blocksToRead; } else if (duration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(currentBlock, duration); mhddLog.Write(currentBlock, duration); ibgLog.Write(currentBlock, currentSpeed * 1024); } else { ScanUnreadable?.Invoke(currentBlock); results.Errored += blocksToRead; results.UnreadableSectors.Add(currentBlock); mhddLog.Write(currentBlock, duration < 500 ? 65535 : duration); ibgLog.Write(currentBlock, 0); } sectorSpeedStart++; currentBlock++; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); ScanSpeed?.Invoke(currentBlock, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } } } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (double)(results.Blocks + 1) / 1024 / (results.ProcessingTime / 1000), devicePath); InitProgress?.Invoke(); if (ataReader.CanSeek) { for (int i = 0; i < SEEK_TIMES; i++) { if (aborted) { break; } ushort seekCy = (ushort)rnd.Next(cylinders); byte seekHd = (byte)rnd.Next(heads); byte seekSc = (byte)rnd.Next(sectors); PulseProgress ?.Invoke($"\rSeeking to cylinder {seekCy}, head {seekHd}, sector {seekSc}...\t\t"); ataReader.SeekChs(seekCy, seekHd, seekSc, out seekCur); #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (seekCur > results.SeekMax && seekCur != 0) { results.SeekMax = seekCur; } if (seekCur < results.SeekMin && seekCur != 0) { results.SeekMin = seekCur; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator results.SeekTotal += seekCur; GC.Collect(); } } EndProgress?.Invoke(); } results.ProcessingTime /= 1000; results.TotalTime = (end - start).TotalSeconds; results.AvgSpeed = blockSize * (double)(results.Blocks + 1) / 1048576 / results.ProcessingTime; results.SeekTimes = SEEK_TIMES; return(results); } StoppingErrorMessage?.Invoke("Unable to communicate with ATA device."); return(results); }
/// <summary>Dumps an optical disc</summary> void Mmc() { MediaType dskType = MediaType.Unknown; bool sense; byte[] tmpBuf; bool compactDisc = true; bool isXbox = false; _speedMultiplier = 1; // 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; _speedMultiplier = -1; goto default; case 0x0002: dskType = MediaType.PD650; _speedMultiplier = -1; 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; _speedMultiplier = 9; goto default; case 0x0011: dskType = MediaType.DVDR; _speedMultiplier = 9; goto default; case 0x0012: dskType = MediaType.DVDRAM; _speedMultiplier = 9; goto default; case 0x0013: case 0x0014: dskType = MediaType.DVDRW; _speedMultiplier = 9; goto default; case 0x0015: case 0x0016: dskType = MediaType.DVDRDL; _speedMultiplier = 9; goto default; case 0x0017: dskType = MediaType.DVDRWDL; _speedMultiplier = 9; goto default; case 0x0018: dskType = MediaType.DVDDownload; _speedMultiplier = 9; goto default; case 0x001A: dskType = MediaType.DVDPRW; _speedMultiplier = 9; goto default; case 0x001B: dskType = MediaType.DVDPR; _speedMultiplier = 9; 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; _speedMultiplier = 9; goto default; case 0x002B: dskType = MediaType.DVDPRDL; _speedMultiplier = 9; goto default; case 0x0040: dskType = MediaType.BDROM; _speedMultiplier = 30; goto default; case 0x0041: case 0x0042: dskType = MediaType.BDR; _speedMultiplier = 30; goto default; case 0x0043: dskType = MediaType.BDRE; _speedMultiplier = 30; goto default; case 0x0050: dskType = MediaType.HDDVDROM; _speedMultiplier = 30; goto default; case 0x0051: dskType = MediaType.HDDVDR; _speedMultiplier = 30; goto default; case 0x0052: dskType = MediaType.HDDVDRAM; _speedMultiplier = 30; goto default; case 0x0053: dskType = MediaType.HDDVDRW; _speedMultiplier = 30; goto default; case 0x0058: dskType = MediaType.HDDVDRDL; _speedMultiplier = 30; goto default; case 0x005A: dskType = MediaType.HDDVDRWDL; _speedMultiplier = 30; goto default; default: compactDisc = false; break; } } if (compactDisc) { _speedMultiplier *= 177; CompactDisc(); return; } _speedMultiplier *= 150; var scsiReader = new Reader(_dev, _dev.Timeout, null, _errorLog, _dumpRaw); ulong blocks = scsiReader.GetDeviceBlocks(); _dumpLog.WriteLine("Device reports disc has {0} blocks", blocks); Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); if (dskType == MediaType.PD650) { switch (blocks + 1) { case 1281856: dskType = MediaType.PD650_WORM; break; case 58620544: dskType = MediaType.REV120; break; case 17090880: dskType = MediaType.REV35; break; // TODO: Unknown value default: dskType = MediaType.REV70; break; } } #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?.DiskCategory == DiskCategory.Nintendo && nintendoPfi.Value.PartVersion == 15) { _dumpLog.WriteLine("Dumping Nintendo GameCube or Wii discs is not yet implemented."); StoppingErrorMessage?. Invoke("Dumping Nintendo GameCube or Wii discs is not yet implemented."); return; } } 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; UpdateStatus?.Invoke($"PFI:\n{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."); StoppingErrorMessage?. Invoke("Dumping Xbox Game Discs requires a drive with Kreon firmware."); return; } if (_dumpRaw && !_force) { StoppingErrorMessage?. Invoke("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(mediaTags, dskType); return; } Sbc(mediaTags, dskType, true); }
void DumpUmd() { const uint blockSize = 2048; const MediaType dskType = MediaType.UMD; uint blocksToRead = 16; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; DateTime start; DateTime end; byte[] senseBuf; bool sense = _dev.Read12(out byte[] readBuffer, out _, 0, false, true, false, false, 0, 512, 0, 1, false, _dev.Timeout, out _); if (sense) { _dumpLog.WriteLine("Could not read..."); StoppingErrorMessage?.Invoke("Could not read..."); return; } ushort fatStart = (ushort)((readBuffer[0x0F] << 8) + readBuffer[0x0E]); ushort sectorsPerFat = (ushort)((readBuffer[0x17] << 8) + readBuffer[0x16]); ushort rootStart = (ushort)((sectorsPerFat * 2) + fatStart); ushort rootSize = (ushort)((((readBuffer[0x12] << 8) + readBuffer[0x11]) * 32) / 512); ushort umdStart = (ushort)(rootStart + rootSize); UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}..."); _dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart); sense = _dev.Read12(out readBuffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false, _dev.Timeout, out _); if (sense) { _dumpLog.WriteLine("Could not read..."); StoppingErrorMessage?.Invoke("Could not read..."); return; } uint umdSizeInBytes = BitConverter.ToUInt32(readBuffer, 0x3C); ulong blocks = umdSizeInBytes / blockSize; string mediaPartNumber = Encoding.ASCII.GetString(readBuffer, 0, 11).Trim(); ulong totalSize = blocks * blockSize; if (totalSize > 1099511627776) { UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1099511627776d:F3} TiB)"); } else if (totalSize > 1073741824) { UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1073741824d:F3} GiB)"); } else if (totalSize > 1048576) { UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1048576d:F3} MiB)"); } else if (totalSize > 1024) { UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1024d:F3} KiB)"); } else { UpdateStatus?. Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize} bytes)"); } UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * blockSize} bytes)."); UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time."); UpdateStatus?.Invoke($"Device reports {blockSize} bytes per logical block."); UpdateStatus?.Invoke($"Device reports {2048} bytes per physical block."); UpdateStatus?.Invoke($"SCSI device type: {_dev.ScsiType}."); UpdateStatus?.Invoke($"Media identified as {dskType}."); UpdateStatus?.Invoke($"Media part number is {mediaPartNumber}."); _dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize); _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.", 2048); _dumpLog.WriteLine("SCSI device type: {0}.", _dev.ScsiType); _dumpLog.WriteLine("Media identified as {0}.", dskType); _dumpLog.WriteLine("Media part number is {0}.", mediaPartNumber); bool ret; var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private); var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010); ret = _outputPlugin.Create(_outputPath, dskType, _formatOptions, blocks, blockSize); // Cannot create image if (!ret) { _dumpLog.WriteLine("Error creating output image, not continuing."); _dumpLog.WriteLine(_outputPlugin.ErrorMessage); StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } start = DateTime.UtcNow; double imageWriteDuration = 0; (_outputPlugin as IWritableOpticalImage)?.SetTracks(new List <Track> { new Track { TrackBytesPerSector = (int)blockSize, TrackEndSector = blocks - 1, TrackSequence = 1, TrackRawBytesPerSector = (int)blockSize, TrackSubchannelType = TrackSubchannelType.None, TrackSession = 1, TrackType = TrackType.Data } }); DumpHardwareType currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(true, _dev.IsRemovable, blocks, _dev.Manufacturer, _dev.Model, _dev.Serial, _dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision, _private); if (currentTry == null || extents == null) { StoppingErrorMessage?.Invoke("Could not process resume file, not continuing..."); return; } if (_resume.NextBlock > 0) { _dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock); } bool newTrim = false; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = _resume.NextBlock; i < blocks; i += blocksToRead) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } if (blocks - i < blocksToRead) { blocksToRead = (uint)(blocks - i); } if (currentSpeed > maxSpeed && currentSpeed > 0) { maxSpeed = currentSpeed; } if (currentSpeed < minSpeed && currentSpeed > 0) { minSpeed = currentSpeed; } UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(umdStart + (i * 4)), 512, 0, blocksToRead * 4, false, _dev.Timeout, out double 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 { _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf); // 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[blockSize * _skip], i, _skip); imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds; for (ulong b = i; b < i + _skip; b++) { _resume.BadBlocks.Add(b); } 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; newTrim = true; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)blockSize * (double)(blocks + 1)) / 1024 / imageWriteDuration:F3} KiB/sec."); _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 && _trim && newTrim) { start = DateTime.UtcNow; _dumpLog.WriteLine("Trimming skipped sectors"); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke($"Trimming sector {badSector}"); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(umdStart + (badSector * 4)), 512, 0, 4, false, _dev.Timeout, out double _); if (sense || _dev.Error) { _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf); continue; } _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); } EndProgress?.Invoke(); end = DateTime.UtcNow; _dumpLog.WriteLine("Trimming 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; Modes.ModePage?currentModePage = null; byte[] md6; if (_persistent) { Modes.ModePage_01 pg; sense = _dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout, out _); if (!sense) { Modes.DecodedMode?dcMode6 = Modes.DecodeMode6(readBuffer, _dev.ScsiType); if (dcMode6.HasValue) { foreach (Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage.Page == 0x01 && modePage.Subpage == 0x00)) { currentModePage = modePage; } } } if (currentModePage == null) { pg = new Modes.ModePage_01 { PS = false, AWRE = true, ARRE = true, TB = false, RC = false, EER = true, PER = false, DTE = true, DCR = false, ReadRetryCount = 32 }; currentModePage = new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) }; } pg = new Modes.ModePage_01 { PS = false, AWRE = false, ARRE = false, TB = true, RC = false, EER = true, PER = false, DTE = false, DCR = false, ReadRetryCount = 255 }; var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) } } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks)."); sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _); if (sense) { UpdateStatus?. Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf)); _dumpLog. WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); } else { runningPersistent = true; } } InitProgress?.Invoke(); repeatRetry: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?. Invoke($"Retrying sector {badSector}, pass {pass}, {(runningPersistent ? "recovering partial data, " : "")}{(forward ? "forward" : "reverse")}"); sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(umdStart + (badSector * 4)), 512, 0, 4, false, _dev.Timeout, out double cmdDuration); totalDuration += cmdDuration; if (sense || _dev.Error) { _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf); } if (!sense && !_dev.Error) { _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}."); _dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (runningPersistent) { _outputPlugin.WriteSector(readBuffer, badSector); } } if (pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0) { pass++; forward = !forward; _resume.BadBlocks.Sort(); if (!forward) { _resume.BadBlocks.Reverse(); } goto repeatRetry; } if (runningPersistent && currentModePage.HasValue) { var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { currentModePage.Value } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status)."); _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _); } EndProgress?.Invoke(); AaruConsole.WriteLine(); } #endregion Error handling _resume.BadBlocks.Sort(); foreach (ulong bad in _resume.BadBlocks) { _dumpLog.WriteLine("Sector {0} could not be read.", bad); } currentTry.Extents = ExtentsConverter.ToMetadata(extents); var metadata = new CommonTypes.Structs.ImageInfo { Application = "Aaru", ApplicationVersion = Version.GetVersion(), MediaPartNumber = mediaPartNumber }; if (!_outputPlugin.SetMetadata(metadata)) { ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine + _outputPlugin.ErrorMessage); } _outputPlugin.SetDumpHardware(_resume.Tries); if (_preSidecar != null) { _outputPlugin.SetCicmMetadata(_preSidecar); } _dumpLog.WriteLine("Closing output file."); UpdateStatus?.Invoke("Closing output file."); DateTime closeStart = DateTime.Now; _outputPlugin.Close(); DateTime closeEnd = DateTime.Now; _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds); if (_aborted) { UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); return; } double totalChkDuration = 0; if (_metadata) { WriteOpticalSidecar(blockSize, blocks, dskType, null, null, 1, out totalChkDuration, null); } UpdateStatus?.Invoke(""); UpdateStatus?. Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing)."); UpdateStatus?. Invoke($"Average speed: {((double)blockSize * (double)(blocks + 1)) / 1048576 / (totalDuration / 1000):F3} MiB/sec."); if (maxSpeed > 0) { UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec."); } if (minSpeed > 0 && minSpeed < double.MaxValue) { UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec."); } UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read."); UpdateStatus?.Invoke(""); Statistics.AddMedia(dskType, true); }
ScanResults SecureDigital() { var results = new ScanResults(); byte[] cmdBuf; bool sense; results.Blocks = 0; const uint timeout = 5; double duration; const ushort sdProfile = 0x0001; ushort blocksToRead = 128; uint blockSize = 512; bool byteAddressed = true; bool supportsCmd23 = false; switch (_dev.Type) { case DeviceType.MMC: { sense = _dev.ReadCsd(out cmdBuf, out _, timeout, out _); if (!sense) { CSD csd = Decoders.MMC.Decoders.DecodeCSD(cmdBuf); results.Blocks = (ulong)((csd.Size + 1) * Math.Pow(2, csd.SizeMultiplier + 2)); blockSize = (uint)Math.Pow(2, csd.ReadBlockLength); // Found at least since MMC System Specification 3.31 supportsCmd23 = csd.Version >= 3; if (csd.Size == 0xFFF) { sense = _dev.ReadExtendedCsd(out cmdBuf, out _, timeout, out _); if (!sense) { ExtendedCSD ecsd = Decoders.MMC.Decoders.DecodeExtendedCSD(cmdBuf); results.Blocks = ecsd.SectorCount; blockSize = (uint)(ecsd.SectorSize == 1 ? 4096 : 512); blocksToRead = (ushort)(ecsd.OptimalReadSize * 4096 / blockSize); if (blocksToRead == 0) { blocksToRead = 128; } // Supposing it's high-capacity MMC if it has Extended CSD... byteAddressed = false; } } } break; } case DeviceType.SecureDigital: { sense = _dev.ReadCsd(out cmdBuf, out _, timeout, out _); if (!sense) { Decoders.SecureDigital.CSD csd = Decoders.SecureDigital.Decoders.DecodeCSD(cmdBuf); results.Blocks = (ulong)(csd.Structure == 0 ? (csd.Size + 1) * Math.Pow(2, csd.SizeMultiplier + 2) : (csd.Size + 1) * 1024); blockSize = (uint)Math.Pow(2, csd.ReadBlockLength); // Structure >=1 for SDHC/SDXC, so that's block addressed byteAddressed = csd.Structure == 0; if (blockSize != 512) { uint ratio = blockSize / 512; results.Blocks *= ratio; blockSize = 512; } sense = _dev.ReadScr(out cmdBuf, out _, timeout, out _); if (!sense) { supportsCmd23 = Decoders.SecureDigital.Decoders.DecodeSCR(cmdBuf)?.CommandSupport. HasFlag(CommandSupport.SetBlockCount) ?? false; } } break; } } if (results.Blocks == 0) { StoppingErrorMessage?.Invoke("Unable to get device size."); return(results); } if (supportsCmd23) { sense = _dev.ReadWithBlockCount(out cmdBuf, out _, 0, blockSize, 1, byteAddressed, timeout, out duration); if (sense || _dev.Error) { UpdateStatus?. Invoke("Environment does not support setting block count, downgrading to OS reading."); supportsCmd23 = false; } // Need to restart device, otherwise is it just busy streaming data with no one listening sense = _dev.ReOpen(); if (sense) { StoppingErrorMessage?.Invoke($"Error {_dev.LastError} reopening device."); return(results); } } if (supportsCmd23) { while (true) { sense = _dev.ReadWithBlockCount(out cmdBuf, out _, 0, blockSize, blocksToRead, byteAddressed, timeout, out duration); if (sense) { blocksToRead /= 2; } if (!sense || blocksToRead == 1) { break; } } if (sense) { StoppingErrorMessage?. Invoke($"Device error {_dev.LastError} trying to guess ideal transfer length."); return(results); } } results.A = 0; // <3ms results.B = 0; // >=3ms, <10ms results.C = 0; // >=10ms, <50ms results.D = 0; // >=50ms, <150ms results.E = 0; // >=150ms, <500ms results.F = 0; // >=500ms results.Errored = 0; DateTime start; DateTime end; results.ProcessingTime = 0; double currentSpeed = 0; results.MaxSpeed = double.MinValue; results.MinSpeed = double.MaxValue; results.UnreadableSectors = new List <ulong>(); results.SeekMax = double.MinValue; results.SeekMin = double.MaxValue; results.SeekTotal = 0; const int seekTimes = 100; var rnd = new Random(); if (supportsCmd23 || blocksToRead == 1) { UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); } else if (_useBufferedReads) { UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time using OS buffered reads."); } else { UpdateStatus?.Invoke($"Reading {blocksToRead} sectors using sequential single commands."); } InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, sdProfile); var mhddLog = new MhddLog(_mhddLogPath, _dev, results.Blocks, blockSize, blocksToRead, false); var ibgLog = new IbgLog(_ibgLogPath, sdProfile); start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = 0; i < results.Blocks; i += blocksToRead) { if (_aborted) { break; } if (results.Blocks - i < blocksToRead) { blocksToRead = (byte)(results.Blocks - i); } if (currentSpeed > results.MaxSpeed && currentSpeed > 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed > 0) { results.MinSpeed = currentSpeed; } UpdateProgress?.Invoke($"Reading sector {i} of {results.Blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)results.Blocks); bool error; if (blocksToRead == 1) { error = _dev.ReadSingleBlock(out cmdBuf, out _, (uint)i, blockSize, byteAddressed, timeout, out duration); } else if (supportsCmd23) { error = _dev.ReadWithBlockCount(out cmdBuf, out _, (uint)i, blockSize, blocksToRead, byteAddressed, timeout, out duration); } else if (_useBufferedReads) { error = _dev.BufferedOsRead(out cmdBuf, (long)(i * blockSize), blockSize * blocksToRead, out duration); } else { error = _dev.ReadMultipleUsingSingle(out cmdBuf, out _, (uint)i, blockSize, blocksToRead, byteAddressed, timeout, out duration); } if (!error) { if (duration >= 500) { results.F += blocksToRead; } else if (duration >= 150) { results.E += blocksToRead; } else if (duration >= 50) { results.D += blocksToRead; } else if (duration >= 10) { results.C += blocksToRead; } else if (duration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(i, duration); mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); } else { ScanUnreadable?.Invoke(i); results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); } sectorSpeedStart += blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); ScanSpeed?.Invoke(i, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (double)(results.Blocks + 1) / 1024 / (results.ProcessingTime / 1000), _devicePath); InitProgress?.Invoke(); for (int i = 0; i < seekTimes; i++) { if (_aborted || !_seekTest) { break; } uint seekPos = (uint)rnd.Next((int)results.Blocks); PulseProgress?.Invoke($"Seeking to sector {seekPos}...\t\t"); _dev.ReadSingleBlock(out cmdBuf, out _, seekPos, blockSize, byteAddressed, timeout, out double seekCur); if (seekCur > results.SeekMax && seekCur > 0) { results.SeekMax = seekCur; } if (seekCur < results.SeekMin && seekCur > 0) { results.SeekMin = seekCur; } results.SeekTotal += seekCur; GC.Collect(); } EndProgress?.Invoke(); results.ProcessingTime /= 1000; results.TotalTime = (end - start).TotalSeconds; results.AvgSpeed = blockSize * (double)(results.Blocks + 1) / 1048576 / results.ProcessingTime; results.SeekTimes = seekTimes; return(results); }
/// <summary>Creates optical metadata sidecar</summary> /// <param name="blockSize">Size of the read sector in bytes</param> /// <param name="blocks">Total number of positive sectors</param> /// <param name="mediaType">Disc type</param> /// <param name="layers">Disc layers</param> /// <param name="mediaTags">Media tags</param> /// <param name="sessions">Disc sessions</param> /// <param name="totalChkDuration">Total time spent doing checksums</param> /// <param name="discOffset">Disc write offset</param> void WriteOpticalSidecar(uint blockSize, ulong blocks, MediaType mediaType, LayersType layers, Dictionary <MediaTagType, byte[]> mediaTags, int sessions, out double totalChkDuration, int?discOffset) { _dumpLog.WriteLine("Creating sidecar."); var filters = new FiltersList(); IFilter filter = filters.GetFilter(_outputPath); IMediaImage inputPlugin = ImageFormat.Detect(filter); totalChkDuration = 0; if (!inputPlugin.Open(filter)) { StoppingErrorMessage?.Invoke("Could not open created image."); return; } DateTime chkStart = DateTime.UtcNow; // ReSharper disable once UseObjectOrCollectionInitializer _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding); _sidecarClass.InitProgressEvent += InitProgress; _sidecarClass.UpdateProgressEvent += UpdateProgress; _sidecarClass.EndProgressEvent += EndProgress; _sidecarClass.InitProgressEvent2 += InitProgress2; _sidecarClass.UpdateProgressEvent2 += UpdateProgress2; _sidecarClass.EndProgressEvent2 += EndProgress2; _sidecarClass.UpdateStatusEvent += UpdateStatus; CICMMetadataType sidecar = _sidecarClass.Create(); DateTime end = DateTime.UtcNow; if (_aborted) { return; } 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)); if (_preSidecar != null) { _preSidecar.OpticalDisc = sidecar.OpticalDisc; sidecar = _preSidecar; } 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(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].Dimensions = Dimensions.DimensionsFromMediaType(mediaType); (string type, string subType)discType = CommonTypes.Metadata.MediaType.MediaTypeToString(mediaType); sidecar.OpticalDisc[0].DiscType = discType.type; sidecar.OpticalDisc[0].DiscSubType = discType.subType; sidecar.OpticalDisc[0].DumpHardwareArray = _resume.Tries.ToArray(); sidecar.OpticalDisc[0].Sessions = (uint)sessions; sidecar.OpticalDisc[0].Layers = layers; if (discOffset.HasValue) { sidecar.OpticalDisc[0].Offset = (int)(discOffset / 4); sidecar.OpticalDisc[0].OffsetSpecified = true; } if (mediaTags != null) { foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags.Where(tag => _outputPlugin. SupportedMediaTags. Contains(tag.Key))) { AddMediaTagToSidecar(_outputPath, tag, ref sidecar); } } UpdateStatus?.Invoke("Writing metadata sidecar"); var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); }
/// <summary>Dumps an optical disc</summary> void Mmc() { MediaType dskType = MediaType.Unknown; bool sense; byte[] tmpBuf; bool compactDisc = true; bool gotConfiguration = false; bool isXbox = false; DVDDecryption dvdDecrypt = null; _speedMultiplier = 1; // 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) { gotConfiguration = true; 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; _speedMultiplier = -1; goto default; case 0x0002: dskType = MediaType.PD650; _speedMultiplier = -1; 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; _speedMultiplier = 9; goto default; case 0x0011: dskType = MediaType.DVDR; _speedMultiplier = 9; goto default; case 0x0012: dskType = MediaType.DVDRAM; _speedMultiplier = 9; goto default; case 0x0013: case 0x0014: dskType = MediaType.DVDRW; _speedMultiplier = 9; goto default; case 0x0015: case 0x0016: dskType = MediaType.DVDRDL; _speedMultiplier = 9; goto default; case 0x0017: dskType = MediaType.DVDRWDL; _speedMultiplier = 9; goto default; case 0x0018: dskType = MediaType.DVDDownload; _speedMultiplier = 9; goto default; case 0x001A: dskType = MediaType.DVDPRW; _speedMultiplier = 9; goto default; case 0x001B: dskType = MediaType.DVDPR; _speedMultiplier = 9; 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; _speedMultiplier = 9; goto default; case 0x002B: dskType = MediaType.DVDPRDL; _speedMultiplier = 9; goto default; case 0x0040: dskType = MediaType.BDROM; _speedMultiplier = 30; goto default; case 0x0041: case 0x0042: dskType = MediaType.BDR; _speedMultiplier = 30; goto default; case 0x0043: dskType = MediaType.BDRE; _speedMultiplier = 30; goto default; case 0x0050: dskType = MediaType.HDDVDROM; _speedMultiplier = 30; goto default; case 0x0051: dskType = MediaType.HDDVDR; _speedMultiplier = 30; goto default; case 0x0052: dskType = MediaType.HDDVDRAM; _speedMultiplier = 30; goto default; case 0x0053: dskType = MediaType.HDDVDRW; _speedMultiplier = 30; goto default; case 0x0058: dskType = MediaType.HDDVDRDL; _speedMultiplier = 30; goto default; case 0x005A: dskType = MediaType.HDDVDRWDL; _speedMultiplier = 30; goto default; default: compactDisc = false; break; } } Modes.DecodedMode?decMode = null; sense = _dev.ModeSense6(out cmdBuf, out _, true, ScsiModeSensePageControl.Current, 0x00, _dev.Timeout, out _); if (sense || _dev.Error) { sense = _dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x00, _dev.Timeout, out _); if (!sense && !_dev.Error) { decMode = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } } else { decMode = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } if (decMode is null) { sense = _dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current, 0x3F, 0x00, _dev.Timeout, out _); if (sense || _dev.Error) { sense = _dev.ModeSense10(out cmdBuf, out _, false, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, _dev.Timeout, out _); if (sense || _dev.Error) { sense = _dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current, 0x00, 0x00, _dev.Timeout, out _); if (sense || _dev.Error) { sense = _dev.ModeSense10(out cmdBuf, out _, false, false, ScsiModeSensePageControl.Current, 0x00, 0x00, _dev.Timeout, out _); if (!sense && !_dev.Error) { decMode = Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } } else { decMode = Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } } else { decMode = Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } } else { decMode = Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice); } } if (decMode.HasValue && _dev.IsUsb && !gotConfiguration && (decMode.Value.Header.MediumType == MediumTypes.UnknownBlockDevice || decMode.Value.Header.MediumType == MediumTypes.ReadOnlyBlockDevice || decMode.Value.Header.MediumType == MediumTypes.ReadWriteBlockDevice)) { _speedMultiplier = -1; Sbc(null, MediaType.Unknown, false); return; } if (compactDisc) { _speedMultiplier *= 177; CompactDisc(); return; } _speedMultiplier *= 150; var scsiReader = new Reader(_dev, _dev.Timeout, null, _errorLog, _dumpRaw); ulong blocks = scsiReader.GetDeviceBlocks(); _dumpLog.WriteLine("Device reports disc has {0} blocks", blocks); Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); if (dskType == MediaType.PD650) { switch (blocks + 1) { case 1281856: dskType = MediaType.PD650_WORM; break; case 58620544: dskType = MediaType.REV120; break; case 17090880: dskType = MediaType.REV35; break; case 34185728: dskType = MediaType.REV70; break; } } #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, dskType); if (nintendoPfi?.DiskCategory == DiskCategory.Nintendo && nintendoPfi.Value.PartVersion == 15) { _dumpLog.WriteLine("Dumping Nintendo GameCube or Wii discs is not yet implemented."); StoppingErrorMessage?. Invoke("Dumping Nintendo GameCube or Wii discs is not yet implemented."); return; } } 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, dskType).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, dskType).Value; UpdateStatus?.Invoke($"PFI:\n{PFI.Prettify(decPfi)}"); // False book types 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 >= 15 ? 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 + 1 == 25063 || // Locked (or non compatible drive) blocks + 1 == 4229664 || // Xtreme unlock blocks + 1 == 4246304) // Wxripper unlock { dskType = MediaType.XGD3; } } isXbox = true; 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."); StoppingErrorMessage?. Invoke("Dumping Xbox Game Discs requires a drive with Kreon firmware."); if (!_force) { return; } isXbox = false; } if (_dumpRaw && !_force) { StoppingErrorMessage?. Invoke("Not continuing. If you want to continue reading cooked data when raw is not available use the force option."); // TODO: Exit more gracefully return; } } 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); CSS_CPRM.LeadInCopyright?cmi = CSS_CPRM.DecodeLeadInCopyright(cmdBuf); if (cmi !.Value.CopyrightType == CopyrightType.NoProtection) { UpdateStatus?.Invoke("Drive reports no copy protection on disc."); } else { if (!Settings.Settings.Current.EnableDecryption) { UpdateStatus?.Invoke("Drive reports the disc uses copy protection. " + "The dump will be incorrect unless decryption is enabled."); } else { if (cmi !.Value.CopyrightType == CopyrightType.CSS) { UpdateStatus?.Invoke("Drive reports disc uses CSS copy protection."); dvdDecrypt = new DVDDecryption(_dev); sense = dvdDecrypt.ReadBusKey(out cmdBuf, out _, CSS_CPRM.DecodeLeadInCopyright(cmdBuf) !.Value. CopyrightType, _dev.Timeout, out _); if (!sense) { byte[] busKey = cmdBuf; UpdateStatus?.Invoke("Reading disc key."); sense = dvdDecrypt.ReadDiscKey(out cmdBuf, out _, _dev.Timeout, out _); if (!sense) { CSS_CPRM.DiscKey?decodedDiscKey = CSS.DecodeDiscKey(cmdBuf, busKey); sense = dvdDecrypt.ReadAsf(out cmdBuf, out _, DvdCssKeyClass.DvdCssCppmOrCprm, _dev.Timeout, out _); if (!sense) { if (cmdBuf[7] == 1) { UpdateStatus?.Invoke("Disc and drive authentication succeeded."); sense = dvdDecrypt.ReadRpc(out cmdBuf, out _, DvdCssKeyClass.DvdCssCppmOrCprm, _dev.Timeout, out _); if (!sense) { CSS_CPRM.RegionalPlaybackControlState?rpc = CSS_CPRM.DecodeRegionalPlaybackControlState(cmdBuf); if (rpc.HasValue) { UpdateStatus?.Invoke(CSS.CheckRegion(rpc.Value, cmi.Value) ? "Disc and drive regions match." : "Disc and drive regions do not match. The dump will be incorrect"); } } if (decodedDiscKey.HasValue) { mediaTags.Add(MediaTagType.DVD_DiscKey, decodedDiscKey.Value.Key); UpdateStatus?.Invoke("Decrypting disc key."); CSS.DecryptDiscKey(decodedDiscKey.Value.Key, out byte[] discKey); if (discKey != null) { UpdateStatus?.Invoke("Decryption of disc key succeeded."); mediaTags.Add(MediaTagType.DVD_DiscKey_Decrypted, discKey); } else { UpdateStatus?.Invoke("Decryption of disc key failed."); } } } } } } } else { UpdateStatus?. Invoke($"Drive reports disc uses {CSS_CPRM.DecodeLeadInCopyright(cmdBuf)!.Value.CopyrightType.ToString()} copy protection. " + "This is not yet supported and the dump will be incorrect."); } } }
ScanResults Nvme() { StoppingErrorMessage?.Invoke("NVMe devices not yet supported."); return(default);
/// <summary>Dumps a MultiMediaCard or SecureDigital flash card</summary> public void SecureDigital() { if (_dumpRaw) { if (_force) { ErrorMessage?. Invoke("Raw dumping is not supported in MultiMediaCard or SecureDigital devices. Continuing..."); } else { StoppingErrorMessage?. Invoke("Raw dumping is not supported in MultiMediaCard or SecureDigital devices. 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; uint physicalBlockSize = 0; bool byteAddressed = true; Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>(); switch (_dev.Type) { case DeviceType.MMC: { UpdateStatus?.Invoke("Reading Extended CSD"); _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; } UpdateStatus?.Invoke("Reading CSD"); _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; } UpdateStatus?.Invoke("Reading OCR"); _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: { UpdateStatus?.Invoke("Reading CSD"); _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; } UpdateStatus?.Invoke("Reading OCR"); _dumpLog.WriteLine("Reading OCR"); sense = _dev.ReadSdocr(out ocr, out _, TIMEOUT, out duration); if (sense) { ocr = null; } else { mediaTags.Add(MediaTagType.SD_OCR, null); } UpdateStatus?.Invoke("Reading SCR"); _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; } } UpdateStatus?.Invoke("Reading CID"); _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; if (blocks == 0) { _dumpLog.WriteLine("Unable to get device size."); StoppingErrorMessage?.Invoke("Unable to get device size."); return; } UpdateStatus?.Invoke($"Device reports {blocks} blocks."); _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); StoppingErrorMessage?.Invoke($"Device error {_dev.LastError} trying to guess ideal transfer length."); return; } UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time."); _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) { StoppingErrorMessage?.Invoke("Could not process resume file, not continuing..."); return; } 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}."); ErrorMessage?.Invoke($"Output format does not support {tag}."); } if (!ret) { if (_force) { _dumpLog.WriteLine("Several media tags not supported, continuing..."); ErrorMessage?.Invoke("Several media tags not supported, continuing..."); } else { _dumpLog.WriteLine("Several media tags not supported, not continuing..."); StoppingErrorMessage?.Invoke("Several media tags not supported, not continuing..."); return; } } var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead); var 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); StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } if (_resume.NextBlock > 0) { UpdateStatus?.Invoke($"Resuming from block {_resume.NextBlock}."); _dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock); } start = DateTime.UtcNow; double imageWriteDuration = 0; bool newTrim = false; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = _resume.NextBlock; i < blocks; i += blocksToRead) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _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 UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)blocks); 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; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * blockSize) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.Now; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)blockSize * (double)(blocks + 1)) / 1024 / imageWriteDuration:F3} KiB/sec."); _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 && _trim && newTrim) { start = DateTime.UtcNow; UpdateStatus?.Invoke("Trimming bad sectors"); _dumpLog.WriteLine("Trimming bad sectors"); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke($"Trimming sector {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); } EndProgress?.Invoke(); end = DateTime.UtcNow; UpdateStatus?.Invoke($"Trimmming finished in {(end - start).TotalSeconds} seconds."); _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; InitProgress?.Invoke(); repeatRetryLba: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass, forward ? "forward" : "reverse", runningPersistent ? "recovering partial data, " : "")); 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); UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}."); _dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (runningPersistent) { _outputPlugin.WriteSector(cmdBuf, badSector); } } if (pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0) { pass++; forward = !forward; _resume.BadBlocks.Sort(); _resume.BadBlocks.Reverse(); goto repeatRetryLba; } EndProgress?.Invoke(); } #endregion Error handling currentTry.Extents = ExtentsConverter.ToMetadata(extents); _outputPlugin.SetDumpHardware(_resume.Tries); if (_preSidecar != null) { _outputPlugin.SetCicmMetadata(_preSidecar); } _dumpLog.WriteLine("Closing output file."); UpdateStatus?.Invoke("Closing output file."); DateTime closeStart = DateTime.Now; _outputPlugin.Close(); DateTime closeEnd = DateTime.Now; UpdateStatus?.Invoke($"Closed in {(closeEnd - closeStart).TotalSeconds} seconds."); _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds); if (_aborted) { UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); return; } double totalChkDuration = 0; if (_metadata) { UpdateStatus?.Invoke("Creating sidecar."); _dumpLog.WriteLine("Creating sidecar."); var filters = new FiltersList(); IFilter filter = filters.GetFilter(_outputPath); IMediaImage inputPlugin = ImageFormat.Detect(filter); if (!inputPlugin.Open(filter)) { StoppingErrorMessage?.Invoke("Could not open created image."); } DateTime chkStart = DateTime.UtcNow; _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding); _sidecarClass.InitProgressEvent += InitProgress; _sidecarClass.UpdateProgressEvent += UpdateProgress; _sidecarClass.EndProgressEvent += EndProgress; _sidecarClass.InitProgressEvent2 += InitProgress2; _sidecarClass.UpdateProgressEvent2 += UpdateProgress2; _sidecarClass.EndProgressEvent2 += EndProgress2; _sidecarClass.UpdateStatusEvent += UpdateStatus; CICMMetadataType sidecar = _sidecarClass.Create(); 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 = (ulong)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."); StoppingErrorMessage?.Invoke("Cannot write CID to output image." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } } if (csd != null) { csdDump = new DumpType { Image = _outputPath, Size = (ulong)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."); StoppingErrorMessage?.Invoke("Cannot write CSD to output image." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } } if (ecsd != null) { sidecar.BlockMedia[0].MultiMediaCard.ExtendedCSD = new DumpType { Image = _outputPath, Size = (ulong)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."); StoppingErrorMessage?.Invoke("Cannot write Extended CSD to output image." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } } if (ocr != null) { ocrDump = new DumpType { Image = _outputPath, Size = (ulong)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."); StoppingErrorMessage?.Invoke("Cannot write OCR to output image." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } } if (scr != null) { sidecar.BlockMedia[0].SecureDigital.SCR = new DumpType { Image = _outputPath, Size = (ulong)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."); StoppingErrorMessage?.Invoke("Cannot write SCR to output image." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } } 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; UpdateStatus?.Invoke($"Sidecar created in {(end - chkStart).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average checksum speed {((double)blockSize * (double)(blocks + 1)) / 1024 / (totalChkDuration / 1000):F3} KiB/sec."); _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 type, string subType)xmlType = (null, null); switch (_dev.Type) { case DeviceType.MMC: xmlType = CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.MMC); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.MMC); break; case DeviceType.SecureDigital: CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.SecureDigital); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.SecureDigital); break; } sidecar.BlockMedia[0].DiskType = xmlType.type; sidecar.BlockMedia[0].DiskSubType = xmlType.subType; // TODO: Implement device firmware revision sidecar.BlockMedia[0].LogicalBlocks = blocks; sidecar.BlockMedia[0].PhysicalBlockSize = physicalBlockSize > 0 ? physicalBlockSize : blockSize; sidecar.BlockMedia[0].LogicalBlockSize = blockSize; sidecar.BlockMedia[0].Manufacturer = _dev.Manufacturer; sidecar.BlockMedia[0].Model = _dev.Model; sidecar.BlockMedia[0].Serial = _dev.Serial; sidecar.BlockMedia[0].Size = blocks * blockSize; UpdateStatus?.Invoke("Writing metadata sidecar"); var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); } UpdateStatus?.Invoke(""); UpdateStatus?. Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing)."); UpdateStatus?. Invoke($"Average speed: {((double)blockSize * (double)(blocks + 1)) / 1048576 / (totalDuration / 1000):F3} MiB/sec."); UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec."); UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec."); UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read."); UpdateStatus?.Invoke(""); if (_resume.BadBlocks.Count > 0) { _resume.BadBlocks.Sort(); } switch (_dev.Type) { case DeviceType.MMC: Statistics.AddMedia(MediaType.MMC, true); break; case DeviceType.SecureDigital: Statistics.AddMedia(MediaType.SecureDigital, true); break; } }
ScanResults SecureDigital() { ScanResults results = new ScanResults(); byte[] cmdBuf; bool sense; results.Blocks = 0; const uint TIMEOUT = 5; double duration; const ushort SD_PROFILE = 0x0001; uint blocksToRead = 128; uint blockSize = 512; bool byteAddressed = true; switch (dev.Type) { case DeviceType.MMC: { sense = dev.ReadExtendedCsd(out cmdBuf, out _, TIMEOUT, out _); if (!sense) { ExtendedCSD ecsd = Decoders.MMC.Decoders.DecodeExtendedCSD(cmdBuf); blocksToRead = ecsd.OptimalReadSize; results.Blocks = ecsd.SectorCount; blockSize = (uint)(ecsd.SectorSize == 1 ? 4096 : 512); // Supposing it's high-capacity MMC if it has Extended CSD... byteAddressed = false; } if (sense || results.Blocks == 0) { sense = dev.ReadCsd(out cmdBuf, out _, TIMEOUT, out _); if (!sense) { CSD csd = Decoders.MMC.Decoders.DecodeCSD(cmdBuf); results.Blocks = (ulong)((csd.Size + 1) * Math.Pow(2, csd.SizeMultiplier + 2)); blockSize = (uint)Math.Pow(2, csd.ReadBlockLength); } } break; } case DeviceType.SecureDigital: { sense = dev.ReadCsd(out cmdBuf, out _, TIMEOUT, out _); if (!sense) { Decoders.SecureDigital.CSD csd = Decoders.SecureDigital.Decoders.DecodeCSD(cmdBuf); results.Blocks = (ulong)(csd.Structure == 0 ? (csd.Size + 1) * Math.Pow(2, csd.SizeMultiplier + 2) : (csd.Size + 1) * 1024); blockSize = (uint)Math.Pow(2, csd.ReadBlockLength); // Structure >=1 for SDHC/SDXC, so that's block addressed byteAddressed = csd.Structure == 0; } break; } } if (results.Blocks == 0) { StoppingErrorMessage?.Invoke("Unable to get device size."); return(results); } while (true) { sense = dev.Read(out cmdBuf, out _, 0, blockSize, blocksToRead, byteAddressed, TIMEOUT, out duration); if (sense) { blocksToRead /= 2; } if (!sense || blocksToRead == 1) { break; } } if (sense) { StoppingErrorMessage?.Invoke($"Device error {dev.LastError} trying to guess ideal transfer length."); return(results); } results.A = 0; // <3ms results.B = 0; // >=3ms, <10ms results.C = 0; // >=10ms, <50ms results.D = 0; // >=50ms, <150ms results.E = 0; // >=150ms, <500ms results.F = 0; // >=500ms results.Errored = 0; DateTime start; DateTime end; results.ProcessingTime = 0; double currentSpeed = 0; results.MaxSpeed = double.MinValue; results.MinSpeed = double.MaxValue; results.UnreadableSectors = new List <ulong>(); results.SeekMax = double.MinValue; results.SeekMin = double.MaxValue; results.SeekTotal = 0; const int SEEK_TIMES = 1000; Random rnd = new Random(); UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time."); InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, SD_PROFILE); MhddLog mhddLog = new MhddLog(mhddLogPath, dev, results.Blocks, blockSize, blocksToRead); IbgLog ibgLog = new IbgLog(ibgLogPath, SD_PROFILE); start = DateTime.UtcNow; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = 0; i < results.Blocks; i += blocksToRead) { if (aborted) { break; } if (results.Blocks - i < blocksToRead) { blocksToRead = (byte)(results.Blocks - i); } #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (currentSpeed > results.MaxSpeed && currentSpeed != 0) { results.MaxSpeed = currentSpeed; } if (currentSpeed < results.MinSpeed && currentSpeed != 0) { results.MinSpeed = currentSpeed; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator UpdateProgress?.Invoke($"Reading sector {i} of {results.Blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, (long)results.Blocks); bool error = dev.Read(out cmdBuf, out _, (uint)i, blockSize, blocksToRead, byteAddressed, TIMEOUT, out duration); if (!error) { if (duration >= 500) { results.F += blocksToRead; } else if (duration >= 150) { results.E += blocksToRead; } else if (duration >= 50) { results.D += blocksToRead; } else if (duration >= 10) { results.C += blocksToRead; } else if (duration >= 3) { results.B += blocksToRead; } else { results.A += blocksToRead; } ScanTime?.Invoke(i, duration); mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); } else { ScanUnreadable?.Invoke(i); results.Errored += blocksToRead; for (ulong b = i; b < i + blocksToRead; b++) { results.UnreadableSectors.Add(b); } mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); } sectorSpeedStart += blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed); ScanSpeed?.Invoke(i, currentSpeed * 1024); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, blockSize * (double)(results.Blocks + 1) / 1024 / (results.ProcessingTime / 1000), devicePath); InitProgress?.Invoke(); for (int i = 0; i < SEEK_TIMES; i++) { if (aborted) { break; } uint seekPos = (uint)rnd.Next((int)results.Blocks); PulseProgress?.Invoke($"Seeking to sector {seekPos}...\t\t"); dev.Read(out cmdBuf, out _, seekPos, blockSize, blocksToRead, byteAddressed, TIMEOUT, out double seekCur); #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator if (seekCur > results.SeekMax && seekCur != 0) { results.SeekMax = seekCur; } if (seekCur < results.SeekMin && seekCur != 0) { results.SeekMin = seekCur; } #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator results.SeekTotal += seekCur; GC.Collect(); } EndProgress?.Invoke(); results.ProcessingTime /= 1000; results.TotalTime = (end - start).TotalSeconds; results.AvgSpeed = blockSize * (double)(results.Blocks + 1) / 1048576 / results.ProcessingTime; results.SeekTimes = SEEK_TIMES; return(results); }
/// <summary>Dumps a CFW PlayStation Portable UMD</summary> void PlayStationPortable() { if (!_outputPlugin.SupportedMediaTypes.Contains(MediaType.MemoryStickDuo) && !_outputPlugin.SupportedMediaTypes.Contains(MediaType.MemoryStickProDuo) && !_outputPlugin.SupportedMediaTypes.Contains(MediaType.UMD)) { _dumpLog.WriteLine("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump..."); StoppingErrorMessage?. Invoke("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump..."); return; } UpdateStatus?.Invoke("Checking if media is UMD or MemoryStick..."); _dumpLog.WriteLine("Checking if media is UMD or MemoryStick..."); bool sense = _dev.ModeSense6(out byte[] buffer, out _, false, ScsiModeSensePageControl.Current, 0, _dev.Timeout, out _); if (sense) { _dumpLog.WriteLine("Could not get MODE SENSE..."); StoppingErrorMessage?.Invoke("Could not get MODE SENSE..."); return; } Modes.DecodedMode?decoded = Modes.DecodeMode6(buffer, PeripheralDeviceTypes.DirectAccess); if (!decoded.HasValue) { _dumpLog.WriteLine("Could not decode MODE SENSE..."); StoppingErrorMessage?.Invoke("Could not decode MODE SENSE..."); return; } // UMDs are always write protected if (!decoded.Value.Header.WriteProtected) { DumpMs(); return; } sense = _dev.Read12(out buffer, out _, 0, false, true, false, false, 0, 512, 0, 1, false, _dev.Timeout, out _); if (sense) { _dumpLog.WriteLine("Could not read..."); StoppingErrorMessage?.Invoke("Could not read..."); return; } byte[] tmp = new byte[8]; Array.Copy(buffer, 0x36, tmp, 0, 8); // UMDs are stored inside a FAT16 volume if (!tmp.SequenceEqual(_fatSignature)) { DumpMs(); return; } ushort fatStart = (ushort)((buffer[0x0F] << 8) + buffer[0x0E]); ushort sectorsPerFat = (ushort)((buffer[0x17] << 8) + buffer[0x16]); ushort rootStart = (ushort)((sectorsPerFat * 2) + fatStart); UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}..."); _dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart); sense = _dev.Read12(out buffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false, _dev.Timeout, out _); if (sense) { StoppingErrorMessage?.Invoke("Could not read..."); _dumpLog.WriteLine("Could not read..."); return; } tmp = new byte[3]; Array.Copy(buffer, 0x28, tmp, 0, 3); if (!tmp.SequenceEqual(_isoExtension)) { DumpMs(); return; } UpdateStatus?.Invoke($"FAT starts at sector {fatStart} and runs for {sectorsPerFat} sectors..."); _dumpLog.WriteLine("FAT starts at sector {0} and runs for {1} sectors...", fatStart, sectorsPerFat); UpdateStatus?.Invoke("Reading FAT..."); _dumpLog.WriteLine("Reading FAT..."); byte[] fat = new byte[sectorsPerFat * 512]; uint position = 0; while (position < sectorsPerFat) { uint transfer = 64; if (transfer + position > sectorsPerFat) { transfer = sectorsPerFat - position; } sense = _dev.Read12(out buffer, out _, 0, false, true, false, false, position + fatStart, 512, 0, transfer, false, _dev.Timeout, out _); if (sense) { StoppingErrorMessage?.Invoke("Could not read..."); _dumpLog.WriteLine("Could not read..."); return; } Array.Copy(buffer, 0, fat, position * 512, transfer * 512); position += transfer; } UpdateStatus?.Invoke("Traversing FAT..."); _dumpLog.WriteLine("Traversing FAT..."); ushort previousCluster = BitConverter.ToUInt16(fat, 4); for (int i = 3; i < fat.Length / 2; i++) { ushort nextCluster = BitConverter.ToUInt16(fat, i * 2); if (nextCluster == previousCluster + 1) { previousCluster = nextCluster; continue; } if (nextCluster == 0xFFFF) { break; } DumpMs(); return; } if (_outputPlugin is IWritableOpticalImage) { DumpUmd(); } else { StoppingErrorMessage?.Invoke("The specified plugin does not support storing optical disc images."); } }
void DumpMs() { const ushort SBC_PROFILE = 0x0001; const uint BLOCK_SIZE = 512; double totalDuration = 0; double currentSpeed = 0; double maxSpeed = double.MinValue; double minSpeed = double.MaxValue; uint blocksToRead = 64; DateTime start; DateTime end; MediaType dskType; bool sense; sense = _dev.ReadCapacity(out byte[] readBuffer, out _, _dev.Timeout, out _); if (sense) { _dumpLog.WriteLine("Could not detect capacity..."); StoppingErrorMessage?.Invoke("Could not detect capacity..."); return; } uint blocks = (uint)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]); blocks++; UpdateStatus?. Invoke($"Media has {blocks} blocks of {BLOCK_SIZE} bytes/each. (for a total of {blocks * (ulong)BLOCK_SIZE} bytes)"); if (blocks == 0) { _dumpLog.WriteLine("ERROR: Unable to read medium or empty medium present..."); StoppingErrorMessage?.Invoke("Unable to read medium or empty medium present..."); return; } UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * BLOCK_SIZE} bytes)."); UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time."); UpdateStatus?.Invoke($"Device reports {BLOCK_SIZE} bytes per logical block."); UpdateStatus?.Invoke($"SCSI device type: {_dev.ScsiType}."); if (blocks > 262144) { dskType = MediaType.MemoryStickProDuo; _dumpLog.WriteLine("Media detected as MemoryStick Pro Duo..."); UpdateStatus?.Invoke("Media detected as MemoryStick Pro Duo..."); } else { dskType = MediaType.MemoryStickDuo; _dumpLog.WriteLine("Media detected as MemoryStick Duo..."); UpdateStatus?.Invoke("Media detected as MemoryStick Duo..."); } bool ret; var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, BLOCK_SIZE, blocksToRead, _private); var ibgLog = new IbgLog(_outputPrefix + ".ibg", SBC_PROFILE); 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); StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine + _outputPlugin.ErrorMessage); return; } start = DateTime.UtcNow; double imageWriteDuration = 0; DumpHardwareType currentTry = null; ExtentsULong extents = null; ResumeSupport.Process(true, _dev.IsRemovable, blocks, _dev.Manufacturer, _dev.Model, _dev.Serial, _dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision, _private); if (currentTry == null || extents == null) { StoppingErrorMessage?.Invoke("Could not process resume file, not continuing..."); return; } if (_resume.NextBlock > 0) { _dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock); } bool newTrim = false; DateTime timeSpeedStart = DateTime.UtcNow; ulong sectorSpeedStart = 0; InitProgress?.Invoke(); for (ulong i = _resume.NextBlock; i < blocks; i += blocksToRead) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } if (blocks - i < blocksToRead) { blocksToRead = (uint)(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 UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, blocks); sense = _dev.Read12(out readBuffer, out _, 0, false, true, false, false, (uint)i, BLOCK_SIZE, 0, blocksToRead, false, _dev.Timeout, out double 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); } 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; newTrim = true; } sectorSpeedStart += blocksToRead; _resume.NextBlock = i + blocksToRead; double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds; if (elapsed < 1) { continue; } currentSpeed = (sectorSpeedStart * BLOCK_SIZE) / (1048576 * elapsed); sectorSpeedStart = 0; timeSpeedStart = DateTime.UtcNow; } end = DateTime.UtcNow; EndProgress?.Invoke(); mhddLog.Close(); ibgLog.Close(_dev, blocks, BLOCK_SIZE, (end - start).TotalSeconds, currentSpeed * 1024, (BLOCK_SIZE * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average dump speed {((double)BLOCK_SIZE * (double)(blocks + 1)) / 1024 / (totalDuration / 1000):F3} KiB/sec."); UpdateStatus?. Invoke($"Average write speed {((double)BLOCK_SIZE * (double)(blocks + 1)) / 1024 / imageWriteDuration:F3} KiB/sec."); _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 && _trim && newTrim) { start = DateTime.UtcNow; UpdateStatus?.Invoke("Trimming skipped sectors"); _dumpLog.WriteLine("Trimming skipped sectors"); ulong[] tmpArray = _resume.BadBlocks.ToArray(); InitProgress?.Invoke(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke($"Trimming sector {badSector}"); sense = _dev.Read12(out readBuffer, out _, 0, false, true, false, false, (uint)badSector, BLOCK_SIZE, 0, 1, false, _dev.Timeout, out double cmdDuration); if (sense || _dev.Error) { continue; } _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); } EndProgress?.Invoke(); 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; Modes.ModePage?currentModePage = null; byte[] md6; if (_persistent) { Modes.ModePage_01 pg; 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, _dev.ScsiType); 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, _dev.ScsiType); if (dcMode6.HasValue) { foreach (Modes.ModePage modePage in dcMode6.Value.Pages) { if (modePage.Page == 0x01 && modePage.Subpage == 0x00) { currentModePage = modePage; } } } } if (currentModePage == null) { pg = new Modes.ModePage_01 { PS = false, AWRE = true, ARRE = true, TB = false, RC = false, EER = true, PER = false, DTE = true, DCR = false, ReadRetryCount = 32 }; currentModePage = new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) }; } pg = new Modes.ModePage_01 { PS = false, AWRE = false, ARRE = false, TB = true, RC = false, EER = true, PER = false, DTE = false, DCR = false, ReadRetryCount = 255 }; var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { new Modes.ModePage { Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) } } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks)."); _dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks)."); sense = _dev.ModeSelect(md6, out byte[] senseBuf, true, false, _dev.Timeout, out _); if (sense) { UpdateStatus?. Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf)); _dumpLog. WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive."); } else { runningPersistent = true; } } InitProgress?.Invoke(); repeatRetry: ulong[] tmpArray = _resume.BadBlocks.ToArray(); foreach (ulong badSector in tmpArray) { if (_aborted) { currentTry.Extents = ExtentsConverter.ToMetadata(extents); _dumpLog.WriteLine("Aborted!"); break; } PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass, forward ? "forward" : "reverse", runningPersistent ? "recovering partial data, " : "")); sense = _dev.Read12(out readBuffer, out _, 0, false, true, false, false, (uint)badSector, BLOCK_SIZE, 0, 1, false, _dev.Timeout, out double cmdDuration); totalDuration += cmdDuration; if (!sense && !_dev.Error) { _resume.BadBlocks.Remove(badSector); extents.Add(badSector); _outputPlugin.WriteSector(readBuffer, badSector); UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}."); _dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } else if (runningPersistent) { _outputPlugin.WriteSector(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) { var md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), Pages = new[] { currentModePage.Value } }; md6 = Modes.EncodeMode6(md, _dev.ScsiType); UpdateStatus?.Invoke("Sending MODE SELECT to drive (return device to previous status)."); _dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status)."); sense = _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _); } EndProgress?.Invoke(); } #endregion Error handling _resume.BadBlocks.Sort(); foreach (ulong bad in _resume.BadBlocks) { _dumpLog.WriteLine("Sector {0} could not be read.", bad); } currentTry.Extents = ExtentsConverter.ToMetadata(extents); var metadata = new CommonTypes.Structs.ImageInfo { Application = "Aaru", ApplicationVersion = Version.GetVersion() }; if (!_outputPlugin.SetMetadata(metadata)) { ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine + _outputPlugin.ErrorMessage); } _outputPlugin.SetDumpHardware(_resume.Tries); if (_preSidecar != null) { _outputPlugin.SetCicmMetadata(_preSidecar); } _dumpLog.WriteLine("Closing output file."); UpdateStatus?.Invoke("Closing output file."); DateTime closeStart = DateTime.Now; _outputPlugin.Close(); DateTime closeEnd = DateTime.Now; UpdateStatus?.Invoke($"Closed in {(closeEnd - closeStart).TotalSeconds} seconds."); _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds); if (_aborted) { UpdateStatus?.Invoke("Aborted!"); _dumpLog.WriteLine("Aborted!"); return; } double totalChkDuration = 0; if (_metadata) { UpdateStatus?.Invoke("Creating sidecar."); _dumpLog.WriteLine("Creating sidecar."); var filters = new FiltersList(); IFilter filter = filters.GetFilter(_outputPath); IMediaImage inputPlugin = ImageFormat.Detect(filter); if (!inputPlugin.Open(filter)) { StoppingErrorMessage?.Invoke("Could not open created image."); return; } DateTime chkStart = DateTime.UtcNow; _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding); _sidecarClass.InitProgressEvent += InitProgress; _sidecarClass.UpdateProgressEvent += UpdateProgress; _sidecarClass.EndProgressEvent += EndProgress; _sidecarClass.InitProgressEvent2 += InitProgress2; _sidecarClass.UpdateProgressEvent2 += UpdateProgress2; _sidecarClass.EndProgressEvent2 += EndProgress2; _sidecarClass.UpdateStatusEvent += UpdateStatus; CICMMetadataType sidecar = _sidecarClass.Create(); end = DateTime.UtcNow; totalChkDuration = (end - chkStart).TotalMilliseconds; UpdateStatus?.Invoke($"Sidecar created in {(end - chkStart).TotalSeconds} seconds."); UpdateStatus?. Invoke($"Average checksum speed {((double)BLOCK_SIZE * (double)(blocks + 1)) / 1024 / (totalChkDuration / 1000):F3} KiB/sec."); _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)); if (_preSidecar != null) { _preSidecar.BlockMedia = sidecar.BlockMedia; sidecar = _preSidecar; } 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(partition.StartSector, fileSystem.Type)); } if (filesystems.Count > 0) { foreach (var filesystem in filesystems.Select(o => new { o.start, o.type }).Distinct()) { UpdateStatus?.Invoke($"Found filesystem {filesystem.type} at sector {filesystem.start}"); _dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start); } } sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); (string type, string subType)xmlType = CommonTypes.Metadata.MediaType.MediaTypeToString(dskType); sidecar.BlockMedia[0].DiskType = xmlType.type; sidecar.BlockMedia[0].DiskSubType = xmlType.subType; sidecar.BlockMedia[0].Interface = "USB"; sidecar.BlockMedia[0].LogicalBlocks = blocks; sidecar.BlockMedia[0].PhysicalBlockSize = (int)BLOCK_SIZE; sidecar.BlockMedia[0].LogicalBlockSize = (int)BLOCK_SIZE; sidecar.BlockMedia[0].Manufacturer = _dev.Manufacturer; sidecar.BlockMedia[0].Model = _dev.Model; if (!_private) { sidecar.BlockMedia[0].Serial = _dev.Serial; } sidecar.BlockMedia[0].Size = blocks * BLOCK_SIZE; if (_dev.IsRemovable) { sidecar.BlockMedia[0].DumpHardwareArray = _resume.Tries.ToArray(); } UpdateStatus?.Invoke("Writing metadata sidecar"); var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create); var xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); } UpdateStatus?.Invoke(""); UpdateStatus?. Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing)."); UpdateStatus?. Invoke($"Average speed: {((double)BLOCK_SIZE * (double)(blocks + 1)) / 1048576 / (totalDuration / 1000):F3} MiB/sec."); UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec."); UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec."); UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read."); UpdateStatus?.Invoke(""); Statistics.AddMedia(dskType, true); }