Files
Aaru/DiscImageChef.Core/Devices/Dumping/SBC.cs

853 lines
41 KiB
C#
Raw Normal View History

// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename : SBC.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Core algorithms.
//
// --[ Description ] ----------------------------------------------------------
//
// Dumps SCSI Block devices.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2018 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
2017-12-21 14:30:38 +00:00
using System.Xml.Serialization;
using DiscImageChef.CommonTypes;
using DiscImageChef.Console;
using DiscImageChef.Core.Logging;
2017-12-21 14:30:38 +00:00
using DiscImageChef.Decoders.SCSI;
using DiscImageChef.Devices;
2017-12-21 14:30:38 +00:00
using DiscImageChef.DiscImages;
using DiscImageChef.Filesystems;
using DiscImageChef.Filters;
2017-12-21 14:30:38 +00:00
using DiscImageChef.Metadata;
2017-06-20 05:48:09 +01:00
using Extents;
2017-12-21 14:30:38 +00:00
using Schemas;
using MediaType = DiscImageChef.CommonTypes.MediaType;
using TrackType = DiscImageChef.DiscImages.TrackType;
namespace DiscImageChef.Core.Devices.Dumping
{
/// <summary>
/// Implements dumping SCSI Block Commands and Reduced Block Commands devices
/// </summary>
static class Sbc
{
/// <summary>
/// Dumps a SCSI Block Commands device or a Reduced Block Commands devices
/// </summary>
/// <param name="dev">Device</param>
/// <param name="devicePath">Path to the device</param>
/// <param name="outputPrefix">Prefix for output data files</param>
/// <param name="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="opticalDisc">If device contains an optical disc (e.g. DVD or BD)</param>
/// <param name="sidecar">Partially filled initialized sidecar</param>
/// <param name="dskType">Disc type as detected in SCSI or MMC layer</param>
/// <param name="alcohol">Alcohol disc image already initialized for optical discs, null otherwise</param>
/// <exception cref="InvalidOperationException">If the resume file is invalid</exception>
2017-12-19 20:33:03 +00:00
internal static void Dump(Device dev, string devicePath, string outputPrefix, ushort retryPasses, bool force,
bool dumpRaw, bool persistent, bool stopOnError, ref CICMMetadataType sidecar,
ref MediaType dskType, bool opticalDisc, ref Resume resume, ref DumpLog dumpLog,
Encoding encoding, Alcohol120 alcohol = null)
{
2017-12-21 16:07:20 +00:00
bool sense;
ulong blocks;
uint blockSize;
uint logicalBlockSize;
uint physicalBlockSize;
byte scsiMediumType = 0;
byte scsiDensityCode = 0;
bool containsFloppyPage = false;
const ushort SBC_PROFILE = 0x0001;
DateTime start;
DateTime end;
double totalDuration = 0;
double totalChkDuration = 0;
double currentSpeed = 0;
double maxSpeed = double.MinValue;
double minSpeed = double.MaxValue;
byte[] readBuffer;
uint blocksToRead;
bool aborted = false;
System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Initializing reader.");
Reader scsiReader = new Reader(dev, dev.Timeout, null, dumpRaw);
blocks = scsiReader.GetDeviceBlocks();
blockSize = scsiReader.LogicalBlockSize;
if(scsiReader.FindReadCommand())
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("ERROR: Cannot find correct read command: {0}.", scsiReader.ErrorMessage);
DicConsole.ErrorWriteLine("Unable to read medium.");
return;
}
if(blocks != 0 && blockSize != 0)
{
blocks++;
2017-12-19 20:33:03 +00:00
DicConsole.WriteLine("Media has {0} blocks of {1} bytes/each. (for a total of {2} bytes)", blocks,
blockSize, blocks * (ulong)blockSize);
}
2017-06-20 05:48:09 +01:00
// Check how many blocks to read, if error show and return
if(scsiReader.GetBlocksToRead())
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("ERROR: Cannot get blocks to read: {0}.", scsiReader.ErrorMessage);
2017-06-20 05:48:09 +01:00
DicConsole.ErrorWriteLine(scsiReader.ErrorMessage);
return;
}
2017-12-19 20:33:03 +00:00
blocksToRead = scsiReader.BlocksToRead;
logicalBlockSize = blockSize;
physicalBlockSize = scsiReader.PhysicalBlockSize;
if(blocks == 0)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("ERROR: Unable to read medium or empty medium present...");
DicConsole.ErrorWriteLine("Unable to read medium or empty medium present...");
return;
}
if(!opticalDisc)
{
sidecar.BlockMedia = new BlockMediaType[1];
sidecar.BlockMedia[0] = new BlockMediaType();
// All USB flash drives report as removable, even if the media is not removable
if(!dev.IsRemovable || dev.IsUsb)
{
if(dev.IsUsb)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Reading USB descriptors.");
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].USB = new USBType
{
ProductID = dev.UsbProductId,
VendorID = dev.UsbVendorId,
Descriptors = new DumpType
2017-06-08 21:12:05 +01:00
{
Image = outputPrefix + ".usbdescriptors.bin",
Size = dev.UsbDescriptors.Length,
Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray()
2017-06-08 21:12:05 +01:00
}
};
DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].USB.Descriptors.Image, dev.UsbDescriptors);
}
byte[] cmdBuf;
if(dev.Type == DeviceType.ATAPI)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Requesting ATAPI IDENTIFY PACKET DEVICE.");
sense = dev.AtapiIdentify(out cmdBuf, out _);
if(!sense)
{
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].ATA = new ATAType
{
Identify = new DumpType
{
Image = outputPrefix + ".identify.bin",
Size = cmdBuf.Length,
Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
}
};
DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].ATA.Identify.Image, cmdBuf);
}
}
sense = dev.ScsiInquiry(out cmdBuf, out _);
if(!sense)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Requesting SCSI INQUIRY.");
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].SCSI = new SCSIType
{
Inquiry = new DumpType
{
Image = outputPrefix + ".inquiry.bin",
Size = cmdBuf.Length,
Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
}
};
DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.Inquiry.Image, cmdBuf);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Reading SCSI Extended Vendor Page Descriptors.");
sense = dev.ScsiInquiry(out cmdBuf, out _, 0x00);
if(!sense)
{
2017-12-21 14:30:38 +00:00
byte[] pages = EVPD.DecodePage00(cmdBuf);
if(pages != null)
{
List<EVPDType> evpds = new List<EVPDType>();
foreach(byte page in pages)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Requesting page {0:X2}h.", page);
sense = dev.ScsiInquiry(out cmdBuf, out _, page);
if(sense) continue;
EVPDType evpd = new EVPDType
{
Image = $"{outputPrefix}.evpd_{page:X2}h.bin",
Checksums = Checksum.GetChecksums(cmdBuf).ToArray(),
Size = cmdBuf.Length
};
evpd.Checksums = Checksum.GetChecksums(cmdBuf).ToArray();
DataFile.WriteTo("SCSI Dump", evpd.Image, cmdBuf);
evpds.Add(evpd);
}
2017-12-19 20:33:03 +00:00
if(evpds.Count > 0) sidecar.BlockMedia[0].SCSI.EVPD = evpds.ToArray();
}
}
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Requesting MODE SENSE (10).");
sense = dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current, 0x3F,
0xFF, 5, out _);
if(!sense || dev.Error)
sense = dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current,
0x3F, 0x00, 5, out _);
2017-12-21 14:30:38 +00:00
Modes.DecodedMode? decMode = null;
if(!sense && !dev.Error)
2017-12-21 14:30:38 +00:00
if(Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue)
{
2017-12-21 14:30:38 +00:00
decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType);
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType
{
Image = outputPrefix + ".modesense10.bin",
Size = cmdBuf.Length,
Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
};
DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense10.Image, cmdBuf);
}
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Requesting MODE SENSE (6).");
sense = dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x3F, 0x00,
5, out _);
if(sense || dev.Error)
sense = dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x3F,
0x00, 5, out _);
if(sense || dev.Error) sense = dev.ModeSense(out cmdBuf, out _, 5, out _);
if(!sense && !dev.Error)
2017-12-21 14:30:38 +00:00
if(Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue)
{
2017-12-21 14:30:38 +00:00
decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType);
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType
{
Image = outputPrefix + ".modesense.bin",
Size = cmdBuf.Length,
Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
};
DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense.Image, cmdBuf);
}
if(decMode.HasValue)
{
scsiMediumType = (byte)decMode.Value.Header.MediumType;
2017-12-19 20:33:03 +00:00
if(decMode.Value.Header.BlockDescriptors != null &&
decMode.Value.Header.BlockDescriptors.Length >= 1)
scsiDensityCode = (byte)decMode.Value.Header.BlockDescriptors[0].Density;
containsFloppyPage =
decMode.Value.Pages.Aggregate(containsFloppyPage,
(current, modePage) => current | (modePage.Page == 0x05));
}
}
}
}
if(dskType == MediaType.Unknown)
dskType = MediaTypeFromScsi.Get((byte)dev.ScsiType, dev.Manufacturer, dev.Model, scsiMediumType,
scsiDensityCode, blocks, blockSize);
dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead);
dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize);
dumpLog.WriteLine("Device reports {0} bytes per physical block.", scsiReader.LongBlockSize);
dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType);
dumpLog.WriteLine("SCSI medium type: {0}.", scsiMediumType);
dumpLog.WriteLine("SCSI density type: {0}.", scsiDensityCode);
if(dskType == MediaType.Unknown && dev.IsUsb && containsFloppyPage) dskType = MediaType.FlashDrive;
DicConsole.WriteLine("Media identified as {0}", dskType);
dumpLog.WriteLine("SCSI floppy mode page present: {0}.", containsFloppyPage);
dumpLog.WriteLine("Media identified as {0}.", dskType);
uint longBlockSize = scsiReader.LongBlockSize;
if(dumpRaw)
if(blockSize == longBlockSize)
{
DicConsole.ErrorWriteLine(!scsiReader.CanReadRaw
? "Device doesn't seem capable of reading raw data from media."
: "Device is capable of reading raw data but I've been unable to guess correct sector size.");
if(!force)
{
2017-12-19 20:33:03 +00:00
DicConsole
.ErrorWriteLine("Not continuing. If you want to continue reading cooked data when raw is not available use the force option.");
// TODO: Exit more gracefully
return;
}
DicConsole.ErrorWriteLine("Continuing dumping cooked data.");
dumpRaw = false;
}
else
{
// Only a block will be read, but it contains 16 sectors and command expect sector number not block number
blocksToRead = (uint)(longBlockSize == 37856 ? 16 : 1);
2017-12-19 20:33:03 +00:00
DicConsole.WriteLine("Reading {0} raw bytes ({1} cooked bytes) per sector.", longBlockSize,
blockSize * blocksToRead);
physicalBlockSize = longBlockSize;
blockSize = longBlockSize;
}
2017-12-19 20:33:03 +00:00
DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead);
string outputExtension = ".bin";
2017-12-19 20:33:03 +00:00
if(opticalDisc && blockSize == 2048) outputExtension = ".iso";
MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead);
IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", SBC_PROFILE);
DataFile dumpFile = new DataFile(outputPrefix + outputExtension);
start = DateTime.UtcNow;
if(alcohol != null && !dumpRaw)
{
alcohol.AddSessions(new[] {new Session {StartTrack = 1, EndTrack = 1, SessionSequence = 1}});
alcohol.AddTrack(20, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1);
alcohol.SetExtension(outputExtension);
alcohol.SetTrackSizes(1, (int)blockSize, 0, 0, (long)blocks);
alcohol.SetTrackTypes(1, TrackType.Data, TrackSubchannelType.None);
}
2017-12-19 20:33:03 +00:00
2017-06-20 05:48:09 +01:00
DumpHardwareType currentTry = null;
ExtentsULong extents = null;
2017-12-19 20:33:03 +00:00
ResumeSupport.Process(true, dev.IsRemovable, blocks, dev.Manufacturer, dev.Model, dev.Serial,
dev.PlatformId, ref resume, ref currentTry, ref extents);
2017-06-20 05:48:09 +01:00
if(currentTry == null || extents == null)
throw new InvalidOperationException("Could not process resume file, not continuing...");
2017-12-19 20:33:03 +00:00
2017-06-20 05:48:09 +01:00
dumpFile.Seek(resume.NextBlock, blockSize);
2017-12-19 20:33:03 +00:00
if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock);
2017-06-20 05:48:09 +01:00
for(ulong i = resume.NextBlock; i < blocks; i += blocksToRead)
{
if(aborted)
2017-06-20 05:48:09 +01:00
{
2017-12-21 14:30:38 +00:00
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Aborted!");
break;
2017-06-20 05:48:09 +01:00
}
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
2017-12-19 20:33:03 +00:00
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);
2017-06-08 21:12:05 +01:00
sense = scsiReader.ReadBlocks(out readBuffer, i, blocksToRead, out double cmdDuration);
totalDuration += cmdDuration;
if(!sense && !dev.Error)
{
mhddLog.Write(i, cmdDuration);
ibgLog.Write(i, currentSpeed * 1024);
dumpFile.Write(readBuffer);
2017-06-20 05:48:09 +01:00
extents.Add(i, blocksToRead, true);
}
else
{
// TODO: Reset device after X errors
2017-12-19 20:33:03 +00:00
if(stopOnError) return; // TODO: Return more cleanly
// Write empty data
dumpFile.Write(new byte[blockSize * blocksToRead]);
2017-12-19 20:33:03 +00:00
for(ulong b = i; b < i + blocksToRead; b++) resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
ibgLog.Write(i, 0);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Error reading {0} blocks from block {1}.", blocksToRead, i);
}
double newSpeed = (double)blockSize * blocksToRead / 1048576 / (cmdDuration / 1000);
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
2017-06-20 05:48:09 +01:00
resume.NextBlock = i + blocksToRead;
}
2017-12-19 20:33:03 +00:00
end = DateTime.UtcNow;
DicConsole.WriteLine();
mhddLog.Close();
2017-12-19 20:33:03 +00:00
ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
2017-12-21 14:30:38 +00:00
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000), devicePath);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
2017-12-19 20:33:03 +00:00
dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
(double)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
#region Error handling
2017-06-20 05:48:09 +01:00
if(resume.BadBlocks.Count > 0 && !aborted)
{
int pass = 0;
bool forward = true;
bool runningPersistent = false;
2017-12-19 20:33:03 +00:00
repeatRetry:
2017-06-20 05:48:09 +01:00
ulong[] tmpArray = resume.BadBlocks.ToArray();
foreach(ulong badSector in tmpArray)
{
if(aborted)
2017-06-20 05:48:09 +01:00
{
2017-12-21 14:30:38 +00:00
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Aborted!");
break;
2017-06-20 05:48:09 +01:00
}
2017-12-19 20:33:03 +00:00
DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass + 1,
forward ? "forward" : "reverse",
runningPersistent ? "recovering partial data, " : "");
2017-06-08 21:12:05 +01:00
sense = scsiReader.ReadBlock(out readBuffer, badSector, out double cmdDuration);
totalDuration += cmdDuration;
if(!sense && !dev.Error)
{
2017-06-20 05:48:09 +01:00
resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
dumpFile.WriteAt(readBuffer, badSector, blockSize);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
}
2017-12-19 20:33:03 +00:00
else if(runningPersistent) dumpFile.WriteAt(readBuffer, badSector, blockSize);
}
2017-06-20 05:48:09 +01:00
if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0)
{
pass++;
forward = !forward;
2017-06-20 05:48:09 +01:00
resume.BadBlocks.Sort();
resume.BadBlocks.Reverse();
goto repeatRetry;
}
2017-12-21 14:30:38 +00:00
Modes.ModePage? currentModePage = null;
2017-12-21 16:07:20 +00:00
byte[] md6;
byte[] md10;
if(!runningPersistent && persistent)
{
2017-12-21 14:30:38 +00:00
if(dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice)
{
2017-12-21 14:30:38 +00:00
Modes.ModePage_01_MMC pgMmc =
new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x20};
2017-12-21 14:30:38 +00:00
Modes.DecodedMode md = new Modes.DecodedMode
2017-06-08 21:12:05 +01:00
{
2017-12-21 14:30:38 +00:00
Header = new Modes.ModeHeader(),
Pages = new[]
2017-06-08 21:12:05 +01:00
{
2017-12-21 14:30:38 +00:00
new Modes.ModePage
2017-06-08 21:12:05 +01:00
{
Page = 0x01,
Subpage = 0x00,
2017-12-21 14:30:38 +00:00
PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
2017-06-08 21:12:05 +01:00
}
}
};
2017-12-21 14:30:38 +00:00
md6 = Modes.EncodeMode6(md, dev.ScsiType);
md10 = Modes.EncodeMode10(md, dev.ScsiType);
}
else
{
2017-12-21 14:30:38 +00:00
Modes.ModePage_01 pg = new Modes.ModePage_01
2017-06-08 21:12:05 +01:00
{
PS = false,
AWRE = false,
ARRE = false,
TB = true,
RC = false,
EER = true,
PER = false,
DTE = false,
DCR = false,
ReadRetryCount = 255
};
2017-12-21 14:30:38 +00:00
Modes.DecodedMode md = new Modes.DecodedMode
2017-06-08 21:12:05 +01:00
{
2017-12-21 14:30:38 +00:00
Header = new Modes.ModeHeader(),
Pages = new[]
2017-06-08 21:12:05 +01:00
{
2017-12-21 14:30:38 +00:00
new Modes.ModePage
2017-06-08 21:12:05 +01:00
{
Page = 0x01,
Subpage = 0x00,
2017-12-21 14:30:38 +00:00
PageResponse = Modes.EncodeModePage_01(pg)
2017-06-08 21:12:05 +01:00
}
}
};
2017-12-21 14:30:38 +00:00
md6 = Modes.EncodeMode6(md, dev.ScsiType);
md10 = Modes.EncodeMode10(md, dev.ScsiType);
}
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Sending MODE SELECT to drive.");
sense = dev.ModeSelect(md6, out _, true, false, dev.Timeout, out _);
if(sense) sense = dev.ModeSelect10(md10, out _, true, false, dev.Timeout, out _);
runningPersistent = true;
if(!sense && !dev.Error)
{
pass--;
goto repeatRetry;
}
}
else if(runningPersistent && persistent && currentModePage.HasValue)
{
2017-12-21 14:30:38 +00:00
Modes.DecodedMode md = new Modes.DecodedMode
2017-06-08 21:12:05 +01:00
{
2017-12-21 14:30:38 +00:00
Header = new Modes.ModeHeader(),
Pages = new[] {currentModePage.Value}
2017-06-08 21:12:05 +01:00
};
2017-12-21 14:30:38 +00:00
md6 = Modes.EncodeMode6(md, dev.ScsiType);
md10 = Modes.EncodeMode10(md, dev.ScsiType);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Sending MODE SELECT to drive.");
sense = dev.ModeSelect(md6, out _, true, false, dev.Timeout, out _);
if(sense) dev.ModeSelect10(md10, out _, true, false, dev.Timeout, out _);
}
DicConsole.WriteLine();
}
#endregion Error handling
2017-12-19 20:33:03 +00:00
2017-06-20 05:48:09 +01:00
resume.BadBlocks.Sort();
2017-12-21 14:30:38 +00:00
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
Checksum dataChk = new Checksum();
dumpFile.Seek(0, SeekOrigin.Begin);
blocksToRead = 500;
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Checksum starts.");
for(ulong i = 0; i < blocks; i += blocksToRead)
{
if(aborted)
2017-11-20 05:07:16 +00:00
{
dumpLog.WriteLine("Aborted!");
break;
2017-11-20 05:07:16 +00:00
}
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
DicConsole.Write("\rChecksumming sector {0} of {1} ({2:F3} MiB/sec.)", i, blocks, currentSpeed);
DateTime chkStart = DateTime.UtcNow;
byte[] dataToCheck = new byte[blockSize * blocksToRead];
dumpFile.Read(dataToCheck, 0, (int)(blockSize * blocksToRead));
dataChk.Update(dataToCheck);
DateTime chkEnd = DateTime.UtcNow;
double chkDuration = (chkEnd - chkStart).TotalMilliseconds;
totalChkDuration += chkDuration;
double newSpeed = (double)blockSize * blocksToRead / 1048576 / (chkDuration / 1000);
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
}
2017-12-19 20:33:03 +00:00
DicConsole.WriteLine();
dumpFile.Close();
end = DateTime.UtcNow;
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Checksum finished in {0} seconds.", (end - start).TotalSeconds);
2017-12-19 20:33:03 +00:00
dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
(double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));
PluginBase plugins = new PluginBase();
FiltersList filtersList = new FiltersList();
IFilter inputFilter = filtersList.GetFilter(outputPrefix + outputExtension);
if(inputFilter == null)
{
DicConsole.ErrorWriteLine("Cannot open file just created, this should not happen.");
return;
}
IMediaImage imageFormat = ImageFormat.Detect(inputFilter);
PartitionType[] xmlFileSysInfo = null;
try { if(!imageFormat.OpenImage(inputFilter)) imageFormat = null; }
catch { imageFormat = null; }
if(imageFormat != null)
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Getting partitions.");
List<Partition> partitions = Partitions.GetAll(imageFormat);
Partitions.AddSchemesToStats(partitions);
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Found {0} partitions.", partitions.Count);
if(partitions.Count > 0)
{
xmlFileSysInfo = new PartitionType[partitions.Count];
for(int i = 0; i < partitions.Count; i++)
{
2017-06-08 21:12:05 +01:00
xmlFileSysInfo[i] = new PartitionType
{
2017-07-19 16:37:11 +01:00
Description = partitions[i].Description,
EndSector = (int)(partitions[i].Start + partitions[i].Length - 1),
Name = partitions[i].Name,
Sequence = (int)partitions[i].Sequence,
StartSector = (int)partitions[i].Start,
Type = partitions[i].Type
2017-06-08 21:12:05 +01:00
};
List<FileSystemType> lstFs = new List<FileSystemType>();
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Getting filesystems on partition {0}, starting at {1}, ending at {2}, with type {3}, under scheme {4}.",
2017-12-19 20:33:03 +00:00
i, partitions[i].Start, partitions[i].End, partitions[i].Type,
partitions[i].Scheme);
foreach(IFilesystem plugin in plugins.PluginsList.Values)
try
{
if(!plugin.Identify(imageFormat, partitions[i])) continue;
plugin.GetInformation(imageFormat, partitions[i], out _, encoding);
lstFs.Add(plugin.XmlFsType);
Statistics.AddFilesystem(plugin.XmlFsType.Type);
dumpLog.WriteLine("Filesystem {0} found.", plugin.XmlFsType.Type);
switch(plugin.XmlFsType.Type)
{
case "Opera":
dskType = MediaType.ThreeDO;
break;
case "PC Engine filesystem":
dskType = MediaType.SuperCDROM2;
break;
case "Nintendo Wii filesystem":
dskType = MediaType.WOD;
break;
case "Nintendo Gamecube filesystem":
dskType = MediaType.GOD;
break;
}
}
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
catch
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
{
//DicConsole.DebugWriteLine("Dump-media command", "Plugin {0} crashed", _plugin.Name);
}
2017-12-19 20:33:03 +00:00
if(lstFs.Count > 0) xmlFileSysInfo[i].FileSystems = lstFs.ToArray();
}
}
else
{
2017-11-20 05:07:16 +00:00
dumpLog.WriteLine("Getting filesystem for whole device.");
xmlFileSysInfo = new PartitionType[1];
2017-12-19 20:33:03 +00:00
xmlFileSysInfo[0] = new PartitionType {EndSector = (int)(blocks - 1), StartSector = 0};
List<FileSystemType> lstFs = new List<FileSystemType>();
2017-12-19 20:33:03 +00:00
Partition wholePart =
new Partition {Name = "Whole device", Length = blocks, Size = blocks * blockSize};
foreach(IFilesystem plugin in plugins.PluginsList.Values)
try
{
if(!plugin.Identify(imageFormat, wholePart)) continue;
plugin.GetInformation(imageFormat, wholePart, out _, encoding);
lstFs.Add(plugin.XmlFsType);
Statistics.AddFilesystem(plugin.XmlFsType.Type);
dumpLog.WriteLine("Filesystem {0} found.", plugin.XmlFsType.Type);
switch(plugin.XmlFsType.Type)
{
case "Opera":
dskType = MediaType.ThreeDO;
break;
case "PC Engine filesystem":
dskType = MediaType.SuperCDROM2;
break;
case "Nintendo Wii filesystem":
dskType = MediaType.WOD;
break;
case "Nintendo Gamecube filesystem":
dskType = MediaType.GOD;
break;
}
}
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
catch
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
{
//DicConsole.DebugWriteLine("Create-sidecar command", "Plugin {0} crashed", _plugin.Name);
}
2017-12-19 20:33:03 +00:00
if(lstFs.Count > 0) xmlFileSysInfo[0].FileSystems = lstFs.ToArray();
}
}
2017-12-19 20:33:03 +00:00
if(alcohol != null && !dumpRaw) alcohol.SetMediaType(dskType);
if(opticalDisc)
{
sidecar.OpticalDisc[0].Checksums = dataChk.End().ToArray();
2017-06-20 05:48:09 +01:00
sidecar.OpticalDisc[0].DumpHardwareArray = resume.Tries.ToArray();
2017-06-08 21:12:05 +01:00
sidecar.OpticalDisc[0].Image = new ImageType
{
format = "Raw disk image (sector by sector copy)",
Value = outputPrefix + outputExtension
};
// TODO: Implement layers
//sidecar.OpticalDisc[0].Layers = new LayersType();
sidecar.OpticalDisc[0].Sessions = 1;
2017-12-19 20:33:03 +00:00
sidecar.OpticalDisc[0].Tracks = new[] {1};
sidecar.OpticalDisc[0].Track = new Schemas.TrackType[1];
2017-06-08 21:12:05 +01:00
sidecar.OpticalDisc[0].Track[0] = new Schemas.TrackType
{
BytesPerSector = (int)blockSize,
Checksums = sidecar.OpticalDisc[0].Checksums,
EndSector = (long)(blocks - 1),
2017-12-19 20:33:03 +00:00
Image =
new ImageType
{
format = "BINARY",
offset = 0,
offsetSpecified = true,
Value = sidecar.OpticalDisc[0].Image.Value
},
Sequence = new TrackSequenceType {Session = 1, TrackNumber = 1},
2017-06-08 21:12:05 +01:00
Size = (long)(blocks * blockSize),
StartSector = 0
};
2017-12-19 20:33:03 +00:00
if(xmlFileSysInfo != null) sidecar.OpticalDisc[0].Track[0].FileSystemInformation = xmlFileSysInfo;
switch(dskType)
{
case MediaType.DDCD:
case MediaType.DDCDR:
case MediaType.DDCDRW:
sidecar.OpticalDisc[0].Track[0].TrackType1 = TrackTypeTrackType.ddcd;
break;
case MediaType.DVDROM:
case MediaType.DVDR:
case MediaType.DVDRAM:
case MediaType.DVDRW:
case MediaType.DVDRDL:
case MediaType.DVDRWDL:
case MediaType.DVDDownload:
case MediaType.DVDPRW:
case MediaType.DVDPR:
case MediaType.DVDPRWDL:
case MediaType.DVDPRDL:
sidecar.OpticalDisc[0].Track[0].TrackType1 = TrackTypeTrackType.dvd;
break;
case MediaType.HDDVDROM:
case MediaType.HDDVDR:
case MediaType.HDDVDRAM:
case MediaType.HDDVDRW:
case MediaType.HDDVDRDL:
case MediaType.HDDVDRWDL:
sidecar.OpticalDisc[0].Track[0].TrackType1 = TrackTypeTrackType.hddvd;
break;
case MediaType.BDROM:
case MediaType.BDR:
case MediaType.BDRE:
case MediaType.BDREXL:
case MediaType.BDRXL:
sidecar.OpticalDisc[0].Track[0].TrackType1 = TrackTypeTrackType.bluray;
break;
}
2017-12-19 20:33:03 +00:00
2017-12-21 14:30:38 +00:00
sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
2017-06-08 21:12:05 +01:00
Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp);
sidecar.OpticalDisc[0].DiscType = xmlDskTyp;
sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp;
}
else
{
sidecar.BlockMedia[0].Checksums = dataChk.End().ToArray();
2017-12-21 14:30:38 +00:00
sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
2017-06-08 21:12:05 +01:00
Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp);
sidecar.BlockMedia[0].DiskType = xmlDskTyp;
sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp;
// TODO: Implement device firmware revision
2017-06-08 21:12:05 +01:00
sidecar.BlockMedia[0].Image = new ImageType
{
format = "Raw disk image (sector by sector copy)",
Value = outputPrefix + ".bin"
};
if(!dev.IsRemovable || dev.IsUsb)
2017-12-19 20:33:03 +00:00
if(dev.Type == DeviceType.ATAPI) sidecar.BlockMedia[0].Interface = "ATAPI";
else if(dev.IsUsb) sidecar.BlockMedia[0].Interface = "USB";
2017-12-19 20:33:03 +00:00
else if(dev.IsFireWire) sidecar.BlockMedia[0].Interface = "FireWire";
else sidecar.BlockMedia[0].Interface = "SCSI";
sidecar.BlockMedia[0].LogicalBlocks = (long)blocks;
sidecar.BlockMedia[0].PhysicalBlockSize = (int)physicalBlockSize;
sidecar.BlockMedia[0].LogicalBlockSize = (int)logicalBlockSize;
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);
2017-12-19 20:33:03 +00:00
if(xmlFileSysInfo != null) sidecar.BlockMedia[0].FileSystemInformation = xmlFileSysInfo;
2017-12-19 20:33:03 +00:00
if(dev.IsRemovable) sidecar.BlockMedia[0].DumpHardwareArray = resume.Tries.ToArray();
}
DicConsole.WriteLine();
2017-12-19 20:33:03 +00:00
DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming).",
(end - start).TotalSeconds, totalDuration / 1000, totalChkDuration / 1000);
DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.",
(double)blockSize * (double)(blocks + 1) / 1048576 / (totalDuration / 1000));
DicConsole.WriteLine("Fastest speed burst: {0:F3} MiB/sec.", maxSpeed);
DicConsole.WriteLine("Slowest speed burst: {0:F3} MiB/sec.", minSpeed);
2017-06-20 05:48:09 +01:00
DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count);
DicConsole.WriteLine();
if(!aborted)
{
DicConsole.WriteLine("Writing metadata sidecar");
2017-12-19 20:33:03 +00:00
FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);
XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
xmlSer.Serialize(xmlFs, sidecar);
xmlFs.Close();
2017-12-19 20:33:03 +00:00
if(alcohol != null && !dumpRaw) alcohol.Close();
}
Statistics.AddMedia(dskType, true);
}
}
2017-12-19 20:33:03 +00:00
}