Пример #1
0
        /// <summary>Dumps a MultiMediaCard or SecureDigital flash card</summary>
        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 sdProfile = 0x0001;
            const uint   timeout   = 5;
            double       duration;
            uint         blocksToRead = 1;
            uint         blockSize    = 512;
            ulong        blocks       = 0;

            byte[] csd  = null;
            byte[] ocr  = null;
            byte[] ecsd = null;
            byte[] scr  = null;
            uint   physicalBlockSize = 0;
            bool   byteAddressed     = true;

            uint[] response;

            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 response, 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
                {
                    _errorLog?.WriteLine("Read eCSD", _dev.Error, _dev.LastError, response);
                    ecsd = null;
                }

                UpdateStatus?.Invoke("Reading CSD");
                _dumpLog.WriteLine("Reading CSD");
                sense = _dev.ReadCsd(out csd, out response, 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
                {
                    _errorLog?.WriteLine("Read CSD", _dev.Error, _dev.LastError, response);
                    csd = null;
                }

                UpdateStatus?.Invoke("Reading OCR");
                _dumpLog.WriteLine("Reading OCR");
                sense = _dev.ReadOcr(out ocr, out response, timeout, out duration);

                if (sense)
                {
                    _errorLog?.WriteLine("Read OCR", _dev.Error, _dev.LastError, response);
                    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 response, 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
                {
                    _errorLog?.WriteLine("Read CSD", _dev.Error, _dev.LastError, response);
                    csd = null;
                }

                UpdateStatus?.Invoke("Reading OCR");
                _dumpLog.WriteLine("Reading OCR");
                sense = _dev.ReadSdocr(out ocr, out response, timeout, out duration);

                if (sense)
                {
                    _errorLog?.WriteLine("Read OCR", _dev.Error, _dev.LastError, response);
                    ocr = null;
                }
                else
                {
                    mediaTags.Add(MediaTagType.SD_OCR, null);
                }

                UpdateStatus?.Invoke("Reading SCR");
                _dumpLog.WriteLine("Reading SCR");
                sense = _dev.ReadScr(out scr, out response, timeout, out duration);

                if (sense)
                {
                    _errorLog?.WriteLine("Read SCR", _dev.Error, _dev.LastError, response);
                    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 response, timeout, out duration);

            if (sense)
            {
                _errorLog?.WriteLine("Read CID", _dev.Error, _dev.LastError, response);
                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, _dev.FirmwareRevision, _private);

            if (currentTry == null ||
                extents == null)
            {
                StoppingErrorMessage?.Invoke("Could not process resume file, not continuing...");

                return;
            }

            bool ret = true;

            foreach (MediaTagType tag in mediaTags.Keys.Where(tag => !_outputPlugin.SupportedMediaTags.Contains(tag)))
            {
                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, _private);
            var ibgLog  = new IbgLog(_outputPrefix + ".ibg", sdProfile);

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

                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);

                error = _dev.Read(out cmdBuf, out response, (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
                {
                    _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, byteAddressed, response);

                    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}");

                    error = _dev.Read(out cmdBuf, out response, (uint)badSector, blockSize, 1, byteAddressed, timeout,
                                      out duration);

                    totalDuration += duration;

                    if (error)
                    {
                        _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, byteAddressed, response);

                        continue;
                    }

                    _resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    _outputPlugin.WriteSector(cmdBuf, 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;

                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 response, (uint)badSector, blockSize, 1, byteAddressed, timeout,
                                      out duration);

                    totalDuration += duration;

                    if (error)
                    {
                        _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, byteAddressed, response);
                    }

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

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

                    goto repeatRetryLba;
                }

                EndProgress?.Invoke();
            }
            #endregion Error handling

            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

            _outputPlugin.SetDumpHardware(_resume.Tries);

            // TODO: Drive info
            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.");
                }

                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)
                {
                    if (_dev.Type == DeviceType.SecureDigital && _private)
                    {
                        // Clear serial number and manufacturing date
                        cid[9]  = 0;
                        cid[10] = 0;
                        cid[11] = 0;
                        cid[12] = 0;
                        cid[13] = 0;
                        cid[14] = 0;
                    }
                    else if (_dev.Type == DeviceType.MMC && _private)
                    {
                        // Clear serial number and manufacturing date
                        cid[10] = 0;
                        cid[11] = 0;
                        cid[12] = 0;
                        cid[13] = 0;
                        cid[14] = 0;
                    }

                    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;

                if (!_private)
                {
                    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.");

            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("");

            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;
            }
        }
Пример #2
0
        ScanResults SecureDigital()
        {
            var 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;

            var rnd = new Random();

            UpdateStatus?.Invoke($"Reading {blocksToRead} sectors at a time.");

            InitBlockMap?.Invoke(results.Blocks, blockSize, blocksToRead, SD_PROFILE);
            var mhddLog = new MhddLog(_mhddLogPath, _dev, results.Blocks, blockSize, blocksToRead, false);
            var 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 || !_seekTest)
                {
                    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);
        }
Пример #3
0
        public static ScanResults Scan(string mhddLogPath, string ibgLogPath, string devicePath, Device dev)
        {
            ScanResults results = new ScanResults();
            bool        aborted;

            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)
            {
                DicConsole.ErrorWriteLine("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)
            {
                DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                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();

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

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

            MhddLog mhddLog = new MhddLog(mhddLogPath, dev, results.Blocks, blockSize, blocksToRead);
            IbgLog  ibgLog  = new IbgLog(ibgLogPath, SD_PROFILE);

            start = DateTime.UtcNow;
            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

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

                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;
                    }

                    mhddLog.Write(i, duration);
                    ibgLog.Write(i, currentSpeed * 1024);
                }
                else
                {
                    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);
                }

                double newSpeed = (double)blockSize * blocksToRead / 1048576 / (duration / 1000);
                if (!double.IsInfinity(newSpeed))
                {
                    currentSpeed = newSpeed;
                }
            }

            end = DateTime.UtcNow;
            DicConsole.WriteLine();
            mhddLog.Close();
            ibgLog.Close(dev, results.Blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
                         blockSize * (double)(results.Blocks + 1) / 1024 / (results.ProcessingTime / 1000), devicePath);

            for (int i = 0; i < SEEK_TIMES; i++)
            {
                if (aborted)
                {
                    break;
                }

                uint seekPos = (uint)rnd.Next((int)results.Blocks);

                DicConsole.Write("\rSeeking to sector {0}...\t\t", seekPos);

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

            DicConsole.WriteLine();

            results.ProcessingTime /= 1000;
            results.TotalTime       = (end - start).TotalSeconds;
            results.AvgSpeed        = blockSize * (double)(results.Blocks + 1) / 1048576 / results.ProcessingTime;
            results.SeekTimes       = SEEK_TIMES;

            return(results);
        }
Пример #4
0
        /// <summary>
        ///     Dumps a MultiMediaCard or SecureDigital flash card
        /// </summary>
        /// <param name="dev">Device</param>
        /// <param name="devicePath">Path to the device</param>
        /// <param name="outputPrefix">Prefix for output data files</param>
        /// <param name="outputPlugin">Plugin for output file</param>
        /// <param name="retryPasses">How many times to retry</param>
        /// <param name="force">Force to continue dump whenever possible</param>
        /// <param name="dumpRaw">Dump long or scrambled sectors</param>
        /// <param name="persistent">Store whatever data the drive returned on error</param>
        /// <param name="stopOnError">Stop dump on first error</param>
        /// <param name="resume">Information for dump resuming</param>
        /// <param name="dumpLog">Dump logger</param>
        /// <param name="encoding">Encoding to use when analyzing dump</param>
        /// <param name="outputPath">Path to output file</param>
        /// <param name="formatOptions">Formats to pass to output file plugin</param>
        /// <exception cref="ArgumentException">If you asked to dump long sectors from a SCSI Streaming device</exception>
        public static void Dump(Device dev, string devicePath,
                                IWritableImage outputPlugin, ushort retryPasses,
                                bool force, bool dumpRaw,
                                bool persistent, bool stopOnError, ref Resume resume,
                                ref DumpLog dumpLog, Encoding encoding,
                                string outputPrefix, string outputPath,
                                Dictionary <string, string> formatOptions, CICMMetadataType preSidecar,
                                uint skip,
                                bool nometadata, bool notrim)
        {
            bool aborted;

            if (dumpRaw)
            {
                DicConsole.ErrorWriteLine("Raw dumping is not supported in MultiMediaCard or SecureDigital devices.");

                if (force)
                {
                    DicConsole.ErrorWriteLine("Continuing...");
                }
                else
                {
                    DicConsole.ErrorWriteLine("Aborting...");
                    return;
                }
            }

            bool         sense;
            const ushort SD_PROFILE = 0x0001;
            const uint   TIMEOUT    = 5;
            double       duration;

            uint  blocksToRead = 128;
            uint  blockSize    = 512;
            ulong blocks       = 0;

            byte[] csd  = null;
            byte[] ocr  = null;
            byte[] ecsd = null;
            byte[] scr  = null;
            int    physicalBlockSize = 0;
            bool   byteAddressed     = true;

            Dictionary <MediaTagType, byte[]> mediaTags = new Dictionary <MediaTagType, byte[]>();

            switch (dev.Type)
            {
            case DeviceType.MMC:
            {
                dumpLog.WriteLine("Reading Extended CSD");
                sense = dev.ReadExtendedCsd(out ecsd, out _, TIMEOUT, out duration);
                if (!sense)
                {
                    ExtendedCSD ecsdDecoded = Decoders.MMC.Decoders.DecodeExtendedCSD(ecsd);
                    blocksToRead = ecsdDecoded.OptimalReadSize;
                    blocks       = ecsdDecoded.SectorCount;
                    blockSize    = (uint)(ecsdDecoded.SectorSize == 1 ? 4096 : 512);
                    if (ecsdDecoded.NativeSectorSize == 0)
                    {
                        physicalBlockSize = 512;
                    }
                    else if (ecsdDecoded.NativeSectorSize == 1)
                    {
                        physicalBlockSize = 4096;
                    }
                    // Supposing it's high-capacity MMC if it has Extended CSD...
                    byteAddressed = false;
                    mediaTags.Add(MediaTagType.MMC_ExtendedCSD, null);
                }
                else
                {
                    ecsd = null;
                }

                dumpLog.WriteLine("Reading CSD");
                sense = dev.ReadCsd(out csd, out _, TIMEOUT, out duration);
                if (!sense)
                {
                    if (blocks == 0)
                    {
                        CSD csdDecoded = Decoders.MMC.Decoders.DecodeCSD(csd);
                        blocks    = (ulong)((csdDecoded.Size + 1) * Math.Pow(2, csdDecoded.SizeMultiplier + 2));
                        blockSize = (uint)Math.Pow(2, csdDecoded.ReadBlockLength);
                    }

                    mediaTags.Add(MediaTagType.MMC_CSD, null);
                }
                else
                {
                    csd = null;
                }

                dumpLog.WriteLine("Reading OCR");
                sense = dev.ReadOcr(out ocr, out _, TIMEOUT, out duration);
                if (sense)
                {
                    ocr = null;
                }
                else
                {
                    mediaTags.Add(MediaTagType.MMC_OCR, null);
                }

                break;
            }

            case DeviceType.SecureDigital:
            {
                dumpLog.WriteLine("Reading CSD");
                sense = dev.ReadCsd(out csd, out _, TIMEOUT, out duration);
                if (!sense)
                {
                    Decoders.SecureDigital.CSD csdDecoded = Decoders.SecureDigital.Decoders.DecodeCSD(csd);
                    blocks = (ulong)(csdDecoded.Structure == 0
                                             ? (csdDecoded.Size + 1) * Math.Pow(2, csdDecoded.SizeMultiplier + 2)
                                             : (csdDecoded.Size + 1) * 1024);
                    blockSize = (uint)Math.Pow(2, csdDecoded.ReadBlockLength);
                    // Structure >=1 for SDHC/SDXC, so that's block addressed
                    byteAddressed = csdDecoded.Structure == 0;
                    mediaTags.Add(MediaTagType.SD_CSD, null);
                }
                else
                {
                    csd = null;
                }

                dumpLog.WriteLine("Reading OCR");
                sense = dev.ReadSdocr(out ocr, out _, TIMEOUT, out duration);
                if (sense)
                {
                    ocr = null;
                }
                else
                {
                    mediaTags.Add(MediaTagType.SD_OCR, null);
                }

                dumpLog.WriteLine("Reading SCR");
                sense = dev.ReadScr(out scr, out _, TIMEOUT, out duration);
                if (sense)
                {
                    scr = null;
                }
                else
                {
                    mediaTags.Add(MediaTagType.SD_SCR, null);
                }

                break;
            }
            }

            dumpLog.WriteLine("Reading CID");
            sense = dev.ReadCid(out byte[] cid, out _, TIMEOUT, out duration);
            if (sense)
            {
                cid = null;
            }
            else
            {
                mediaTags.Add(dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_CID : MediaTagType.MMC_CID, null);
            }

            DateTime start;
            DateTime end;
            double   totalDuration = 0;
            double   currentSpeed  = 0;
            double   maxSpeed      = double.MinValue;
            double   minSpeed      = double.MaxValue;

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

            if (blocks == 0)
            {
                dumpLog.WriteLine("Cannot get device size.");
                DicConsole.ErrorWriteLine("Unable to get device size.");
                return;
            }

            dumpLog.WriteLine("Device reports {0} blocks.", blocks);

            byte[] cmdBuf;
            bool   error;

            while (true)
            {
                error = dev.Read(out cmdBuf, out _, 0, blockSize, blocksToRead, byteAddressed, TIMEOUT, out duration);

                if (error)
                {
                    blocksToRead /= 2;
                }

                if (!error || blocksToRead == 1)
                {
                    break;
                }
            }

            if (error)
            {
                dumpLog.WriteLine("ERROR: Cannot get blocks to read, device error {0}.", dev.LastError);
                DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                return;
            }

            dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead);

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

            DumpHardwareType currentTry = null;
            ExtentsULong     extents    = null;

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

            bool ret = true;

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

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

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

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

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

            ret = outputPlugin.Create(outputPath,
                                      dev.Type == DeviceType.SecureDigital ? MediaType.SecureDigital : MediaType.MMC,
                                      formatOptions, blocks, blockSize);

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

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

            start = DateTime.UtcNow;
            double imageWriteDuration = 0;
            bool   newTrim            = false;

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

                if (blocks - i < blocksToRead)
                {
                    blocksToRead = (byte)(blocks - i);
                }

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

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

                error = dev.Read(out cmdBuf, out _, (uint)i, blockSize, blocksToRead, byteAddressed, TIMEOUT,
                                 out duration);

                if (!error)
                {
                    mhddLog.Write(i, duration);
                    ibgLog.Write(i, currentSpeed * 1024);
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(cmdBuf, i, blocksToRead);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    extents.Add(i, blocksToRead, true);
                }
                else
                {
                    if (i + skip > blocks)
                    {
                        skip = (uint)(blocks - i);
                    }

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

                    mhddLog.Write(i, duration < 500 ? 65535 : duration);

                    ibgLog.Write(i, 0);
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(new byte[blockSize * skip], i, skip);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i);
                    i      += skip - blocksToRead;
                    newTrim = true;
                }

                double newSpeed =
                    (double)blockSize * blocksToRead / 1048576 / (duration / 1000);
                if (!double.IsInfinity(newSpeed))
                {
                    currentSpeed = newSpeed;
                }
                resume.NextBlock = i + blocksToRead;
            }

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

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

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

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

                    error = dev.Read(out cmdBuf, out _, (uint)badSector, blockSize, 1, byteAddressed, TIMEOUT,
                                     out duration);

                    totalDuration += duration;

                    if (error)
                    {
                        continue;
                    }

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

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

            #region Error handling
            if (resume.BadBlocks.Count > 0 && !aborted && retryPasses > 0)
            {
                int  pass              = 1;
                bool forward           = true;
                bool runningPersistent = false;

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

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

                    error = dev.Read(out cmdBuf, out _, (uint)badSector, blockSize, 1, byteAddressed, TIMEOUT,
                                     out duration);

                    totalDuration += duration;

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

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

                DicConsole.WriteLine();
            }
            #endregion Error handling

            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

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

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

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

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

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

                switch (dev.Type)
                {
                case DeviceType.MMC:
                    sidecar.BlockMedia[0].MultiMediaCard = new MultiMediaCardType();
                    break;

                case DeviceType.SecureDigital:
                    sidecar.BlockMedia[0].SecureDigital = new SecureDigitalType();
                    break;
                }

                DumpType cidDump = null;
                DumpType csdDump = null;
                DumpType ocrDump = null;

                if (cid != null)
                {
                    cidDump = new DumpType
                    {
                        Image     = outputPath,
                        Size      = cid.Length,
                        Checksums = Checksum.GetChecksums(cid).ToArray()
                    };

                    ret =
                        outputPlugin.WriteMediaTag(cid,
                                                   dev.Type == DeviceType.SecureDigital
                                                       ? MediaTagType.SD_CID
                                                       : MediaTagType.MMC_CID);

                    // Cannot write CID to image
                    if (!ret && !force)
                    {
                        dumpLog.WriteLine("Cannot write CID to output image.");
                        throw new ArgumentException(outputPlugin.ErrorMessage);
                    }
                }

                if (csd != null)
                {
                    csdDump = new DumpType
                    {
                        Image     = outputPath,
                        Size      = csd.Length,
                        Checksums = Checksum.GetChecksums(csd).ToArray()
                    };

                    ret =
                        outputPlugin.WriteMediaTag(csd,
                                                   dev.Type == DeviceType.SecureDigital
                                                       ? MediaTagType.SD_CSD
                                                       : MediaTagType.MMC_CSD);

                    // Cannot write CSD to image
                    if (!ret && !force)
                    {
                        dumpLog.WriteLine("Cannot write CSD to output image.");
                        throw new ArgumentException(outputPlugin.ErrorMessage);
                    }
                }

                if (ecsd != null)
                {
                    sidecar.BlockMedia[0].MultiMediaCard.ExtendedCSD = new DumpType
                    {
                        Image     = outputPath,
                        Size      = ecsd.Length,
                        Checksums = Checksum.GetChecksums(ecsd).ToArray()
                    };

                    ret = outputPlugin.WriteMediaTag(ecsd, MediaTagType.MMC_ExtendedCSD);

                    // Cannot write Extended CSD to image
                    if (!ret && !force)
                    {
                        dumpLog.WriteLine("Cannot write Extended CSD to output image.");
                        throw new ArgumentException(outputPlugin.ErrorMessage);
                    }
                }

                if (ocr != null)
                {
                    ocrDump = new DumpType
                    {
                        Image     = outputPath,
                        Size      = ocr.Length,
                        Checksums = Checksum.GetChecksums(ocr).ToArray()
                    };

                    ret =
                        outputPlugin.WriteMediaTag(ocr,
                                                   dev.Type == DeviceType.SecureDigital
                                                       ? MediaTagType.SD_OCR
                                                       : MediaTagType.MMC_OCR);

                    // Cannot write OCR to image
                    if (!ret && !force)
                    {
                        dumpLog.WriteLine("Cannot write OCR to output image.");
                        throw new ArgumentException(outputPlugin.ErrorMessage);
                    }
                }

                if (scr != null)
                {
                    sidecar.BlockMedia[0].SecureDigital.SCR = new DumpType
                    {
                        Image     = outputPath,
                        Size      = scr.Length,
                        Checksums = Checksum.GetChecksums(scr).ToArray()
                    };

                    ret = outputPlugin.WriteMediaTag(scr, MediaTagType.SD_SCR);

                    // Cannot write SCR to image
                    if (!ret && !force)
                    {
                        dumpLog.WriteLine("Cannot write SCR to output image.");
                        throw new ArgumentException(outputPlugin.ErrorMessage);
                    }
                }

                switch (dev.Type)
                {
                case DeviceType.MMC:
                    sidecar.BlockMedia[0].MultiMediaCard.CID = cidDump;
                    sidecar.BlockMedia[0].MultiMediaCard.CSD = csdDump;
                    sidecar.BlockMedia[0].MultiMediaCard.OCR = ocrDump;
                    break;

                case DeviceType.SecureDigital:
                    sidecar.BlockMedia[0].SecureDigital.CID = cidDump;
                    sidecar.BlockMedia[0].SecureDigital.CSD = csdDump;
                    sidecar.BlockMedia[0].SecureDigital.OCR = ocrDump;
                    break;
                }

                end = DateTime.UtcNow;

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

                string xmlDskTyp = null, xmlDskSubTyp = null;
                switch (dev.Type)
                {
                case DeviceType.MMC:
                    CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.MMC, out xmlDskTyp,
                                                                     out xmlDskSubTyp);
                    sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.MMC);
                    break;

                case DeviceType.SecureDigital:
                    CommonTypes.Metadata.MediaType.MediaTypeToString(MediaType.SecureDigital, out xmlDskTyp,
                                                                     out xmlDskSubTyp);
                    sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.SecureDigital);
                    break;
                }

                sidecar.BlockMedia[0].DiskType    = xmlDskTyp;
                sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp;
                // TODO: Implement device firmware revision
                sidecar.BlockMedia[0].LogicalBlocks     = (long)blocks;
                sidecar.BlockMedia[0].PhysicalBlockSize = physicalBlockSize > 0 ? physicalBlockSize : (int)blockSize;
                sidecar.BlockMedia[0].LogicalBlockSize  = (int)blockSize;
                sidecar.BlockMedia[0].Manufacturer      = dev.Manufacturer;
                sidecar.BlockMedia[0].Model             = dev.Model;
                sidecar.BlockMedia[0].Serial            = dev.Serial;
                sidecar.BlockMedia[0].Size = (long)(blocks * blockSize);

                DicConsole.WriteLine("Writing metadata sidecar");

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

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

            DicConsole.WriteLine();

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

            switch (dev.Type)
            {
            case DeviceType.MMC:
                Statistics.AddMedia(MediaType.MMC, true);
                break;

            case DeviceType.SecureDigital:
                Statistics.AddMedia(MediaType.SecureDigital, true);
                break;
            }
        }
Пример #5
0
        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);
        }