diff --git a/.idea/.idea.DiscImageChef/.idea/contentModel.xml b/.idea/.idea.DiscImageChef/.idea/contentModel.xml index 3f6f648b..916f19d2 100644 --- a/.idea/.idea.DiscImageChef/.idea/contentModel.xml +++ b/.idea/.idea.DiscImageChef/.idea/contentModel.xml @@ -101,7 +101,6 @@ - diff --git a/DiscImageChef.Core/Devices/Dumping/ATA.cs b/DiscImageChef.Core/Devices/Dumping/ATA.cs index 4b16bf18..46cf1d7c 100644 --- a/DiscImageChef.Core/Devices/Dumping/ATA.cs +++ b/DiscImageChef.Core/Devices/Dumping/ATA.cs @@ -33,21 +33,20 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Xml.Serialization; -using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Core.Logging; using DiscImageChef.Decoders.ATA; using DiscImageChef.Decoders.PCMCIA; using DiscImageChef.Devices; using DiscImageChef.DiscImages; -using DiscImageChef.Filesystems; using DiscImageChef.Filters; using DiscImageChef.Metadata; using Extents; using Schemas; -using MediaType = DiscImageChef.Metadata.MediaType; +using MediaType = DiscImageChef.CommonTypes.MediaType; using Tuple = DiscImageChef.Decoders.PCMCIA.Tuple; namespace DiscImageChef.Core.Devices.Dumping @@ -62,7 +61,8 @@ namespace DiscImageChef.Core.Devices.Dumping /// /// Device /// Path to the device - /// Prefix for output data files + /// Prefix for output log files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump long sectors @@ -71,10 +71,16 @@ namespace DiscImageChef.Core.Devices.Dumping /// Information for dump resuming /// Dump logger /// Encoding to use when analyzing dump + /// Path to output file + /// Formats to pass to output file plugin /// If the resume file is invalid - public static void Dump(Device dev, string devicePath, string outputPrefix, ushort retryPasses, bool force, - bool dumpRaw, bool persistent, bool stopOnError, ref Resume resume, ref DumpLog dumpLog, - Encoding encoding) + 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 + formatOptions) { bool aborted; @@ -90,9 +96,9 @@ namespace DiscImageChef.Core.Devices.Dumping } } - bool sense; + bool sense; const ushort ATA_PROFILE = 0x0001; - const uint TIMEOUT = 5; + const uint TIMEOUT = 5; dumpLog.WriteLine("Requesting ATA IDENTIFY DEVICE."); sense = dev.AtaIdentify(out byte[] cmdBuf, out _); @@ -101,101 +107,24 @@ namespace DiscImageChef.Core.Devices.Dumping Identify.IdentifyDevice? ataIdNullable = Identify.Decode(cmdBuf); if(ataIdNullable != null) { - Identify.IdentifyDevice ataId = ataIdNullable.Value; - - CICMMetadataType sidecar = new CICMMetadataType {BlockMedia = new[] {new BlockMediaType()}}; - - if(dev.IsUsb) - { - dumpLog.WriteLine("Reading USB descriptors."); - sidecar.BlockMedia[0].USB = new USBType - { - ProductID = dev.UsbProductId, - VendorID = dev.UsbVendorId, - Descriptors = new DumpType - { - Image = outputPrefix + ".usbdescriptors.bin", - Size = dev.UsbDescriptors.Length, - Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray() - } - }; - DataFile.WriteTo("ATA Dump", sidecar.BlockMedia[0].USB.Descriptors.Image, dev.UsbDescriptors); - } - - if(dev.IsPcmcia) - { - dumpLog.WriteLine("Reading PCMCIA CIS."); - sidecar.BlockMedia[0].PCMCIA = new PCMCIAType - { - CIS = new DumpType - { - Image = outputPrefix + ".cis.bin", - Size = dev.Cis.Length, - Checksums = Checksum.GetChecksums(dev.Cis).ToArray() - } - }; - DataFile.WriteTo("ATA Dump", sidecar.BlockMedia[0].PCMCIA.CIS.Image, dev.Cis); - dumpLog.WriteLine("Decoding PCMCIA CIS."); - Tuple[] tuples = CIS.GetTuples(dev.Cis); - if(tuples != null) - foreach(Tuple tuple in tuples) - switch(tuple.Code) - { - case TupleCodes.CISTPL_MANFID: - ManufacturerIdentificationTuple manfid = - CIS.DecodeManufacturerIdentificationTuple(tuple); - - if(manfid != null) - { - sidecar.BlockMedia[0].PCMCIA.ManufacturerCode = manfid.ManufacturerID; - sidecar.BlockMedia[0].PCMCIA.CardCode = manfid.CardID; - sidecar.BlockMedia[0].PCMCIA.ManufacturerCodeSpecified = true; - sidecar.BlockMedia[0].PCMCIA.CardCodeSpecified = true; - } - break; - case TupleCodes.CISTPL_VERS_1: - Level1VersionTuple vers = CIS.DecodeLevel1VersionTuple(tuple); - - if(vers != null) - { - sidecar.BlockMedia[0].PCMCIA.Manufacturer = vers.Manufacturer; - sidecar.BlockMedia[0].PCMCIA.ProductName = vers.Product; - sidecar.BlockMedia[0].PCMCIA.Compliance = - $"{vers.MajorVersion}.{vers.MinorVersion}"; - sidecar.BlockMedia[0].PCMCIA.AdditionalInformation = - vers.AdditionalInformation; - } - break; - } - } - - sidecar.BlockMedia[0].ATA = new ATAType - { - Identify = new DumpType - { - Image = outputPrefix + ".identify.bin", - Size = cmdBuf.Length, - Checksums = Checksum.GetChecksums(cmdBuf).ToArray() - } - }; - DataFile.WriteTo("ATA Dump", sidecar.BlockMedia[0].ATA.Identify.Image, cmdBuf); + Identify.IdentifyDevice ataId = ataIdNullable.Value; + byte[] ataIdentify = cmdBuf; + cmdBuf = new byte[0]; DateTime start; DateTime end; - double totalDuration = 0; - double totalChkDuration = 0; - double currentSpeed = 0; - double maxSpeed = double.MinValue; - double minSpeed = double.MaxValue; + double totalDuration = 0; + double totalChkDuration = 0; + double currentSpeed = 0; + double maxSpeed = double.MinValue; + double minSpeed = double.MaxValue; - aborted = false; + aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; - DataFile dumpFile; - // Initializate reader dumpLog.WriteLine("Initializing reader."); - Reader ataReader = new Reader(dev, TIMEOUT, cmdBuf); + Reader ataReader = new Reader(dev, TIMEOUT, ataIdentify); // Fill reader blocks ulong blocks = ataReader.GetDeviceBlocks(); // Check block sizes @@ -206,7 +135,7 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - uint blockSize = ataReader.LogicalBlockSize; + uint blockSize = ataReader.LogicalBlockSize; uint physicalsectorsize = ataReader.PhysicalBlockSize; if(ataReader.FindReadCommand()) { @@ -214,6 +143,7 @@ namespace DiscImageChef.Core.Devices.Dumping DicConsole.ErrorWriteLine(ataReader.ErrorMessage); return; } + // Check how many blocks to read, if error show and return if(ataReader.GetBlocksToRead()) { @@ -222,40 +152,89 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - uint blocksToRead = ataReader.BlocksToRead; - ushort cylinders = ataReader.Cylinders; - byte heads = ataReader.Heads; - byte sectors = ataReader.Sectors; + uint blocksToRead = ataReader.BlocksToRead; + ushort cylinders = ataReader.Cylinders; + byte heads = ataReader.Heads; + byte sectors = ataReader.Sectors; dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize); dumpLog.WriteLine("Device reports {0} cylinders {1} heads {2} sectors per track.", cylinders, heads, sectors); - dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead); - dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize); + dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead); + dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize); dumpLog.WriteLine("Device reports {0} bytes per physical block.", physicalsectorsize); bool removable = !dev.IsCompactFlash && ataId.GeneralConfiguration.HasFlag(Identify.GeneralConfigurationBit.Removable); DumpHardwareType currentTry = null; - ExtentsULong extents = null; + ExtentsULong extents = null; ResumeSupport.Process(ataReader.IsLba, removable, blocks, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId, ref resume, ref currentTry, ref extents); if(currentTry == null || extents == null) throw new InvalidOperationException("Could not process resume file, not continuing..."); MhddLog mhddLog; - IbgLog ibgLog; - double duration; + IbgLog ibgLog; + double duration; + + bool ret = true; + + if(dev.IsUsb && dev.UsbDescriptors != null && + !outputPlugin.SupportedMediaTags.Contains(MediaTagType.USB_Descriptors)) + { + ret = false; + dumpLog.WriteLine("Output format does not support USB descriptors."); + DicConsole.ErrorWriteLine("Output format does not support USB descriptors."); + } + + if(dev.IsPcmcia && dev.Cis != null && + !outputPlugin.SupportedMediaTags.Contains(MediaTagType.PCMCIA_CIS)) + { + ret = false; + dumpLog.WriteLine("Output format does not support PCMCIA CIS descriptors."); + DicConsole.ErrorWriteLine("Output format does not support PCMCIA CIS descriptors."); + } + + if(!outputPlugin.SupportedMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) + { + ret = false; + dumpLog.WriteLine("Output format does not support ATA IDENTIFY."); + DicConsole.ErrorWriteLine("Output format does not support ATA IDENTIFY."); + } + + if(!ret) + { + dumpLog.WriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not "); + DicConsole.ErrorWriteLine("Several media tags not supported, {0}continuing...", + force ? "" : "not "); + if(!force) return; + } + + ret = outputPlugin.Create(outputPath, + dev.IsCompactFlash ? MediaType.CompactFlash : MediaType.GENERIC_HDD, + formatOptions, blocks, blockSize); + + // Cannot create image + if(!ret) + { + dumpLog.WriteLine("Error creating output image, not continuing."); + dumpLog.WriteLine(outputPlugin.ErrorMessage); + DicConsole.ErrorWriteLine("Error creating output image, not continuing."); + DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage); + return; + } + + // Setting geometry + outputPlugin.SetGeometry(cylinders, heads, sectors); + if(ataReader.IsLba) { DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead); - ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); - dumpFile = new DataFile(outputPrefix + ".bin"); - if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); + ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); - dumpFile.Seek(resume.NextBlock, blockSize); + if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); start = DateTime.UtcNow; for(ulong i = resume.NextBlock; i < blocks; i += blocksToRead) @@ -269,10 +248,10 @@ namespace DiscImageChef.Core.Devices.Dumping if(blocks - i < blocksToRead) blocksToRead = (byte)(blocks - i); -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + #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 + #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); @@ -282,7 +261,7 @@ namespace DiscImageChef.Core.Devices.Dumping { mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); - dumpFile.Write(cmdBuf); + outputPlugin.WriteSectors(cmdBuf, i, blocksToRead); extents.Add(i, blocksToRead, true); } else @@ -292,30 +271,32 @@ namespace DiscImageChef.Core.Devices.Dumping mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); - dumpFile.Write(new byte[blockSize * blocksToRead]); + outputPlugin.WriteSectors(new byte[blockSize * blocksToRead], i, blocksToRead); dumpLog.WriteLine("Error reading {0} blocks from block {1}.", blocksToRead, i); } - double newSpeed = (double)blockSize * blocksToRead / 1048576 / (duration / 1000); + double newSpeed = + (double)blockSize * blocksToRead / 1048576 / (duration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; - resume.NextBlock = i + blocksToRead; + 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); + 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)); #region Error handling if(resume.BadBlocks.Count > 0 && !aborted) { - int pass = 0; + int pass = 0; bool forward = true; - bool runningPersistent = false; repeatRetryLba: ulong[] tmpArray = resume.BadBlocks.ToArray(); @@ -330,7 +311,7 @@ namespace DiscImageChef.Core.Devices.Dumping DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass + 1, forward ? "forward" : "reverse", - runningPersistent ? "recovering partial data, " : ""); + persistent ? "recovering partial data, " : ""); bool error = ataReader.ReadBlock(out cmdBuf, badSector, out duration); @@ -340,10 +321,11 @@ namespace DiscImageChef.Core.Devices.Dumping { resume.BadBlocks.Remove(badSector); extents.Add(badSector); - dumpFile.WriteAt(cmdBuf, badSector, blockSize); + outputPlugin.WriteSector(cmdBuf, badSector); dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } - else if(runningPersistent) dumpFile.WriteAt(cmdBuf, badSector, blockSize); + else if(persistent) + outputPlugin.WriteSector(cmdBuf, badSector); } if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) @@ -364,12 +346,11 @@ namespace DiscImageChef.Core.Devices.Dumping else { mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead); - ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); - dumpFile = new DataFile(outputPrefix + ".bin"); + ibgLog = new IbgLog(outputPrefix + ".ibg", ATA_PROFILE); ulong currentBlock = 0; - blocks = (ulong)(cylinders * heads * sectors); - start = DateTime.UtcNow; + blocks = (ulong)(cylinders * heads * sectors); + start = DateTime.UtcNow; for(ushort cy = 0; cy < cylinders; cy++) { for(byte hd = 0; hd < heads; hd++) @@ -383,10 +364,10 @@ namespace DiscImageChef.Core.Devices.Dumping break; } -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + #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 + #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator DicConsole.Write("\rReading cylinder {0} head {1} sector {2} ({3:F3} MiB/sec.)", cy, hd, sc, currentSpeed); @@ -399,7 +380,8 @@ namespace DiscImageChef.Core.Devices.Dumping { mhddLog.Write(currentBlock, duration); ibgLog.Write(currentBlock, currentSpeed * 1024); - dumpFile.Write(cmdBuf); + outputPlugin.WriteSector(cmdBuf, + (ulong)((cy * heads + hd) * sectors + (sc - 1))); extents.Add(currentBlock); dumpLog.WriteLine("Error reading cylinder {0} head {1} sector {2}.", cy, hd, sc); @@ -410,10 +392,12 @@ namespace DiscImageChef.Core.Devices.Dumping mhddLog.Write(currentBlock, duration < 500 ? 65535 : duration); ibgLog.Write(currentBlock, 0); - dumpFile.Write(new byte[blockSize]); + outputPlugin.WriteSector(new byte[blockSize], + (ulong)((cy * heads + hd) * sectors + (sc - 1))); } - double newSpeed = blockSize / (double)1048576 / (duration / 1000); + double newSpeed = + blockSize / (double)1048576 / (duration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; currentBlock++; @@ -425,195 +409,163 @@ namespace DiscImageChef.Core.Devices.Dumping 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); + 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)); } - Checksum dataChk = new Checksum(); - dumpFile.Seek(0, SeekOrigin.Begin); - blocksToRead = 500; + dumpLog.WriteLine("Closing output file."); + DicConsole.WriteLine("Closing output file."); + outputPlugin.Close(); - dumpLog.WriteLine("Checksum starts."); - for(ulong i = 0; i < blocks; i += blocksToRead) + if(aborted) { - if(aborted) - { - dumpLog.WriteLine("Aborted!"); - break; - } - - if(blocks - i < blocksToRead) blocksToRead = (byte)(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; - } - - DicConsole.WriteLine(); - dumpFile.Close(); - end = DateTime.UtcNow; - dumpLog.WriteLine("Checksum finished in {0} seconds.", (end - start).TotalSeconds); - 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 + ".bin"); - - if(inputFilter == null) - { - DicConsole.ErrorWriteLine("Cannot open file just created, this should not happen."); + dumpLog.WriteLine("Aborted!"); return; } - IMediaImage imageFormat = ImageFormat.Detect(inputFilter); - PartitionType[] xmlFileSysInfo = null; + 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."); - try { if(!imageFormat.Open(inputFilter)) imageFormat = null; } - catch { imageFormat = null; } + DateTime chkStart = DateTime.UtcNow; + CICMMetadataType sidecar = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding); - if(imageFormat != null) + if(dev.IsUsb) { - dumpLog.WriteLine("Getting partitions."); - List partitions = Partitions.GetAll(imageFormat); - Partitions.AddSchemesToStats(partitions); - dumpLog.WriteLine("Found {0} partitions.", partitions.Count); + dumpLog.WriteLine("Reading USB descriptors."); + ret = outputPlugin.WriteMediaTag(dev.UsbDescriptors, MediaTagType.USB_Descriptors); - if(partitions.Count > 0) - { - xmlFileSysInfo = new PartitionType[partitions.Count]; - for(int i = 0; i < partitions.Count; i++) + if(ret) + sidecar.BlockMedia[0].USB = new USBType { - xmlFileSysInfo[i] = new PartitionType + ProductID = dev.UsbProductId, + VendorID = dev.UsbVendorId, + Descriptors = new DumpType { - 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 - }; - List lstFs = new List(); - dumpLog - .WriteLine("Getting filesystems on partition {0}, starting at {1}, ending at {2}, with type {3}, under scheme {4}.", - 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); - } -#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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[i].FileSystems = lstFs.ToArray(); - } - } - else - { - dumpLog.WriteLine("Getting filesystem for whole device."); - - xmlFileSysInfo = new PartitionType[1]; - xmlFileSysInfo[0] = new PartitionType {EndSector = (int)(blocks - 1), StartSector = 0}; - List lstFs = new List(); - - Partition wholePart = new Partition - { - Name = "Whole device", - Length = blocks, - Size = blocks * blockSize + Image = outputPath, + Size = dev.UsbDescriptors.Length, + Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray() + } }; - - 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); - } -#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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[0].FileSystems = lstFs.ToArray(); - } } - sidecar.BlockMedia[0].Checksums = dataChk.End().ToArray(); + if(dev.IsPcmcia) + { + dumpLog.WriteLine("Reading PCMCIA CIS."); + ret = outputPlugin.WriteMediaTag(dev.Cis, MediaTagType.PCMCIA_CIS); + + if(ret) + sidecar.BlockMedia[0].PCMCIA = new PCMCIAType + { + CIS = new DumpType + { + Image = outputPath, + Size = dev.Cis.Length, + Checksums = Checksum.GetChecksums(dev.Cis).ToArray() + } + }; + + dumpLog.WriteLine("Decoding PCMCIA CIS."); + Tuple[] tuples = CIS.GetTuples(dev.Cis); + if(tuples != null) + foreach(Tuple tuple in tuples) + switch(tuple.Code) + { + case TupleCodes.CISTPL_MANFID: + ManufacturerIdentificationTuple manfid = + CIS.DecodeManufacturerIdentificationTuple(tuple); + + if(manfid != null) + { + sidecar.BlockMedia[0].PCMCIA.ManufacturerCode = + manfid.ManufacturerID; + sidecar.BlockMedia[0].PCMCIA.CardCode = manfid.CardID; + sidecar.BlockMedia[0].PCMCIA.ManufacturerCodeSpecified = true; + sidecar.BlockMedia[0].PCMCIA.CardCodeSpecified = true; + } + + break; + case TupleCodes.CISTPL_VERS_1: + Level1VersionTuple vers = CIS.DecodeLevel1VersionTuple(tuple); + + if(vers != null) + { + sidecar.BlockMedia[0].PCMCIA.Manufacturer = vers.Manufacturer; + sidecar.BlockMedia[0].PCMCIA.ProductName = vers.Product; + sidecar.BlockMedia[0].PCMCIA.Compliance = + $"{vers.MajorVersion}.{vers.MinorVersion}"; + sidecar.BlockMedia[0].PCMCIA.AdditionalInformation = + vers.AdditionalInformation; + } + + break; + } + } + + ret = outputPlugin.WriteMediaTag(ataIdentify, MediaTagType.ATA_IDENTIFY); + + if(ret) + sidecar.BlockMedia[0].ATA = new ATAType + { + Identify = new DumpType + { + Image = outputPath, + Size = cmdBuf.Length, + Checksums = Checksum.GetChecksums(cmdBuf).ToArray() + } + }; + + DateTime chkEnd = DateTime.UtcNow; + + totalChkDuration = (chkEnd - chkStart).TotalMilliseconds; + dumpLog.WriteLine("Sidecar created in {0} seconds.", (chkEnd - chkStart).TotalSeconds); + dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", + (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); + + DicConsole.WriteLine(); string xmlDskTyp, xmlDskSubTyp; if(dev.IsCompactFlash) - MediaType.MediaTypeToString(CommonTypes.MediaType.CompactFlash, out xmlDskTyp, - out xmlDskSubTyp); + Metadata.MediaType.MediaTypeToString(MediaType.CompactFlash, out xmlDskTyp, out xmlDskSubTyp); else if(dev.IsPcmcia) - MediaType.MediaTypeToString(CommonTypes.MediaType.PCCardTypeI, out xmlDskTyp, out xmlDskSubTyp); + Metadata.MediaType.MediaTypeToString(MediaType.PCCardTypeI, out xmlDskTyp, out xmlDskSubTyp); else - MediaType.MediaTypeToString(CommonTypes.MediaType.GENERIC_HDD, out xmlDskTyp, out xmlDskSubTyp); - sidecar.BlockMedia[0].DiskType = xmlDskTyp; - sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; - // TODO: Implement device firmware revision - sidecar.BlockMedia[0].Image = new ImageType - { - format = "Raw disk image (sector by sector copy)", - Value = outputPrefix + ".bin" - }; - sidecar.BlockMedia[0].Interface = "ATA"; - sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; + Metadata.MediaType.MediaTypeToString(MediaType.GENERIC_HDD, out xmlDskTyp, out xmlDskSubTyp); + sidecar.BlockMedia[0].DiskType = xmlDskTyp; + sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; + sidecar.BlockMedia[0].Interface = "ATA"; + sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; sidecar.BlockMedia[0].PhysicalBlockSize = (int)physicalsectorsize; - sidecar.BlockMedia[0].LogicalBlockSize = (int)blockSize; - sidecar.BlockMedia[0].Manufacturer = dev.Manufacturer; - sidecar.BlockMedia[0].Model = dev.Model; - sidecar.BlockMedia[0].Serial = dev.Serial; - sidecar.BlockMedia[0].Size = (long)(blocks * blockSize); - if(xmlFileSysInfo != null) sidecar.BlockMedia[0].FileSystemInformation = xmlFileSysInfo; + sidecar.BlockMedia[0].LogicalBlockSize = (int)blockSize; + sidecar.BlockMedia[0].Manufacturer = dev.Manufacturer; + sidecar.BlockMedia[0].Model = dev.Model; + sidecar.BlockMedia[0].Serial = dev.Serial; + sidecar.BlockMedia[0].Size = (long)(blocks * blockSize); if(cylinders > 0 && heads > 0 && sectors > 0) { - sidecar.BlockMedia[0].Cylinders = cylinders; - sidecar.BlockMedia[0].CylindersSpecified = true; - sidecar.BlockMedia[0].Heads = heads; - sidecar.BlockMedia[0].HeadsSpecified = true; - sidecar.BlockMedia[0].SectorsPerTrack = sectors; + sidecar.BlockMedia[0].Cylinders = cylinders; + sidecar.BlockMedia[0].CylindersSpecified = true; + sidecar.BlockMedia[0].Heads = heads; + sidecar.BlockMedia[0].HeadsSpecified = true; + sidecar.BlockMedia[0].SectorsPerTrack = sectors; sidecar.BlockMedia[0].SectorsPerTrackSpecified = true; } DicConsole.WriteLine(); DicConsole - .WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming).", - (end - start).TotalSeconds, totalDuration / 1000, totalChkDuration / 1000); + .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); - DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count); + DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count); if(resume.BadBlocks.Count > 0) resume.BadBlocks.Sort(); DicConsole.WriteLine(); @@ -629,7 +581,11 @@ namespace DiscImageChef.Core.Devices.Dumping } } - Statistics.AddMedia(CommonTypes.MediaType.GENERIC_HDD, true); + if(dev.IsCompactFlash) Statistics.AddMedia(MediaType.CompactFlash, true); + else if(dev.IsPcmcia) + Statistics.AddMedia(MediaType.PCCardTypeI, true); + else + Statistics.AddMedia(MediaType.GENERIC_HDD, true); } else DicConsole.ErrorWriteLine("Unable to communicate with ATA device."); } diff --git a/DiscImageChef.Core/Devices/Dumping/Alcohol120.cs b/DiscImageChef.Core/Devices/Dumping/Alcohol120.cs deleted file mode 100644 index 6804d2df..00000000 --- a/DiscImageChef.Core/Devices/Dumping/Alcohol120.cs +++ /dev/null @@ -1,534 +0,0 @@ -// /*************************************************************************** -// The Disc Image Chef -// ---------------------------------------------------------------------------- -// -// Filename : Alcohol120.cs -// Author(s) : Natalia Portillo -// -// Component : Core algorithms. -// -// --[ Description ] ---------------------------------------------------------- -// -// Contains code for creating Alcohol 120% disc images. -// -// --[ 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 . -// -// ---------------------------------------------------------------------------- -// Copyright © 2011-2018 Natalia Portillo -// ****************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using DiscImageChef.CommonTypes; -using DiscImageChef.DiscImages; - -namespace DiscImageChef.Core.Devices.Dumping -{ - // TODO: For >4.0, this class must disappear - class Alcohol120 - { - byte[] bca; - byte[] dmi; - string extension; - AlcoholFooter footer; - AlcoholHeader header; - string outputPrefix; - byte[] pfi; - List sessions; - Dictionary trackLengths; - List tracks; - - internal Alcohol120(string outputPrefix) - { - this.outputPrefix = outputPrefix; - header = new AlcoholHeader - { - signature = "MEDIA DESCRIPTOR", - unknown1 = new ushort[] {0x0002, 0x0000}, - unknown2 = new uint[2], - unknown3 = new uint[6], - unknown4 = new uint[3], - version = new byte[] {1, 5} - }; - header.version[0] = 1; - header.version[1] = 5; - tracks = new List(); - sessions = new List(); - trackLengths = new Dictionary(); - footer = new AlcoholFooter {widechar = 1}; - } - - internal void Close() - { - if(sessions.Count == 0 || tracks.Count == 0) return; - - // Calculate first offsets - header.sessions = (ushort)sessions.Count; - header.sessionOffset = 88; - long nextOffset = 88 + sessions.Count * 24; - - // Calculate session blocks - AlcoholSession[] sessionsArray = sessions.ToArray(); - for(int i = 0; i < sessionsArray.Length; i++) - { - sessionsArray[i].allBlocks = (byte)(sessionsArray[i].lastTrack - sessionsArray[i].firstTrack + 1 + - sessionsArray[i].nonTrackBlocks); - sessionsArray[i].trackOffset = (uint)nextOffset; - nextOffset += sessionsArray[i].allBlocks * 80; - } - - // Calculate track blocks - AlcoholTrack[] tracksArray = tracks.ToArray(); - AlcoholTrackExtra[] extrasArray = new AlcoholTrackExtra[trackLengths.Count]; - for(int i = 0; i < tracksArray.Length; i++) - { - if(tracksArray[i].point >= 0xA0) continue; - if(!trackLengths.TryGetValue(tracksArray[i].point, out uint trkLen)) continue; - - if(tracksArray[i].mode == AlcoholTrackMode.Dvd) tracksArray[i].extraOffset = trkLen; - else - { - AlcoholTrackExtra extra = new AlcoholTrackExtra(); - if(tracksArray[i].point == 1) - { - extra.pregap = 150; - sessionsArray[0].sessionStart = -150; - } - - extra.sectors = trkLen; - extrasArray[tracksArray[i].point - 1] = extra; - tracksArray[i].extraOffset = (uint)nextOffset; - nextOffset += 8; - } - } - - // DVD things - if(bca != null && bca.Length > 0) - { - header.bcaOffset = (uint)nextOffset; - header.bcaLength = (ushort)bca.Length; - nextOffset += bca.Length; - } - if(pfi != null && pfi.Length > 0 && dmi != null && dmi.Length > 0) - { - header.structuresOffset = (uint)nextOffset; - nextOffset += 4100; - } - - for(int i = 0; i < tracksArray.Length; i++) tracksArray[i].footerOffset = (uint)nextOffset; - - footer.filenameOffset = (uint)(nextOffset + 16); - - byte[] filename = Encoding.Unicode.GetBytes(outputPrefix + extension); - - // Open descriptor file here - FileStream descriptorFile = - new FileStream(outputPrefix + ".mds", FileMode.Create, FileAccess.ReadWrite, FileShare.None); - - byte[] tmp = new byte[88]; - IntPtr hdrPtr = Marshal.AllocHGlobal(88); - Marshal.StructureToPtr(header, hdrPtr, false); - Marshal.Copy(hdrPtr, tmp, 0, 88); - - descriptorFile.Write(tmp, 0, tmp.Length); - - foreach(AlcoholSession session in sessionsArray) - { - tmp = new byte[24]; - IntPtr sesPtr = Marshal.AllocHGlobal(24); - Marshal.StructureToPtr(session, sesPtr, false); - Marshal.Copy(sesPtr, tmp, 0, 24); - descriptorFile.Write(tmp, 0, tmp.Length); - Marshal.FreeHGlobal(sesPtr); - } - - foreach(AlcoholTrack track in tracksArray) - { - tmp = new byte[80]; - IntPtr trkPtr = Marshal.AllocHGlobal(80); - Marshal.StructureToPtr(track, trkPtr, false); - Marshal.Copy(trkPtr, tmp, 0, 80); - descriptorFile.Write(tmp, 0, tmp.Length); - Marshal.FreeHGlobal(trkPtr); - } - - if(header.type == AlcoholMediumType.Cd || header.type == AlcoholMediumType.Cdr || - header.type == AlcoholMediumType.Cdrw) - foreach(AlcoholTrackExtra extra in extrasArray) - { - tmp = new byte[8]; - IntPtr trkxPtr = Marshal.AllocHGlobal(8); - Marshal.StructureToPtr(extra, trkxPtr, false); - Marshal.Copy(trkxPtr, tmp, 0, 8); - descriptorFile.Write(tmp, 0, tmp.Length); - Marshal.FreeHGlobal(trkxPtr); - } - - if(bca != null && bca.Length > 0) - { - header.bcaOffset = (uint)descriptorFile.Position; - header.bcaLength = (ushort)bca.Length; - descriptorFile.Write(bca, 0, bca.Length); - } - - if(pfi != null && pfi.Length > 0 && dmi != null && dmi.Length > 0) - { - descriptorFile.Write(new byte[4], 0, 4); - descriptorFile.Write(dmi, 0, 2048); - descriptorFile.Write(pfi, 0, 2048); - } - - tmp = new byte[16]; - - IntPtr ftrPtr = Marshal.AllocHGlobal(16); - Marshal.StructureToPtr(footer, ftrPtr, false); - Marshal.Copy(ftrPtr, tmp, 0, 16); - Marshal.FreeHGlobal(ftrPtr); - descriptorFile.Write(tmp, 0, tmp.Length); - - descriptorFile.Write(filename, 0, filename.Length); - - // This is because of marshalling strings - descriptorFile.Position = 15; - descriptorFile.WriteByte(0x52); - - descriptorFile.Dispose(); - } - - internal void SetMediaType(MediaType type) - { - switch(type) - { - case MediaType.DVDDownload: - case MediaType.DVDPR: - case MediaType.DVDPRDL: - case MediaType.DVDPRW: - case MediaType.DVDPRWDL: - case MediaType.DVDR: - case MediaType.DVDRAM: - case MediaType.DVDRDL: - case MediaType.DVDRW: - case MediaType.DVDRWDL: - header.type = AlcoholMediumType.Dvdr; - break; - case MediaType.CD: - case MediaType.CDDA: - case MediaType.CDEG: - case MediaType.CDG: - case MediaType.CDI: - case MediaType.CDMIDI: - case MediaType.CDPLUS: - case MediaType.CDROM: - case MediaType.CDROMXA: - case MediaType.CDV: - case MediaType.DDCD: - case MediaType.DTSCD: - case MediaType.JaguarCD: - case MediaType.MEGACD: - case MediaType.PCD: - case MediaType.PS1CD: - case MediaType.PS2CD: - case MediaType.SATURNCD: - case MediaType.SuperCDROM2: - case MediaType.SVCD: - case MediaType.VCD: - case MediaType.VCDHD: - case MediaType.GDROM: - case MediaType.ThreeDO: - header.type = AlcoholMediumType.Cd; - break; - case MediaType.CDR: - case MediaType.DDCDR: - case MediaType.GDR: - header.type = AlcoholMediumType.Cdr; - break; - case MediaType.CDRW: - case MediaType.DDCDRW: - case MediaType.CDMO: - case MediaType.CDMRW: - header.type = AlcoholMediumType.Cdrw; - break; - default: - header.type = AlcoholMediumType.Dvd; - break; - } - } - - internal void AddSessions(Session[] cdSessions) - { - foreach(AlcoholSession session in cdSessions.Select(cdSession => new AlcoholSession - { - firstTrack = (ushort)cdSession.StartTrack, - lastTrack = (ushort)cdSession.EndTrack, - sessionSequence = cdSession.SessionSequence - })) sessions.Add(session); - } - - internal void SetTrackTypes(byte point, TrackType mode, TrackSubchannelType subMode) - { - AlcoholTrack[] trkArray = tracks.ToArray(); - - for(int i = 0; i < trkArray.Length; i++) - { - if(trkArray[i].point != point) continue; - - switch(mode) - { - case TrackType.Audio: - trkArray[i].mode = AlcoholTrackMode.Audio; - break; - case TrackType.Data: - trkArray[i].mode = AlcoholTrackMode.Dvd; - break; - case TrackType.CdMode1: - trkArray[i].mode = AlcoholTrackMode.Mode1; - break; - case TrackType.CdMode2Formless: - trkArray[i].mode = AlcoholTrackMode.Mode2; - break; - case TrackType.CdMode2Form1: - trkArray[i].mode = AlcoholTrackMode.Mode2F1; - break; - case TrackType.CdMode2Form2: - trkArray[i].mode = AlcoholTrackMode.Mode2F2; - break; - default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); - } - - switch(subMode) - { - case TrackSubchannelType.None: - trkArray[i].subMode = AlcoholSubchannelMode.None; - break; - case TrackSubchannelType.RawInterleaved: - trkArray[i].subMode = AlcoholSubchannelMode.Interleaved; - break; - case TrackSubchannelType.Packed: - case TrackSubchannelType.Raw: - case TrackSubchannelType.PackedInterleaved: - case TrackSubchannelType.Q16: - case TrackSubchannelType.Q16Interleaved: - throw new FeatureUnsupportedImageException("Specified subchannel type is not supported."); - default: throw new ArgumentOutOfRangeException(nameof(subMode), subMode, null); - } - - tracks = new List(trkArray); - break; - } - } - - internal void SetTrackSizes(byte point, int sectorSize, long startLba, long startOffset, long length) - { - AlcoholTrack[] trkArray = tracks.ToArray(); - - for(int i = 0; i < trkArray.Length; i++) - { - if(trkArray[i].point != point) continue; - - trkArray[i].sectorSize = (ushort)sectorSize; - trkArray[i].startLba = (uint)startLba; - trkArray[i].startOffset = (ulong)startOffset; - - tracks = new List(trkArray); - break; - } - - if(trackLengths.ContainsKey(point)) trackLengths.Remove(point); - - trackLengths.Add(point, (uint)length); - - AlcoholSession[] sess = sessions.ToArray(); - - for(int i = 0; i < sess.Length; i++) - { - if(sess[i].firstTrack == point) sess[i].sessionStart = (int)startLba; - if(sess[i].lastTrack == point) sess[i].sessionEnd = (int)(startLba + length); - } - - sessions = new List(sess); - } - - internal void AddTrack(byte adrCtl, byte tno, byte point, byte min, byte sec, byte frame, byte zero, byte pmin, - byte psec, byte pframe, byte session) - { - AlcoholTrack trk = new AlcoholTrack - { - mode = AlcoholTrackMode.NoData, - subMode = AlcoholSubchannelMode.None, - adrCtl = adrCtl, - tno = tno, - point = point, - min = min, - sec = sec, - frame = frame, - zero = zero, - pmin = pmin, - psec = psec, - pframe = pframe, - unknown = new byte[18], - files = 1, - unknown2 = new byte[24] - }; - - tracks.Add(trk); - - if(point < 0xA0) return; - - AlcoholSession[] sess = sessions.ToArray(); - - for(int i = 0; i < sess.Length; i++) if(sess[i].sessionSequence == session) sess[i].nonTrackBlocks++; - - sessions = new List(sess); - } - - internal void AddBca(byte[] bca) - { - this.bca = bca; - } - - internal void AddPfi(byte[] pfi) - { - if(pfi.Length == 2052) - { - this.pfi = new byte[2048]; - Array.Copy(pfi, 4, this.pfi, 0, 2048); - } - else this.pfi = pfi; - } - - internal void AddDmi(byte[] dmi) - { - if(dmi.Length == 2052) - { - this.dmi = new byte[2048]; - Array.Copy(dmi, 4, this.dmi, 0, 2048); - } - else this.dmi = dmi; - } - - internal void SetExtension(string extension) - { - this.extension = extension; - } - - #region Internal Structures - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholHeader - { - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string signature; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] version; - public AlcoholMediumType type; - public ushort sessions; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public ushort[] unknown1; - public ushort bcaLength; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] unknown2; - public uint bcaOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public uint[] unknown3; - public uint structuresOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public uint[] unknown4; - public uint sessionOffset; - public uint dpmOffset; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholSession - { - public int sessionStart; - public int sessionEnd; - public ushort sessionSequence; - public byte allBlocks; - public byte nonTrackBlocks; - public ushort firstTrack; - public ushort lastTrack; - public uint unknown; - public uint trackOffset; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholTrack - { - public AlcoholTrackMode mode; - public AlcoholSubchannelMode subMode; - public byte adrCtl; - public byte tno; - public byte point; - public byte min; - public byte sec; - public byte frame; - public byte zero; - public byte pmin; - public byte psec; - public byte pframe; - public uint extraOffset; - public ushort sectorSize; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] public byte[] unknown; - public uint startLba; - public ulong startOffset; - public uint files; - public uint footerOffset; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] public byte[] unknown2; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholTrackExtra - { - public uint pregap; - public uint sectors; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct AlcoholFooter - { - public uint filenameOffset; - public uint widechar; - public uint unknown1; - public uint unknown2; - } - #endregion Internal Structures - - #region Internal enumerations - enum AlcoholMediumType : ushort - { - Cd = 0x00, - Cdr = 0x01, - Cdrw = 0x02, - Dvd = 0x10, - Dvdr = 0x12 - } - - enum AlcoholTrackMode : byte - { - NoData = 0x00, - Dvd = 0x02, - Audio = 0xA9, - Mode1 = 0xAA, - Mode2 = 0xAB, - Mode2F1 = 0xAC, - Mode2F2 = 0xAD - } - - enum AlcoholSubchannelMode : byte - { - None = 0x00, - Interleaved = 0x08 - } - #endregion Internal enumerations - } -} \ No newline at end of file diff --git a/DiscImageChef.Core/Devices/Dumping/CompactDisc.cs b/DiscImageChef.Core/Devices/Dumping/CompactDisc.cs index 89e56a22..5479b811 100644 --- a/DiscImageChef.Core/Devices/Dumping/CompactDisc.cs +++ b/DiscImageChef.Core/Devices/Dumping/CompactDisc.cs @@ -32,23 +32,11 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Serialization; -using DiscImageChef.Console; using DiscImageChef.Core.Logging; -using DiscImageChef.Decoders.CD; -using DiscImageChef.Decoders.SCSI; -using DiscImageChef.Decoders.SCSI.MMC; using DiscImageChef.Devices; using DiscImageChef.DiscImages; using DiscImageChef.Metadata; -using Extents; -using Schemas; using MediaType = DiscImageChef.CommonTypes.MediaType; -using PlatformID = DiscImageChef.Interop.PlatformID; -using Session = DiscImageChef.Decoders.CD.Session; -using TrackType = Schemas.TrackType; namespace DiscImageChef.Core.Devices.Dumping { @@ -63,6 +51,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump scrambled sectors @@ -70,20 +59,25 @@ namespace DiscImageChef.Core.Devices.Dumping /// Stop dump on first error /// Information for dump resuming /// Dump logger - /// Partially filled initialized sidecar /// Disc type as detected in MMC layer - /// Write subchannel separate from main channel - /// Alcohol disc image already initialized /// Try to read and dump as much Lead-in as possible + /// Path to output file + /// Formats to pass to output file plugin /// If trying to dump scrambled sectors /// If the resume file is invalid /// If the track type is unknown (never) - 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 separateSubchannel, ref Resume resume, - ref DumpLog dumpLog, Alcohol120 alcohol, bool dumpLeadIn) + internal static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, + bool force, bool dumpRaw, bool persistent, bool stopOnError, + ref MediaType dskType, + ref + Resume resume, ref DumpLog dumpLog, bool dumpLeadIn, + string outputPrefix, + string + outputPath, Dictionary formatOptions) { - bool sense = false; + throw new NotImplementedException("Dumping CompactDisc is disable pending rewrite."); + + /*bool sense = false; ulong blocks; // TODO: Check subchannel support uint blockSize; @@ -99,7 +93,6 @@ namespace DiscImageChef.Core.Devices.Dumping Checksum dataChk; bool readcd; uint blocksToRead = 64; - DataFile dumpFile; bool aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; @@ -902,7 +895,7 @@ namespace DiscImageChef.Core.Devices.Dumping alcohol.Close(); } - Statistics.AddMedia(dskType, true); + Statistics.AddMedia(dskType, true);*/ } } } \ No newline at end of file diff --git a/DiscImageChef.Core/Devices/Dumping/MMC.cs b/DiscImageChef.Core/Devices/Dumping/MMC.cs index 0817c7ae..b3246449 100644 --- a/DiscImageChef.Core/Devices/Dumping/MMC.cs +++ b/DiscImageChef.Core/Devices/Dumping/MMC.cs @@ -31,6 +31,7 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.Text; using DiscImageChef.Console; using DiscImageChef.Core.Logging; @@ -39,6 +40,7 @@ using DiscImageChef.Decoders.DVD; using DiscImageChef.Decoders.SCSI; using DiscImageChef.Decoders.SCSI.MMC; using DiscImageChef.Devices; +using DiscImageChef.DiscImages; using DiscImageChef.Metadata; using Schemas; using DDS = DiscImageChef.Decoders.DVD.DDS; @@ -59,6 +61,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump raw/long sectors @@ -67,25 +70,25 @@ namespace DiscImageChef.Core.Devices.Dumping /// Information for dump resuming /// Dump logger /// Encoding to use when analyzing dump - /// Partially filled initialized sidecar /// Disc type as detected in MMC layer - /// Write subchannel separate from main channel /// Try to read and dump as much Lead-in as possible + /// Path to output file + /// Formats to pass to output file plugin /// If trying to dump GOD or WOD, or XGDs without a Kreon drive - 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 separateSubchannel, ref Resume resume, - ref DumpLog dumpLog, bool dumpLeadIn, Encoding encoding) + internal static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, + bool force, bool dumpRaw, bool persistent, bool stopOnError, + ref MediaType dskType, + ref + Resume resume, ref DumpLog dumpLog, bool dumpLeadIn, + Encoding encoding, + string + outputPrefix, string outputPath, Dictionary formatOptions) { - bool sense; - ulong blocks; + bool sense; + ulong blocks; byte[] tmpBuf; - bool compactDisc = true; - bool isXbox = false; - Alcohol120 alcohol = new Alcohol120(outputPrefix); - - sidecar.OpticalDisc = new OpticalDiscType[1]; - sidecar.OpticalDisc[0] = new OpticalDiscType(); + bool compactDisc = true; + bool isXbox = false; // TODO: Log not only what is it reading, but if it was read correctly or not. @@ -193,15 +196,16 @@ namespace DiscImageChef.Core.Devices.Dumping if(compactDisc) { - CompactDisc.Dump(dev, devicePath, outputPrefix, retryPasses, force, dumpRaw, persistent, stopOnError, - ref sidecar, ref dskType, separateSubchannel, ref resume, ref dumpLog, alcohol, - dumpLeadIn); + CompactDisc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, + ref dskType, ref resume, ref dumpLog, dumpLeadIn, outputPrefix, outputPath, + formatOptions); return; } Reader scsiReader = new Reader(dev, dev.Timeout, null, dumpRaw); - blocks = scsiReader.GetDeviceBlocks(); + blocks = scsiReader.GetDeviceBlocks(); dumpLog.WriteLine("Device reports disc has {0} blocks", blocks); + Dictionary mediaTags = new Dictionary(); #region Nintendo switch(dskType) @@ -213,9 +217,9 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense) { PFI.PhysicalFormatInformation? nintendoPfi = PFI.Decode(cmdBuf); - if(nintendoPfi != null) + if(nintendoPfi != null) if(nintendoPfi.Value.DiskCategory == DiskCategory.Nintendo && - nintendoPfi.Value.PartVersion == 15) + nintendoPfi.Value.PartVersion == 15) { dumpLog.WriteLine("Dumping Nintendo GameCube or Wii discs is not yet implemented."); throw new @@ -245,19 +249,11 @@ namespace DiscImageChef.Core.Devices.Dumping sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.PhysicalInformation, 0, dev.Timeout, out _); if(!sense) - { - alcohol.AddPfi(cmdBuf); if(PFI.Decode(cmdBuf).HasValue) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].PFI = new DumpType - { - Image = outputPrefix + ".pfi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].PFI.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf); PFI.PhysicalFormatInformation decPfi = PFI.Decode(cmdBuf).Value; DicConsole.WriteLine("PFI:\n{0}", PFI.Prettify(decPfi)); @@ -310,7 +306,6 @@ namespace DiscImageChef.Core.Devices.Dumping break; } } - } dumpLog.WriteLine("Reading Disc Manufacturing Information"); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Dvd, 0, 0, @@ -326,9 +321,9 @@ namespace DiscImageChef.Core.Devices.Dumping dskType = MediaType.XGD2; // All XGD3 all have the same number of blocks - if(blocks == 25063 || // Locked (or non compatible drive) + if(blocks == 25063 || // Locked (or non compatible drive) blocks == 4229664 || // Xtreme unlock - blocks == 4246304) // Wxripper unlock + blocks == 4246304) // Wxripper unlock dskType = MediaType.XGD3; } @@ -345,7 +340,7 @@ namespace DiscImageChef.Core.Devices.Dumping if(dumpRaw && !force) { DicConsole - .ErrorWriteLine("Not continuing. If you want to continue reading cooked data when raw is not available use the force option."); + .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; } @@ -353,19 +348,11 @@ namespace DiscImageChef.Core.Devices.Dumping isXbox = true; } - alcohol.AddDmi(cmdBuf); - if(cmdBuf.Length == 2052) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].DMI = new DumpType - { - Image = outputPrefix + ".dmi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DMI.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf); } } @@ -385,19 +372,9 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense) if(CSS_CPRM.DecodeLeadInCopyright(cmdBuf).HasValue) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].CMI = new DumpType - { - Image = outputPrefix + ".cmi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].CMI.Image, tmpBuf); - - CSS_CPRM.LeadInCopyright cpy = CSS_CPRM.DecodeLeadInCopyright(cmdBuf).Value; - if(cpy.CopyrightType != CopyrightType.NoProtection) - sidecar.OpticalDisc[0].CopyProtection = cpy.CopyrightType.ToString(); + mediaTags.Add(MediaTagType.DVD_CMI, tmpBuf); } } #endregion DVD-ROM @@ -413,17 +390,11 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.BurstCuttingArea, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - alcohol.AddBca(tmpBuf); - sidecar.OpticalDisc[0].BCA = new DumpType - { - Image = outputPrefix + ".bca.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].BCA.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVD_BCA, tmpBuf); } + break; #endregion DVD-ROM and HD DVD-ROM @@ -436,15 +407,9 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense) if(DDS.Decode(cmdBuf).HasValue) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].DDS = new DumpType - { - Image = outputPrefix + ".dds.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DDS.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVDRAM_DDS, tmpBuf); } dumpLog.WriteLine("Reading Spare Area Information."); @@ -454,16 +419,11 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense) if(Spare.Decode(cmdBuf).HasValue) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].SAI = new DumpType - { - Image = outputPrefix + ".sai.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].SAI.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVDRAM_SpareArea, tmpBuf); } + break; #endregion DVD-RAM and HD DVD-RAM @@ -475,16 +435,11 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.PreRecordedInfo, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].PRI = new DumpType - { - Image = outputPrefix + ".pri.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].SAI.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVDR_PreRecordedInfo, tmpBuf); } + break; #endregion DVD-R and DVD-RW } @@ -500,15 +455,9 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.DvdrMediaIdentifier, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].MediaID = new DumpType - { - Image = outputPrefix + ".mid.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].MediaID.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVDR_MediaIdentifier, tmpBuf); } dumpLog.WriteLine("Reading Recordable Physical Information."); @@ -517,16 +466,11 @@ namespace DiscImageChef.Core.Devices.Dumping out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].PFIR = new DumpType - { - Image = outputPrefix + ".pfir.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].PFIR.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVDR_PFI, tmpBuf); } + break; #endregion DVD-R, DVD-RW and HD DVD-R @@ -540,15 +484,9 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.Adip, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].ADIP = new DumpType - { - Image = outputPrefix + ".adip.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].ADIP.Image, tmpBuf); + mediaTags.Add(MediaTagType.DVD_ADIP, tmpBuf); } dumpLog.WriteLine("Reading Disc Control Blocks."); @@ -556,16 +494,11 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.Dcb, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].DCB = new DumpType - { - Image = outputPrefix + ".dcb.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DCB.Image, tmpBuf); + mediaTags.Add(MediaTagType.DCB, tmpBuf); } + break; #endregion All DVD+ @@ -577,16 +510,11 @@ namespace DiscImageChef.Core.Devices.Dumping out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].CMI = new DumpType - { - Image = outputPrefix + ".cmi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].CMI.Image, tmpBuf); + mediaTags.Add(MediaTagType.HDDVD_CPI, tmpBuf); } + break; #endregion HD DVD-ROM @@ -602,17 +530,13 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense) if(DI.Decode(cmdBuf).HasValue) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].DI = new DumpType - { - Image = outputPrefix + ".di.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DI.Image, tmpBuf); + mediaTags.Add(MediaTagType.BD_DI, tmpBuf); } + // TODO: PAC + /* dumpLog.WriteLine("Reading PAC."); sense = dev.ReadDiscStructure(out cmdBuf, out _, MmcDiscStructureMediaType.Bd, 0, 0, MmcDiscStructureFormat.Pac, 0, dev.Timeout, out _); @@ -620,14 +544,8 @@ namespace DiscImageChef.Core.Devices.Dumping { tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].PAC = new DumpType - { - Image = outputPrefix + ".pac.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].PAC.Image, tmpBuf); - } + mediaTags.Add(MediaTagType.PAC, tmpBuf); + }*/ break; #endregion All Blu-ray } @@ -641,17 +559,11 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.BdBurstCuttingArea, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - alcohol.AddBca(tmpBuf); - sidecar.OpticalDisc[0].BCA = new DumpType - { - Image = outputPrefix + ".bca.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].BCA.Image, tmpBuf); + mediaTags.Add(MediaTagType.BD_BCA, tmpBuf); } + break; #endregion BD-ROM only @@ -665,15 +577,9 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.BdDds, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].DDS = new DumpType - { - Image = outputPrefix + ".dds.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DDS.Image, tmpBuf); + mediaTags.Add(MediaTagType.BD_DDS, tmpBuf); } dumpLog.WriteLine("Reading Spare Area Information."); @@ -681,29 +587,177 @@ namespace DiscImageChef.Core.Devices.Dumping MmcDiscStructureFormat.BdSpareAreaInformation, 0, dev.Timeout, out _); if(!sense) { - tmpBuf = new byte[cmdBuf.Length - 4]; + tmpBuf = new byte[cmdBuf.Length - 4]; Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4); - sidecar.OpticalDisc[0].SAI = new DumpType - { - Image = outputPrefix + ".sai.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].SAI.Image, tmpBuf); + mediaTags.Add(MediaTagType.BD_SpareArea, tmpBuf); } + break; #endregion Writable Blu-ray only } if(isXbox) { - Xgd.Dump(dev, devicePath, outputPrefix, retryPasses, force, dumpRaw, persistent, stopOnError, - ref sidecar, ref dskType, ref resume, ref dumpLog, encoding); + Xgd.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, mediaTags, + ref dskType, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, formatOptions); return; } - Sbc.Dump(dev, devicePath, outputPrefix, retryPasses, force, dumpRaw, persistent, stopOnError, ref sidecar, - ref dskType, true, ref resume, ref dumpLog, encoding, alcohol); + Sbc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, mediaTags, + ref dskType, true, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, formatOptions); + } + + internal static void AddMediaTagToSidecar(string outputPath, + KeyValuePair tag, + ref CICMMetadataType sidecar) + { + switch(tag.Key) + { + case MediaTagType.DVD_PFI: + sidecar.OpticalDisc[0].PFI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVD_DMI: + sidecar.OpticalDisc[0].DMI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVD_CMI: + case MediaTagType.HDDVD_CPI: + sidecar.OpticalDisc[0].CMI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + + CSS_CPRM.LeadInCopyright cpy = CSS_CPRM.DecodeLeadInCopyright(tag.Value).Value; + if(cpy.CopyrightType != CopyrightType.NoProtection) + sidecar.OpticalDisc[0].CopyProtection = cpy.CopyrightType.ToString(); + + break; + case MediaTagType.DVD_BCA: + case MediaTagType.BD_BCA: + sidecar.OpticalDisc[0].BCA = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.BD_DDS: + case MediaTagType.DVDRAM_DDS: + sidecar.OpticalDisc[0].DDS = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVDRAM_SpareArea: + case MediaTagType.BD_SpareArea: + sidecar.OpticalDisc[0].SAI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVDR_PreRecordedInfo: + sidecar.OpticalDisc[0].PRI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVD_MediaIdentifier: + sidecar.OpticalDisc[0].MediaID = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVDR_PFI: + sidecar.OpticalDisc[0].PFIR = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DVD_ADIP: + sidecar.OpticalDisc[0].ADIP = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.DCB: + sidecar.OpticalDisc[0].DCB = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.BD_DI: + sidecar.OpticalDisc[0].DI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.Xbox_SecuritySector: + if(sidecar.OpticalDisc[0].Xbox == null) sidecar.OpticalDisc[0].Xbox = new XboxType(); + + sidecar.OpticalDisc[0].Xbox.SecuritySectors = new[] + { + new XboxSecuritySectorsType + { + RequestNumber = 0, + RequestVersion = 1, + SecuritySectors = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + } + } + }; + + break; + case MediaTagType.Xbox_PFI: + if(sidecar.OpticalDisc[0].Xbox == null) sidecar.OpticalDisc[0].Xbox = new XboxType(); + + sidecar.OpticalDisc[0].Xbox.PFI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + case MediaTagType.Xbox_DMI: + if(sidecar.OpticalDisc[0].Xbox == null) sidecar.OpticalDisc[0].Xbox = new XboxType(); + + sidecar.OpticalDisc[0].Xbox.DMI = new DumpType + { + Image = outputPath, + Size = tag.Value.Length, + Checksums = Checksum.GetChecksums(tag.Value).ToArray() + }; + break; + } } } } \ No newline at end of file diff --git a/DiscImageChef.Core/Devices/Dumping/NVMe.cs b/DiscImageChef.Core/Devices/Dumping/NVMe.cs index 9e2d1a77..6e4169c9 100644 --- a/DiscImageChef.Core/Devices/Dumping/NVMe.cs +++ b/DiscImageChef.Core/Devices/Dumping/NVMe.cs @@ -31,18 +31,24 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.Text; using DiscImageChef.Core.Logging; using DiscImageChef.Devices; +using DiscImageChef.DiscImages; using DiscImageChef.Metadata; namespace DiscImageChef.Core.Devices.Dumping { public static class NvMe { - public static void Dump(Device dev, string devicePath, string outputPrefix, ushort retryPasses, bool force, - bool dumpRaw, bool persistent, bool stopOnError, ref Resume resume, ref DumpLog dumpLog, - Encoding encoding) + 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 + formatOptions) { throw new NotImplementedException("NVMe devices not yet supported."); } diff --git a/DiscImageChef.Core/Devices/Dumping/SBC.cs b/DiscImageChef.Core/Devices/Dumping/SBC.cs index cd9710f4..80dc9424 100644 --- a/DiscImageChef.Core/Devices/Dumping/SBC.cs +++ b/DiscImageChef.Core/Devices/Dumping/SBC.cs @@ -42,13 +42,11 @@ using DiscImageChef.Core.Logging; using DiscImageChef.Decoders.SCSI; using DiscImageChef.Devices; using DiscImageChef.DiscImages; -using DiscImageChef.Filesystems; using DiscImageChef.Filters; using DiscImageChef.Metadata; using Extents; using Schemas; using MediaType = DiscImageChef.CommonTypes.MediaType; -using TrackType = DiscImageChef.DiscImages.TrackType; namespace DiscImageChef.Core.Devices.Dumping { @@ -63,6 +61,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump long or scrambled sectors @@ -72,40 +71,48 @@ namespace DiscImageChef.Core.Devices.Dumping /// Dump logger /// Encoding to use when analyzing dump /// If device contains an optical disc (e.g. DVD or BD) - /// Partially filled initialized sidecar + /// Media tags as retrieved in MMC layer /// Disc type as detected in SCSI or MMC layer - /// Alcohol disc image already initialized for optical discs, null otherwise + /// Path to output file + /// Formats to pass to output file plugin /// If the resume file is invalid - 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) + internal static void Dump(Device dev, string devicePath, + IWritableImage outputPlugin, ushort retryPasses, + bool force, bool dumpRaw, + bool persistent, bool stopOnError, + Dictionary mediaTags, ref MediaType dskType, + bool opticalDisc, + ref Resume resume, + ref DumpLog dumpLog, Encoding encoding, string outputPrefix, + string outputPath, + Dictionary formatOptions) { - 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; + 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; + Modes.DecodedMode? decMode = null; dumpLog.WriteLine("Initializing reader."); Reader scsiReader = new Reader(dev, dev.Timeout, null, dumpRaw); - blocks = scsiReader.GetDeviceBlocks(); - blockSize = scsiReader.LogicalBlockSize; + blocks = scsiReader.GetDeviceBlocks(); + blockSize = scsiReader.LogicalBlockSize; if(scsiReader.FindReadCommand()) { dumpLog.WriteLine("ERROR: Cannot find correct read command: {0}.", scsiReader.ErrorMessage); @@ -119,6 +126,7 @@ namespace DiscImageChef.Core.Devices.Dumping DicConsole.WriteLine("Media has {0} blocks of {1} bytes/each. (for a total of {2} bytes)", blocks, blockSize, blocks * (ulong)blockSize); } + // Check how many blocks to read, if error show and return if(scsiReader.GetBlocksToRead()) { @@ -127,8 +135,8 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - blocksToRead = scsiReader.BlocksToRead; - logicalBlockSize = blockSize; + blocksToRead = scsiReader.BlocksToRead; + logicalBlockSize = blockSize; physicalBlockSize = scsiReader.PhysicalBlockSize; if(blocks == 0) @@ -140,64 +148,596 @@ namespace DiscImageChef.Core.Devices.Dumping if(!opticalDisc) { - sidecar.BlockMedia = new BlockMediaType[1]; - sidecar.BlockMedia[0] = new BlockMediaType(); + mediaTags = new Dictionary(); + if(dev.IsUsb && dev.UsbDescriptors != null) + mediaTags.Add(MediaTagType.USB_Descriptors, null); + if(dev.Type == DeviceType.ATAPI) + mediaTags.Add(MediaTagType.ATAPI_IDENTIFY, null); + if(dev.IsPcmcia && dev.Cis != null) + mediaTags.Add(MediaTagType.PCMCIA_CIS, null); + + sense = dev.ScsiInquiry(out byte[] cmdBuf, out _); + mediaTags.Add(MediaTagType.SCSI_INQUIRY, cmdBuf); + if(!sense) + { + 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 _); + + if(!sense && !dev.Error) + if(Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue) + { + mediaTags.Add(MediaTagType.SCSI_MODESENSE_10, cmdBuf); + decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType); + } + + 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) + if(Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue) + { + mediaTags.Add(MediaTagType.SCSI_MODESENSE_6, cmdBuf); + decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType); + } + + if(decMode.HasValue) + { + scsiMediumType = (byte)decMode.Value.Header.MediumType; + 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) + { + 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); + DicConsole.WriteLine("Reading {0} raw bytes ({1} cooked bytes) per sector.", longBlockSize, + blockSize * blocksToRead); + physicalBlockSize = longBlockSize; + blockSize = longBlockSize; + } + + 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", SBC_PROFILE); + ret = outputPlugin.Create(outputPath, dskType, formatOptions, blocks, blockSize); + + // Cannot create image + if(!ret) + { + dumpLog.WriteLine("Error creating output image, not continuing."); + dumpLog.WriteLine(outputPlugin.ErrorMessage); + DicConsole.ErrorWriteLine("Error creating output image, not continuing."); + DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage); + return; + } + + start = DateTime.UtcNow; + + if(opticalDisc) + outputPlugin.SetTracks(new List + { + new Track + { + TrackBytesPerSector = (int)blockSize, + TrackEndSector = blocks - 1, + TrackSequence = 1, + TrackRawBytesPerSector = (int)blockSize, + TrackSubchannelType = TrackSubchannelType.None, + TrackSession = 1 + } + }); + else if(decMode.HasValue) + { + bool setGeometry = false; + + foreach(Modes.ModePage page in decMode.Value.Pages) + if(page.Page == 0x04 && page.Subpage == 0x00) + { + Modes.ModePage_04? rigidPage = Modes.DecodeModePage_04(page.PageResponse); + if(!rigidPage.HasValue || setGeometry) continue; + + dumpLog.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", + rigidPage.Value.Cylinders, rigidPage.Value.Heads, + (uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))); + DicConsole.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", + rigidPage.Value.Cylinders, rigidPage.Value.Heads, + (uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))); + outputPlugin.SetGeometry(rigidPage.Value.Cylinders, rigidPage.Value.Heads, + (uint)(blocks / (rigidPage.Value.Cylinders * rigidPage.Value.Heads))); + + setGeometry = true; + } + else if(page.Page == 0x05 && page.Subpage == 0x00) + { + Modes.ModePage_05? flexiblePage = Modes.DecodeModePage_05(page.PageResponse); + if(!flexiblePage.HasValue) continue; + + dumpLog.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", + flexiblePage.Value.Cylinders, flexiblePage.Value.Heads, + flexiblePage.Value.SectorsPerTrack); + DicConsole.WriteLine("Setting geometry to {0} cylinders, {1} heads, {2} sectors per track", + flexiblePage.Value.Cylinders, flexiblePage.Value.Heads, + flexiblePage.Value.SectorsPerTrack); + outputPlugin.SetGeometry(flexiblePage.Value.Cylinders, flexiblePage.Value.Heads, + flexiblePage.Value.SectorsPerTrack); + setGeometry = true; + } + } + + DumpHardwareType currentTry = null; + ExtentsULong extents = null; + ResumeSupport.Process(true, dev.IsRemovable, blocks, dev.Manufacturer, dev.Model, dev.Serial, + dev.PlatformId, ref resume, ref currentTry, ref extents); + if(currentTry == null || extents == null) + throw new InvalidOperationException("Could not process resume file, not continuing..."); + + if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); + + 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 = (uint)(blocks - i); + + #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + if(currentSpeed > maxSpeed && currentSpeed != 0) maxSpeed = currentSpeed; + if(currentSpeed < minSpeed && currentSpeed != 0) minSpeed = currentSpeed; + #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator + + DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, blocks, currentSpeed); + + 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); + outputPlugin.WriteSectors(readBuffer, i, blocksToRead); + extents.Add(i, blocksToRead, true); + } + else + { + // TODO: Reset device after X errors + if(stopOnError) return; // TODO: Return more cleanly + + // Write empty data + outputPlugin.WriteSectors(new byte[blockSize * blocksToRead], i, blocksToRead); + + for(ulong b = i; b < i + blocksToRead; b++) resume.BadBlocks.Add(b); + + mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); + + ibgLog.Write(i, 0); + 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; + resume.NextBlock = i + blocksToRead; + } + + end = DateTime.UtcNow; + 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)); + + #region Error handling + if(resume.BadBlocks.Count > 0 && !aborted) + { + int pass = 0; + bool forward = true; + bool runningPersistent = false; + + repeatRetry: + ulong[] tmpArray = resume.BadBlocks.ToArray(); + foreach(ulong badSector in tmpArray) + { + if(aborted) + { + currentTry.Extents = ExtentsConverter.ToMetadata(extents); + dumpLog.WriteLine("Aborted!"); + break; + } + + DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass + 1, + forward ? "forward" : "reverse", + runningPersistent ? "recovering partial data, " : ""); + + sense = scsiReader.ReadBlock(out readBuffer, badSector, out double cmdDuration); + totalDuration += cmdDuration; + + if(!sense && !dev.Error) + { + resume.BadBlocks.Remove(badSector); + extents.Add(badSector); + outputPlugin.WriteSector(readBuffer, badSector); + dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); + } + else if(runningPersistent) + outputPlugin.WriteSector(readBuffer, badSector); + } + + if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) + { + pass++; + forward = !forward; + resume.BadBlocks.Sort(); + resume.BadBlocks.Reverse(); + goto repeatRetry; + } + + Modes.ModePage? currentModePage = null; + byte[] md6; + byte[] md10; + + if(!runningPersistent && persistent) + { + if(dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice) + { + Modes.ModePage_01_MMC pgMmc = + new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x20}; + Modes.DecodedMode md = new Modes.DecodedMode + { + Header = new Modes.ModeHeader(), + Pages = new[] + { + new Modes.ModePage + { + Page = 0x01, + Subpage = 0x00, + PageResponse = Modes.EncodeModePage_01_MMC(pgMmc) + } + } + }; + md6 = Modes.EncodeMode6(md, dev.ScsiType); + md10 = Modes.EncodeMode10(md, dev.ScsiType); + } + else + { + Modes.ModePage_01 pg = new Modes.ModePage_01 + { + PS = false, + AWRE = false, + ARRE = false, + TB = true, + RC = false, + EER = true, + PER = false, + DTE = false, + DCR = false, + ReadRetryCount = 255 + }; + Modes.DecodedMode md = new Modes.DecodedMode + { + Header = new Modes.ModeHeader(), + Pages = new[] + { + new Modes.ModePage + { + Page = 0x01, + Subpage = 0x00, + PageResponse = Modes.EncodeModePage_01(pg) + } + } + }; + md6 = Modes.EncodeMode6(md, dev.ScsiType); + md10 = Modes.EncodeMode10(md, dev.ScsiType); + } + + 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) + { + Modes.DecodedMode md = new Modes.DecodedMode + { + Header = new Modes.ModeHeader(), + Pages = new[] {currentModePage.Value} + }; + md6 = Modes.EncodeMode6(md, dev.ScsiType); + md10 = Modes.EncodeMode10(md, dev.ScsiType); + + dumpLog.WriteLine("Sending MODE SELECT to drive."); + 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 + + if(!aborted) + if(opticalDisc) + foreach(KeyValuePair tag in mediaTags) + { + ret = outputPlugin.WriteMediaTag(tag.Value, tag.Key); + + if(ret || force) continue; + + // Cannot write tag to image + dumpLog.WriteLine($"Cannot write tag {tag.Key}."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + else + { + if(!dev.IsRemovable || dev.IsUsb) + { + if(dev.IsUsb) + { + dumpLog.WriteLine("Reading USB descriptors."); + ret = outputPlugin.WriteMediaTag(dev.UsbDescriptors, MediaTagType.USB_Descriptors); + + if(!ret && !force) + { + dumpLog.WriteLine($"Cannot write USB descriptors."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + } + + byte[] cmdBuf; + if(dev.Type == DeviceType.ATAPI) + { + dumpLog.WriteLine("Requesting ATAPI IDENTIFY PACKET DEVICE."); + sense = dev.AtapiIdentify(out cmdBuf, out _); + if(!sense) + { + ret = outputPlugin.WriteMediaTag(cmdBuf, MediaTagType.ATAPI_IDENTIFY); + + if(!ret && !force) + { + dumpLog.WriteLine($"Cannot write ATAPI IDENTIFY PACKET DEVICE."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + } + } + + sense = dev.ScsiInquiry(out cmdBuf, out _); + if(!sense) + { + dumpLog.WriteLine("Requesting SCSI INQUIRY."); + ret = outputPlugin.WriteMediaTag(cmdBuf, MediaTagType.SCSI_INQUIRY); + + if(!ret && !force) + { + dumpLog.WriteLine($"Cannot write SCSI INQUIRY."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + + 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 _); + + decMode = null; + + if(!sense && !dev.Error) + if(Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue) + { + decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType); + ret = outputPlugin.WriteMediaTag(cmdBuf, MediaTagType.SCSI_MODESENSE_10); + + if(!ret && !force) + { + dumpLog.WriteLine($"Cannot write SCSI MODE SENSE (10)."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + } + + 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) + if(Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue) + { + decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType); + ret = outputPlugin.WriteMediaTag(cmdBuf, MediaTagType.SCSI_MODESENSE_6); + + if(!ret && !force) + { + dumpLog.WriteLine($"Cannot write SCSI MODE SENSE (6)."); + throw new ArgumentException(outputPlugin.ErrorMessage); + } + } + } + } + } + + dumpLog.WriteLine("Closing output file."); + DicConsole.WriteLine("Closing output file."); + outputPlugin.Close(); + + resume.BadBlocks.Sort(); + currentTry.Extents = ExtentsConverter.ToMetadata(extents); + + if(aborted) + { + dumpLog.WriteLine("Aborted!"); + return; + } + + dumpLog.WriteLine("Creating sidecar."); + FiltersList filters = new FiltersList(); + IFilter filter = filters.GetFilter(outputPath); + IMediaImage inputPlugin = ImageFormat.Detect(filter); + if(!inputPlugin.Open(filter)) throw new ArgumentException("Could not open created image."); + + DateTime chkStart = DateTime.UtcNow; + CICMMetadataType sidecar = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding); + end = DateTime.UtcNow; + + totalChkDuration = (end - chkStart).TotalMilliseconds; + dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds); + dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", + (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); + + if(opticalDisc) + { + // TODO: Implement layers + sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); + Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp); + sidecar.OpticalDisc[0].DiscType = xmlDskTyp; + sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp; + sidecar.OpticalDisc[0].DumpHardwareArray = resume.Tries.ToArray(); + + foreach(KeyValuePair tag in mediaTags) + if(outputPlugin.SupportedMediaTags.Contains(tag.Key)) + Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar); + } + else + { // All USB flash drives report as removable, even if the media is not removable if(!dev.IsRemovable || dev.IsUsb) { if(dev.IsUsb) - { - dumpLog.WriteLine("Reading USB descriptors."); - sidecar.BlockMedia[0].USB = new USBType - { - ProductID = dev.UsbProductId, - VendorID = dev.UsbVendorId, - Descriptors = new DumpType + if(outputPlugin.SupportedMediaTags.Contains(MediaTagType.USB_Descriptors)) + sidecar.BlockMedia[0].USB = new USBType { - Image = outputPrefix + ".usbdescriptors.bin", - Size = dev.UsbDescriptors.Length, - Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray() - } - }; - DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].USB.Descriptors.Image, dev.UsbDescriptors); - } + ProductID = dev.UsbProductId, + VendorID = dev.UsbVendorId, + Descriptors = new DumpType + { + Image = outputPath, + Size = dev.UsbDescriptors.Length, + Checksums = Checksum.GetChecksums(dev.UsbDescriptors).ToArray() + } + }; byte[] cmdBuf; if(dev.Type == DeviceType.ATAPI) { - dumpLog.WriteLine("Requesting ATAPI IDENTIFY PACKET DEVICE."); sense = dev.AtapiIdentify(out cmdBuf, out _); if(!sense) - { - sidecar.BlockMedia[0].ATA = new ATAType - { - Identify = new DumpType + if(outputPlugin.SupportedMediaTags.Contains(MediaTagType.ATAPI_IDENTIFY)) + sidecar.BlockMedia[0].ATA = new ATAType { - Image = outputPrefix + ".identify.bin", - Size = cmdBuf.Length, - Checksums = Checksum.GetChecksums(cmdBuf).ToArray() - } - }; - DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].ATA.Identify.Image, cmdBuf); - } + Identify = new DumpType + { + Image = outputPath, + Size = cmdBuf.Length, + Checksums = Checksum.GetChecksums(cmdBuf).ToArray() + } + }; } sense = dev.ScsiInquiry(out cmdBuf, out _); if(!sense) { - dumpLog.WriteLine("Requesting SCSI INQUIRY."); - sidecar.BlockMedia[0].SCSI = new SCSIType - { - Inquiry = new DumpType + if(outputPlugin.SupportedMediaTags.Contains(MediaTagType.SCSI_INQUIRY)) + sidecar.BlockMedia[0].SCSI = new SCSIType { - Image = outputPrefix + ".inquiry.bin", - Size = cmdBuf.Length, - Checksums = Checksum.GetChecksums(cmdBuf).ToArray() - } - }; - DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.Inquiry.Image, cmdBuf); + Inquiry = new DumpType + { + Image = outputPath, + Size = cmdBuf.Length, + Checksums = Checksum.GetChecksums(cmdBuf).ToArray() + } + }; + // TODO: SCSI Extended Vendor Page descriptors + /* dumpLog.WriteLine("Reading SCSI Extended Vendor Page Descriptors."); sense = dev.ScsiInquiry(out cmdBuf, out _, 0x00); if(!sense) @@ -227,6 +767,7 @@ namespace DiscImageChef.Core.Devices.Dumping if(evpds.Count > 0) sidecar.BlockMedia[0].SCSI.EVPD = evpds.ToArray(); } } + */ dumpLog.WriteLine("Requesting MODE SENSE (10)."); sense = dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current, 0x3F, @@ -235,20 +776,17 @@ namespace DiscImageChef.Core.Devices.Dumping sense = dev.ModeSense10(out cmdBuf, out _, false, true, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5, out _); - Modes.DecodedMode? decMode = null; + decMode = null; if(!sense && !dev.Error) if(Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue) - { - decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType); - sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType - { - Image = outputPrefix + ".modesense10.bin", - Size = cmdBuf.Length, - Checksums = Checksum.GetChecksums(cmdBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense10.Image, cmdBuf); - } + if(outputPlugin.SupportedMediaTags.Contains(MediaTagType.SCSI_MODESENSE_10)) + sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType + { + Image = outputPath, + Size = cmdBuf.Length, + Checksums = Checksum.GetChecksums(cmdBuf).ToArray() + }; dumpLog.WriteLine("Requesting MODE SENSE (6)."); sense = dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, @@ -260,566 +798,37 @@ namespace DiscImageChef.Core.Devices.Dumping if(!sense && !dev.Error) if(Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue) - { - decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType); - sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType - { - Image = outputPrefix + ".modesense.bin", - Size = cmdBuf.Length, - Checksums = Checksum.GetChecksums(cmdBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense.Image, cmdBuf); - } - - if(decMode.HasValue) - { - scsiMediumType = (byte)decMode.Value.Header.MediumType; - 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(outputPlugin.SupportedMediaTags.Contains(MediaTagType.SCSI_MODESENSE_6)) + sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType + { + Image = outputPath, + Size = cmdBuf.Length, + Checksums = Checksum.GetChecksums(cmdBuf).ToArray() + }; } } - } - 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) - { - 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); - DicConsole.WriteLine("Reading {0} raw bytes ({1} cooked bytes) per sector.", longBlockSize, - blockSize * blocksToRead); - physicalBlockSize = longBlockSize; - blockSize = longBlockSize; - } - - DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); - - string outputExtension = ".bin"; - 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); - } - - DumpHardwareType currentTry = null; - ExtentsULong extents = null; - ResumeSupport.Process(true, dev.IsRemovable, blocks, dev.Manufacturer, dev.Model, dev.Serial, - dev.PlatformId, ref resume, ref currentTry, ref extents); - if(currentTry == null || extents == null) - throw new InvalidOperationException("Could not process resume file, not continuing..."); - - dumpFile.Seek(resume.NextBlock, blockSize); - if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); - - 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 = (uint)(blocks - i); - -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator - if(currentSpeed > maxSpeed && currentSpeed != 0) maxSpeed = currentSpeed; - if(currentSpeed < minSpeed && currentSpeed != 0) minSpeed = currentSpeed; -#pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator - - DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, blocks, currentSpeed); - - 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); - extents.Add(i, blocksToRead, true); - } - else - { - // TODO: Reset device after X errors - if(stopOnError) return; // TODO: Return more cleanly - - // Write empty data - dumpFile.Write(new byte[blockSize * blocksToRead]); - - for(ulong b = i; b < i + blocksToRead; b++) resume.BadBlocks.Add(b); - - mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration); - - ibgLog.Write(i, 0); - 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; - resume.NextBlock = i + blocksToRead; - } - - end = DateTime.UtcNow; - 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)); - - #region Error handling - if(resume.BadBlocks.Count > 0 && !aborted) - { - int pass = 0; - bool forward = true; - bool runningPersistent = false; - - repeatRetry: - ulong[] tmpArray = resume.BadBlocks.ToArray(); - foreach(ulong badSector in tmpArray) - { - if(aborted) - { - currentTry.Extents = ExtentsConverter.ToMetadata(extents); - dumpLog.WriteLine("Aborted!"); - break; - } - - DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass + 1, - forward ? "forward" : "reverse", - runningPersistent ? "recovering partial data, " : ""); - - sense = scsiReader.ReadBlock(out readBuffer, badSector, out double cmdDuration); - totalDuration += cmdDuration; - - if(!sense && !dev.Error) - { - resume.BadBlocks.Remove(badSector); - extents.Add(badSector); - dumpFile.WriteAt(readBuffer, badSector, blockSize); - dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); - } - else if(runningPersistent) dumpFile.WriteAt(readBuffer, badSector, blockSize); - } - - if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) - { - pass++; - forward = !forward; - resume.BadBlocks.Sort(); - resume.BadBlocks.Reverse(); - goto repeatRetry; - } - - Modes.ModePage? currentModePage = null; - byte[] md6; - byte[] md10; - - if(!runningPersistent && persistent) - { - if(dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice) - { - Modes.ModePage_01_MMC pgMmc = - new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x20}; - Modes.DecodedMode md = new Modes.DecodedMode - { - Header = new Modes.ModeHeader(), - Pages = new[] - { - new Modes.ModePage - { - Page = 0x01, - Subpage = 0x00, - PageResponse = Modes.EncodeModePage_01_MMC(pgMmc) - } - } - }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); - md10 = Modes.EncodeMode10(md, dev.ScsiType); - } - else - { - Modes.ModePage_01 pg = new Modes.ModePage_01 - { - PS = false, - AWRE = false, - ARRE = false, - TB = true, - RC = false, - EER = true, - PER = false, - DTE = false, - DCR = false, - ReadRetryCount = 255 - }; - Modes.DecodedMode md = new Modes.DecodedMode - { - Header = new Modes.ModeHeader(), - Pages = new[] - { - new Modes.ModePage - { - Page = 0x01, - Subpage = 0x00, - PageResponse = Modes.EncodeModePage_01(pg) - } - } - }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); - md10 = Modes.EncodeMode10(md, dev.ScsiType); - } - - 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) - { - Modes.DecodedMode md = new Modes.DecodedMode - { - Header = new Modes.ModeHeader(), - Pages = new[] {currentModePage.Value} - }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); - md10 = Modes.EncodeMode10(md, dev.ScsiType); - - dumpLog.WriteLine("Sending MODE SELECT to drive."); - 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 - - resume.BadBlocks.Sort(); - currentTry.Extents = ExtentsConverter.ToMetadata(extents); - - Checksum dataChk = new Checksum(); - dumpFile.Seek(0, SeekOrigin.Begin); - blocksToRead = 500; - - dumpLog.WriteLine("Checksum starts."); - for(ulong i = 0; i < blocks; i += blocksToRead) - { - if(aborted) - { - dumpLog.WriteLine("Aborted!"); - break; - } - - 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; - } - - DicConsole.WriteLine(); - dumpFile.Close(); - end = DateTime.UtcNow; - dumpLog.WriteLine("Checksum finished in {0} seconds.", (end - start).TotalSeconds); - 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.Open(inputFilter)) imageFormat = null; } - catch { imageFormat = null; } - - if(imageFormat != null) - { - dumpLog.WriteLine("Getting partitions."); - List partitions = Partitions.GetAll(imageFormat); - Partitions.AddSchemesToStats(partitions); - dumpLog.WriteLine("Found {0} partitions.", partitions.Count); - - if(partitions.Count > 0) - { - xmlFileSysInfo = new PartitionType[partitions.Count]; - for(int i = 0; i < partitions.Count; i++) - { - xmlFileSysInfo[i] = new PartitionType - { - 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 - }; - List lstFs = new List(); - dumpLog.WriteLine("Getting filesystems on partition {0}, starting at {1}, ending at {2}, with type {3}, under scheme {4}.", - 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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[i].FileSystems = lstFs.ToArray(); - } - } - else - { - dumpLog.WriteLine("Getting filesystem for whole device."); - xmlFileSysInfo = new PartitionType[1]; - xmlFileSysInfo[0] = new PartitionType {EndSector = (int)(blocks - 1), StartSector = 0}; - List lstFs = new List(); - - 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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[0].FileSystems = lstFs.ToArray(); - } - } - - if(alcohol != null && !dumpRaw) alcohol.SetMediaType(dskType); - - if(opticalDisc) - { - sidecar.OpticalDisc[0].Checksums = dataChk.End().ToArray(); - sidecar.OpticalDisc[0].DumpHardwareArray = resume.Tries.ToArray(); - 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; - sidecar.OpticalDisc[0].Tracks = new[] {1}; - sidecar.OpticalDisc[0].Track = new Schemas.TrackType[1]; - sidecar.OpticalDisc[0].Track[0] = new Schemas.TrackType - { - BytesPerSector = (int)blockSize, - Checksums = sidecar.OpticalDisc[0].Checksums, - EndSector = (long)(blocks - 1), - Image = - new ImageType - { - format = "BINARY", - offset = 0, - offsetSpecified = true, - Value = sidecar.OpticalDisc[0].Image.Value - }, - Sequence = new TrackSequenceType {Session = 1, TrackNumber = 1}, - Size = (long)(blocks * blockSize), - StartSector = 0 - }; - 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; - } - - sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); - Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp); - sidecar.OpticalDisc[0].DiscType = xmlDskTyp; - sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp; - } - else - { - sidecar.BlockMedia[0].Checksums = dataChk.End().ToArray(); sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp); - sidecar.BlockMedia[0].DiskType = xmlDskTyp; + sidecar.BlockMedia[0].DiskType = xmlDskTyp; sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; // TODO: Implement device firmware revision - sidecar.BlockMedia[0].Image = new ImageType - { - format = "Raw disk image (sector by sector copy)", - Value = outputPrefix + ".bin" - }; if(!dev.IsRemovable || dev.IsUsb) - if(dev.Type == DeviceType.ATAPI) sidecar.BlockMedia[0].Interface = "ATAPI"; - else if(dev.IsUsb) sidecar.BlockMedia[0].Interface = "USB"; - else if(dev.IsFireWire) sidecar.BlockMedia[0].Interface = "FireWire"; - else sidecar.BlockMedia[0].Interface = "SCSI"; - sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; + if(dev.Type == DeviceType.ATAPI) + sidecar.BlockMedia[0].Interface = "ATAPI"; + else if(dev.IsUsb) + sidecar.BlockMedia[0].Interface = "USB"; + else if(dev.IsFireWire) + sidecar.BlockMedia[0].Interface = "FireWire"; + else + sidecar.BlockMedia[0].Interface = "SCSI"; + sidecar.BlockMedia[0].LogicalBlocks = (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); - if(xmlFileSysInfo != null) sidecar.BlockMedia[0].FileSystemInformation = xmlFileSysInfo; + 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); if(dev.IsRemovable) sidecar.BlockMedia[0].DumpHardwareArray = resume.Tries.ToArray(); } @@ -832,7 +841,7 @@ namespace DiscImageChef.Core.Devices.Dumping (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); + DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count); DicConsole.WriteLine(); if(!aborted) @@ -844,7 +853,6 @@ namespace DiscImageChef.Core.Devices.Dumping XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType)); xmlSer.Serialize(xmlFs, sidecar); xmlFs.Close(); - if(alcohol != null && !dumpRaw) alcohol.Close(); } Statistics.AddMedia(dskType, true); diff --git a/DiscImageChef.Core/Devices/Dumping/SCSI.cs b/DiscImageChef.Core/Devices/Dumping/SCSI.cs index cb2a735d..b2b5ebe4 100644 --- a/DiscImageChef.Core/Devices/Dumping/SCSI.cs +++ b/DiscImageChef.Core/Devices/Dumping/SCSI.cs @@ -31,14 +31,15 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.Text; using System.Threading; using DiscImageChef.Console; using DiscImageChef.Core.Logging; using DiscImageChef.Decoders.SCSI; using DiscImageChef.Devices; +using DiscImageChef.DiscImages; using DiscImageChef.Metadata; -using Schemas; using MediaType = DiscImageChef.CommonTypes.MediaType; namespace DiscImageChef.Core.Devices.Dumping @@ -55,6 +56,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump long or scrambled sectors @@ -63,15 +65,21 @@ namespace DiscImageChef.Core.Devices.Dumping /// Information for dump resuming /// Dump logger /// Encoding to use when analyzing dump - /// Write subchannel separate from main channel /// Try to read and dump as much Lead-in as possible + /// Path to output file + /// Formats to pass to output file plugin /// If you asked to dump long sectors from a SCSI Streaming device - public static void Dump(Device dev, string devicePath, string outputPrefix, ushort retryPasses, bool force, - bool dumpRaw, bool persistent, bool stopOnError, bool separateSubchannel, - ref Resume resume, ref DumpLog dumpLog, bool dumpLeadIn, Encoding encoding) + public static void Dump(Device dev, string devicePath, IWritableImage outputPlugin, ushort retryPasses, + bool force, bool dumpRaw, bool persistent, bool stopOnError, + ref Resume resume, + ref + DumpLog dumpLog, bool dumpLeadIn, Encoding encoding, + string outputPrefix, + string + outputPath, Dictionary formatOptions) { MediaType dskType = MediaType.Unknown; - int resets = 0; + int resets = 0; if(dev.IsRemovable) { @@ -191,23 +199,22 @@ namespace DiscImageChef.Core.Devices.Dumping } } - CICMMetadataType sidecar = new CICMMetadataType(); - switch(dev.ScsiType) { case PeripheralDeviceTypes.SequentialAccess: if(dumpRaw) throw new ArgumentException("Tapes cannot be dumped raw."); - Ssc.Dump(dev, outputPrefix, devicePath, ref sidecar, ref resume, ref dumpLog); + Ssc.Dump(dev, outputPrefix, devicePath, ref resume, ref dumpLog); return; case PeripheralDeviceTypes.MultiMediaDevice: - Mmc.Dump(dev, devicePath, outputPrefix, retryPasses, force, dumpRaw, persistent, stopOnError, - ref sidecar, ref dskType, separateSubchannel, ref resume, ref dumpLog, dumpLeadIn, - encoding); + Mmc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, + ref dskType, ref resume, ref dumpLog, dumpLeadIn, encoding, outputPrefix, outputPath, + formatOptions); return; default: - Sbc.Dump(dev, devicePath, outputPrefix, retryPasses, force, dumpRaw, persistent, stopOnError, - ref sidecar, ref dskType, false, ref resume, ref dumpLog, encoding); + Sbc.Dump(dev, devicePath, outputPlugin, retryPasses, force, dumpRaw, persistent, stopOnError, null, + ref dskType, false, ref resume, ref dumpLog, encoding, outputPrefix, outputPath, + formatOptions); break; } } diff --git a/DiscImageChef.Core/Devices/Dumping/SSC.cs b/DiscImageChef.Core/Devices/Dumping/SSC.cs index 31a0405f..1dbcabfe 100644 --- a/DiscImageChef.Core/Devices/Dumping/SSC.cs +++ b/DiscImageChef.Core/Devices/Dumping/SSC.cs @@ -47,6 +47,7 @@ using Version = DiscImageChef.Metadata.Version; namespace DiscImageChef.Core.Devices.Dumping { + // TODO: Add support for images static class Ssc { /// @@ -57,24 +58,23 @@ namespace DiscImageChef.Core.Devices.Dumping /// Prefix for output data files /// Information for dump resuming /// Dump logger - /// Partially filled initialized sidecar - internal static void Dump(Device dev, string outputPrefix, string devicePath, ref CICMMetadataType sidecar, - ref Resume resume, - ref DumpLog dumpLog) + internal static void Dump(Device dev, string outputPrefix, string devicePath, ref Resume resume, + ref DumpLog dumpLog) { - FixedSense? fxSense; - bool aborted; - bool sense; - ulong blocks = 0; - uint blockSize; - MediaType dskType = MediaType.Unknown; - DateTime start; - DateTime end; - double totalDuration = 0; - double totalChkDuration = 0; - double currentSpeed = 0; - double maxSpeed = double.MinValue; - double minSpeed = double.MaxValue; + FixedSense? fxSense; + bool aborted; + bool sense; + ulong blocks = 0; + uint blockSize; + MediaType dskType = MediaType.Unknown; + DateTime start; + DateTime end; + double totalDuration = 0; + double totalChkDuration = 0; + double currentSpeed = 0; + double maxSpeed = double.MinValue; + double minSpeed = double.MaxValue; + CICMMetadataType sidecar = new CICMMetadataType(); dev.RequestSense(out byte[] senseBuf, dev.Timeout, out double duration); fxSense = Sense.DecodeFixed(senseBuf, out string strSense); diff --git a/DiscImageChef.Core/Devices/Dumping/SecureDigital.cs b/DiscImageChef.Core/Devices/Dumping/SecureDigital.cs index 1cdc9ef5..7ae324b8 100644 --- a/DiscImageChef.Core/Devices/Dumping/SecureDigital.cs +++ b/DiscImageChef.Core/Devices/Dumping/SecureDigital.cs @@ -33,20 +33,19 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Xml.Serialization; -using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Core.Logging; using DiscImageChef.Decoders.MMC; using DiscImageChef.Devices; using DiscImageChef.DiscImages; -using DiscImageChef.Filesystems; using DiscImageChef.Filters; using DiscImageChef.Metadata; using Extents; using Schemas; -using MediaType = DiscImageChef.Metadata.MediaType; +using MediaType = DiscImageChef.CommonTypes.MediaType; namespace DiscImageChef.Core.Devices.Dumping { @@ -61,6 +60,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump long or scrambled sectors @@ -69,10 +69,16 @@ namespace DiscImageChef.Core.Devices.Dumping /// Information for dump resuming /// Dump logger /// Encoding to use when analyzing dump + /// Path to output file + /// Formats to pass to output file plugin /// If you asked to dump long sectors from a SCSI Streaming device - public static void Dump(Device dev, string devicePath, string outputPrefix, ushort retryPasses, bool force, - bool dumpRaw, bool persistent, bool stopOnError, ref Resume resume, ref DumpLog dumpLog, - Encoding encoding) + 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 + formatOptions) { bool aborted; @@ -88,22 +94,22 @@ namespace DiscImageChef.Core.Devices.Dumping } } - bool sense; + bool sense; const ushort SD_PROFILE = 0x0001; - const uint TIMEOUT = 5; - double duration; + const uint TIMEOUT = 5; + double duration; - CICMMetadataType sidecar = new CICMMetadataType {BlockMedia = new[] {new BlockMediaType()}}; + 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; - 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 mediaTags = new Dictionary(); switch(dev.Type) { @@ -114,13 +120,15 @@ namespace DiscImageChef.Core.Devices.Dumping 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; + 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; @@ -131,17 +139,20 @@ namespace DiscImageChef.Core.Devices.Dumping 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); + 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); + sense = dev.ReadOcr(out ocr, out _, TIMEOUT, out duration); if(sense) ocr = null; + else mediaTags.Add(MediaTagType.MMC_OCR, null); - sidecar.BlockMedia[0].MultiMediaCard = new MultiMediaCardType(); break; } case DeviceType.SecureDigital: @@ -151,115 +162,45 @@ namespace DiscImageChef.Core.Devices.Dumping 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); + 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); + 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); + sense = dev.ReadScr(out scr, out _, TIMEOUT, out duration); if(sense) scr = null; + else mediaTags.Add(MediaTagType.SD_SCR, null); - sidecar.BlockMedia[0].SecureDigital = new SecureDigitalType(); break; } } dumpLog.WriteLine("Reading CID"); - sense = dev.ReadCid(out byte[] cid, out _, TIMEOUT, out duration); + sense = dev.ReadCid(out byte[] cid, out _, TIMEOUT, out duration); if(sense) cid = null; - - DumpType cidDump = null; - DumpType csdDump = null; - DumpType ocrDump = null; - - if(cid != null) - { - cidDump = new DumpType - { - Image = outputPrefix + ".cid.bin", - Size = cid.Length, - Checksums = Checksum.GetChecksums(cid).ToArray() - }; - DataFile.WriteTo("MMC/SecureDigital Dump", cidDump.Image, cid); - } - - if(csd != null) - { - csdDump = new DumpType - { - Image = outputPrefix + ".csd.bin", - Size = csd.Length, - Checksums = Checksum.GetChecksums(csd).ToArray() - }; - DataFile.WriteTo("MMC/SecureDigital Dump", csdDump.Image, csd); - } - - if(ecsd != null) - { - sidecar.BlockMedia[0].MultiMediaCard.ExtendedCSD = new DumpType - { - Image = outputPrefix + ".ecsd.bin", - Size = ecsd.Length, - Checksums = Checksum.GetChecksums(ecsd).ToArray() - }; - DataFile.WriteTo("MMC/SecureDigital Dump", sidecar.BlockMedia[0].MultiMediaCard.ExtendedCSD.Image, - ecsd); - } - - if(ocr != null) - { - ocrDump = new DumpType - { - Image = outputPrefix + ".ocr.bin", - Size = ocr.Length, - Checksums = Checksum.GetChecksums(ocr).ToArray() - }; - DataFile.WriteTo("MMC/SecureDigital Dump", ocrDump.Image, ocr); - } - - if(scr != null) - { - sidecar.BlockMedia[0].SecureDigital.SCR = new DumpType - { - Image = outputPrefix + ".scr.bin", - Size = scr.Length, - Checksums = Checksum.GetChecksums(scr).ToArray() - }; - DataFile.WriteTo("MMC/SecureDigital Dump", sidecar.BlockMedia[0].SecureDigital.SCR.Image, scr); - } - - 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; - } + else mediaTags.Add(dev.Type == DeviceType.SecureDigital ? MediaTagType.SD_CID : MediaTagType.MMC_CID, null); DateTime start; DateTime end; - double totalDuration = 0; - double totalChkDuration = 0; - double currentSpeed = 0; - double maxSpeed = double.MinValue; - double minSpeed = double.MaxValue; + double totalDuration = 0; + double totalChkDuration = 0; + double currentSpeed = 0; + double maxSpeed = double.MinValue; + double minSpeed = double.MaxValue; - aborted = false; + aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; if(blocks == 0) @@ -272,7 +213,7 @@ namespace DiscImageChef.Core.Devices.Dumping dumpLog.WriteLine("Device reports {0} blocks.", blocks); byte[] cmdBuf; - bool error; + bool error; while(true) { @@ -293,18 +234,50 @@ namespace DiscImageChef.Core.Devices.Dumping dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead); DumpHardwareType currentTry = null; - ExtentsULong extents = 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); - DataFile dumpFile = new DataFile(outputPrefix + ".bin"); - dumpFile.Seek(resume.NextBlock, blockSize); + 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; @@ -319,10 +292,10 @@ namespace DiscImageChef.Core.Devices.Dumping if(blocks - i < blocksToRead) blocksToRead = (byte)(blocks - i); -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + #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 + #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); @@ -333,7 +306,7 @@ namespace DiscImageChef.Core.Devices.Dumping { mhddLog.Write(i, duration); ibgLog.Write(i, currentSpeed * 1024); - dumpFile.Write(cmdBuf); + outputPlugin.WriteSectors(cmdBuf, i, blocksToRead); extents.Add(i, blocksToRead, true); } else @@ -343,29 +316,32 @@ namespace DiscImageChef.Core.Devices.Dumping mhddLog.Write(i, duration < 500 ? 65535 : duration); ibgLog.Write(i, 0); - dumpFile.Write(new byte[blockSize * blocksToRead]); + outputPlugin.WriteSectors(new byte[blockSize * blocksToRead], i, blocksToRead); dumpLog.WriteLine("Error reading {0} blocks from block {1}.", blocksToRead, i); } - double newSpeed = (double)blockSize * blocksToRead / 1048576 / (duration / 1000); + double newSpeed = + (double)blockSize * blocksToRead / 1048576 / (duration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; - resume.NextBlock = i + blocksToRead; + 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); + 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)); #region Error handling if(resume.BadBlocks.Count > 0 && !aborted) { - int pass = 0; - bool forward = true; + int pass = 0; + bool forward = true; bool runningPersistent = false; repeatRetryLba: @@ -392,10 +368,11 @@ namespace DiscImageChef.Core.Devices.Dumping { resume.BadBlocks.Remove(badSector); extents.Add(badSector); - dumpFile.WriteAt(cmdBuf, badSector, blockSize); + outputPlugin.WriteSector(cmdBuf, badSector); dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } - else if(runningPersistent) dumpFile.WriteAt(cmdBuf, badSector, blockSize); + else if(runningPersistent) + outputPlugin.WriteSector(cmdBuf, badSector); } if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) @@ -413,179 +390,190 @@ namespace DiscImageChef.Core.Devices.Dumping currentTry.Extents = ExtentsConverter.ToMetadata(extents); - Checksum dataChk = new Checksum(); - dumpFile.Seek(0, SeekOrigin.Begin); - blocksToRead = 500; + dumpLog.WriteLine("Closing output file."); + DicConsole.WriteLine("Closing output file."); + outputPlugin.Close(); - dumpLog.WriteLine("Checksum starts."); - for(ulong i = 0; i < blocks; i += blocksToRead) + if(aborted) { - if(aborted) - { - dumpLog.WriteLine("Aborted!"); - break; - } - - if(blocks - i < blocksToRead) blocksToRead = (byte)(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; - } - - DicConsole.WriteLine(); - dumpFile.Close(); - end = DateTime.UtcNow; - dumpLog.WriteLine("Checksum finished in {0} seconds.", (end - start).TotalSeconds); - 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 + ".bin"); - - if(inputFilter == null) - { - DicConsole.ErrorWriteLine("Cannot open file just created, this should not happen."); + dumpLog.WriteLine("Aborted!"); return; } - IMediaImage imageFormat = ImageFormat.Detect(inputFilter); - PartitionType[] xmlFileSysInfo = null; + 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."); - try { if(!imageFormat.Open(inputFilter)) imageFormat = null; } - catch { imageFormat = null; } + DateTime chkStart = DateTime.UtcNow; + CICMMetadataType sidecar = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding); - if(imageFormat != null) + switch(dev.Type) { - dumpLog.WriteLine("Getting partitions."); - List partitions = Partitions.GetAll(imageFormat); - Partitions.AddSchemesToStats(partitions); - dumpLog.WriteLine("Found {0} partitions.", partitions.Count); + case DeviceType.MMC: + sidecar.BlockMedia[0].MultiMediaCard = new MultiMediaCardType(); + break; + case DeviceType.SecureDigital: + sidecar.BlockMedia[0].SecureDigital = new SecureDigitalType(); + break; + } - if(partitions.Count > 0) + DumpType cidDump = null; + DumpType csdDump = null; + DumpType ocrDump = null; + + if(cid != null) + { + cidDump = new DumpType { - xmlFileSysInfo = new PartitionType[partitions.Count]; - for(int i = 0; i < partitions.Count; i++) - { - xmlFileSysInfo[i] = new PartitionType - { - 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 - }; - List lstFs = new List(); - dumpLog.WriteLine("Getting filesystems on partition {0}, starting at {1}, ending at {2}, with type {3}, under scheme {4}.", - i, partitions[i].Start, partitions[i].End, partitions[i].Type, - partitions[i].Scheme); + Image = outputPath, + Size = cid.Length, + Checksums = Checksum.GetChecksums(cid).ToArray() + }; - foreach(IFilesystem plugin in plugins.PluginsList.Values) - try - { - if(!plugin.Identify(imageFormat, partitions[i])) continue; + ret = + outputPlugin.WriteMediaTag(cid, + dev.Type == DeviceType.SecureDigital + ? MediaTagType.SD_CID + : MediaTagType.MMC_CID); - plugin.GetInformation(imageFormat, partitions[i], out _, encoding); - lstFs.Add(plugin.XmlFsType); - Statistics.AddFilesystem(plugin.XmlFsType.Type); - dumpLog.WriteLine("Filesystem {0} found.", plugin.XmlFsType.Type); - } -#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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[i].FileSystems = lstFs.ToArray(); - } - } - else + // Cannot write CID to image + if(!ret && !force) { - dumpLog.WriteLine("Getting filesystem for whole device."); - - xmlFileSysInfo = new PartitionType[1]; - xmlFileSysInfo[0] = new PartitionType {EndSector = (int)(blocks - 1), StartSector = 0}; - List lstFs = new List(); - - 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); - } -#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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[0].FileSystems = lstFs.ToArray(); + dumpLog.WriteLine("Cannot write CID to output image."); + throw new ArgumentException(outputPlugin.ErrorMessage); } } - sidecar.BlockMedia[0].Checksums = dataChk.End().ToArray(); + 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: - MediaType.MediaTypeToString(CommonTypes.MediaType.MMC, out xmlDskTyp, out xmlDskSubTyp); - sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(CommonTypes.MediaType.MMC); + Metadata.MediaType.MediaTypeToString(MediaType.MMC, out xmlDskTyp, out xmlDskSubTyp); + sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(MediaType.MMC); break; case DeviceType.SecureDigital: - MediaType.MediaTypeToString(CommonTypes.MediaType.SecureDigital, out xmlDskTyp, out xmlDskSubTyp); - sidecar.BlockMedia[0].Dimensions = - Dimensions.DimensionsFromMediaType(CommonTypes.MediaType.SecureDigital); + 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].DiskType = xmlDskTyp; sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp; // TODO: Implement device firmware revision - sidecar.BlockMedia[0].Image = new ImageType - { - format = "Raw disk image (sector by sector copy)", - Value = outputPrefix + ".bin" - }; - switch(dev.Type) - { - case DeviceType.MMC: - sidecar.BlockMedia[0].Interface = "MultiMediaCard"; - break; - case DeviceType.SecureDigital: - sidecar.BlockMedia[0].Interface = "SecureDigital"; - break; - } - - sidecar.BlockMedia[0].LogicalBlocks = (long)blocks; + 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); - if(xmlFileSysInfo != null) sidecar.BlockMedia[0].FileSystemInformation = xmlFileSysInfo; + 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(); @@ -595,7 +583,7 @@ namespace DiscImageChef.Core.Devices.Dumping (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); + DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count); if(resume.BadBlocks.Count > 0) resume.BadBlocks.Sort(); DicConsole.WriteLine(); @@ -613,10 +601,10 @@ namespace DiscImageChef.Core.Devices.Dumping switch(dev.Type) { case DeviceType.MMC: - Statistics.AddMedia(CommonTypes.MediaType.MMC, true); + Statistics.AddMedia(MediaType.MMC, true); break; case DeviceType.SecureDigital: - Statistics.AddMedia(CommonTypes.MediaType.SecureDigital, true); + Statistics.AddMedia(MediaType.SecureDigital, true); break; } } diff --git a/DiscImageChef.Core/Devices/Dumping/XGD.cs b/DiscImageChef.Core/Devices/Dumping/XGD.cs index 1d1ebf0a..6fbb4796 100644 --- a/DiscImageChef.Core/Devices/Dumping/XGD.cs +++ b/DiscImageChef.Core/Devices/Dumping/XGD.cs @@ -33,9 +33,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Xml.Serialization; -using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Core.Logging; using DiscImageChef.Decoders.DVD; @@ -43,13 +43,11 @@ using DiscImageChef.Decoders.SCSI; using DiscImageChef.Decoders.Xbox; using DiscImageChef.Devices; using DiscImageChef.DiscImages; -using DiscImageChef.Filesystems; using DiscImageChef.Filters; using DiscImageChef.Metadata; using Extents; using Schemas; using MediaType = DiscImageChef.CommonTypes.MediaType; -using TrackType = Schemas.TrackType; namespace DiscImageChef.Core.Devices.Dumping { @@ -64,6 +62,7 @@ namespace DiscImageChef.Core.Devices.Dumping /// Device /// Path to the device /// Prefix for output data files + /// Plugin for output file /// How many times to retry /// Force to continue dump whenever possible /// Dump raw/long sectors @@ -72,30 +71,41 @@ namespace DiscImageChef.Core.Devices.Dumping /// Information for dump resuming /// Dump logger /// Encoding to use when analyzing dump - /// Partially filled initialized sidecar + /// Media tags as retrieved in MMC layer /// Disc type as detected in MMC layer + /// Path to output file + /// Formats to pass to output file plugin /// /// If the provided resume does not correspond with the current in progress /// dump /// - 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, ref Resume resume, ref DumpLog dumpLog, Encoding encoding) + internal static void Dump(Device dev, string devicePath, + IWritableImage outputPlugin, ushort retryPasses, + bool force, bool dumpRaw, + bool persistent, bool stopOnError, + Dictionary mediaTags, ref MediaType dskType, + ref Resume resume, + ref DumpLog dumpLog, + Encoding encoding, string outputPrefix, string outputPath, + Dictionary formatOptions) { - bool sense; - ulong blocks; - const uint BLOCK_SIZE = 2048; - uint blocksToRead = 64; - DateTime start; - DateTime end; - double totalDuration = 0; - double totalChkDuration = 0; - double currentSpeed = 0; - double maxSpeed = double.MinValue; - double minSpeed = double.MaxValue; - bool aborted = false; + bool sense; + ulong blocks; + const uint BLOCK_SIZE = 2048; + uint blocksToRead = 64; + DateTime start; + DateTime end; + double totalDuration = 0; + double totalChkDuration = 0; + double currentSpeed = 0; + double maxSpeed = double.MinValue; + double minSpeed = double.MaxValue; + bool aborted = false; System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true; + if(mediaTags.ContainsKey(MediaTagType.DVD_PFI)) mediaTags.Remove(MediaTagType.DVD_PFI); + if(mediaTags.ContainsKey(MediaTagType.DVD_DMI)) mediaTags.Remove(MediaTagType.DVD_DMI); + dumpLog.WriteLine("Reading Xbox Security Sector."); sense = dev.KreonExtractSs(out byte[] ssBuf, out byte[] senseBuf, dev.Timeout, out _); if(sense) @@ -114,26 +124,9 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - byte[] tmpBuf = new byte[ssBuf.Length - 4]; + byte[] tmpBuf = new byte[ssBuf.Length - 4]; Array.Copy(ssBuf, 4, tmpBuf, 0, ssBuf.Length - 4); - sidecar.OpticalDisc[0].Xbox = new XboxType - { - SecuritySectors = new[] - { - new XboxSecuritySectorsType - { - RequestNumber = 0, - RequestVersion = 1, - SecuritySectors = new DumpType - { - Image = outputPrefix + ".ss.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - } - } - } - }; - DataFile.WriteTo("SCSI Dump", outputPrefix + ".ss.bin", ssBuf); + mediaTags.Add(MediaTagType.Xbox_SecuritySector, tmpBuf); ulong l0Video, l1Video, middleZone, gameSize, totalSize, layerBreak; @@ -168,18 +161,12 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - tmpBuf = new byte[readBuffer.Length - 4]; + tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); - sidecar.OpticalDisc[0].PFI = new DumpType - { - Image = outputPrefix + ".pfi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].PFI.Image, tmpBuf, "Locked PFI", true); + mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf); DicConsole.DebugWriteLine("Dump-media command", "Video partition total size: {0} sectors", totalSize); l0Video = PFI.Decode(readBuffer).Value.Layer0EndPSN - PFI.Decode(readBuffer).Value.DataAreaStartPSN + 1; - l1Video = totalSize - l0Video + 1; + l1Video = totalSize - l0Video + 1; dumpLog.WriteLine("Reading Disc Manufacturing Information."); sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _); @@ -190,15 +177,9 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - tmpBuf = new byte[readBuffer.Length - 4]; + tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); - sidecar.OpticalDisc[0].DMI = new DumpType - { - Image = outputPrefix + ".dmi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].DMI.Image, tmpBuf, "Locked DMI", true); + mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf); // Get game partition size DicConsole.DebugWriteLine("Dump-media command", "Getting game partition size"); @@ -256,20 +237,15 @@ namespace DiscImageChef.Core.Devices.Dumping } DicConsole.DebugWriteLine("Dump-media command", "Unlocked total size: {0} sectors", totalSize); - blocks = totalSize + 1; + blocks = totalSize + 1; middleZone = - totalSize - (PFI.Decode(readBuffer).Value.Layer0EndPSN - PFI.Decode(readBuffer).Value.DataAreaStartPSN + - 1) - gameSize + 1; + totalSize - (PFI.Decode(readBuffer).Value.Layer0EndPSN - + PFI.Decode(readBuffer).Value.DataAreaStartPSN + + 1) - gameSize + 1; - tmpBuf = new byte[readBuffer.Length - 4]; + tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); - sidecar.OpticalDisc[0].Xbox.PFI = new DumpType - { - Image = outputPrefix + ".xboxpfi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].Xbox.PFI.Image, tmpBuf, "Unlocked PFI", true); + mediaTags.Add(MediaTagType.Xbox_PFI, tmpBuf); dumpLog.WriteLine("Reading Disc Manufacturing Information."); sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0, @@ -281,33 +257,27 @@ namespace DiscImageChef.Core.Devices.Dumping return; } - tmpBuf = new byte[readBuffer.Length - 4]; + tmpBuf = new byte[readBuffer.Length - 4]; Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4); - sidecar.OpticalDisc[0].Xbox.DMI = new DumpType - { - Image = outputPrefix + ".xboxdmi.bin", - Size = tmpBuf.Length, - Checksums = Checksum.GetChecksums(tmpBuf).ToArray() - }; - DataFile.WriteTo("SCSI Dump", sidecar.OpticalDisc[0].Xbox.DMI.Image, tmpBuf, "Unlocked DMI", true); + mediaTags.Add(MediaTagType.Xbox_DMI, tmpBuf); - totalSize = l0Video + l1Video + middleZone * 2 + gameSize; - layerBreak = l0Video + middleZone + gameSize / 2; + totalSize = l0Video + l1Video + middleZone * 2 + gameSize; + layerBreak = l0Video + middleZone + gameSize / 2; DicConsole.WriteLine("Video layer 0 size: {0} sectors", l0Video); DicConsole.WriteLine("Video layer 1 size: {0} sectors", l1Video); - DicConsole.WriteLine("Middle zone size: {0} sectors", middleZone); - DicConsole.WriteLine("Game data size: {0} sectors", gameSize); - DicConsole.WriteLine("Total size: {0} sectors", totalSize); - DicConsole.WriteLine("Real layer break: {0}", layerBreak); + DicConsole.WriteLine("Middle zone size: {0} sectors", middleZone); + DicConsole.WriteLine("Game data size: {0} sectors", gameSize); + DicConsole.WriteLine("Total size: {0} sectors", totalSize); + DicConsole.WriteLine("Real layer break: {0}", layerBreak); DicConsole.WriteLine(); dumpLog.WriteLine("Video layer 0 size: {0} sectors", l0Video); dumpLog.WriteLine("Video layer 1 size: {0} sectors", l1Video); dumpLog.WriteLine("Middle zone 0 size: {0} sectors", middleZone); - dumpLog.WriteLine("Game data 0 size: {0} sectors", gameSize); - dumpLog.WriteLine("Total 0 size: {0} sectors", totalSize); - dumpLog.WriteLine("Real layer break: {0}", layerBreak); + dumpLog.WriteLine("Game data 0 size: {0} sectors", gameSize); + dumpLog.WriteLine("Total 0 size: {0} sectors", totalSize); + dumpLog.WriteLine("Real layer break: {0}", layerBreak); bool read12 = !dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, BLOCK_SIZE, 0, 1, false, dev.Timeout, out _); @@ -340,26 +310,53 @@ namespace DiscImageChef.Core.Devices.Dumping return; } + bool ret = true; + + foreach(MediaTagType tag in mediaTags.Keys) + { + if(outputPlugin.SupportedMediaTags.Contains(tag)) continue; + + ret = false; + dumpLog.WriteLine($"Output format does not support {tag}."); + DicConsole.ErrorWriteLine($"Output format does not support {tag}."); + } + + if(!ret) + { + dumpLog.WriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not "); + DicConsole.ErrorWriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not "); + if(!force) return; + } + dumpLog.WriteLine("Reading {0} sectors at a time.", blocksToRead); DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead); MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, BLOCK_SIZE, blocksToRead); - IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", 0x0010); - DataFile dumpFile = new DataFile(outputPrefix + ".iso"); + IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", 0x0010); + ret = outputPlugin.Create(outputPath, dskType, formatOptions, blocks, BLOCK_SIZE); + + // Cannot create image + if(!ret) + { + dumpLog.WriteLine("Error creating output image, not continuing."); + dumpLog.WriteLine(outputPlugin.ErrorMessage); + DicConsole.ErrorWriteLine("Error creating output image, not continuing."); + DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage); + return; + } start = DateTime.UtcNow; - double cmdDuration = 0; - uint saveBlocksToRead = blocksToRead; - DumpHardwareType currentTry = null; - ExtentsULong extents = null; + double cmdDuration = 0; + uint saveBlocksToRead = blocksToRead; + DumpHardwareType currentTry = null; + ExtentsULong extents = null; ResumeSupport.Process(true, true, totalSize, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId, ref resume, ref currentTry, ref extents); if(currentTry == null || extents == null) throw new NotImplementedException("Could not process resume file, not continuing..."); ulong currentSector = resume.NextBlock; - dumpFile.Seek(resume.NextBlock, BLOCK_SIZE); if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock); dumpLog.WriteLine("Reading game partition."); @@ -367,7 +364,7 @@ namespace DiscImageChef.Core.Devices.Dumping { if(aborted) { - resume.NextBlock = currentSector; + resume.NextBlock = currentSector; currentTry.Extents = ExtentsConverter.ToMetadata(extents); dumpLog.WriteLine("Aborted!"); break; @@ -382,19 +379,19 @@ namespace DiscImageChef.Core.Devices.Dumping if(xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN) extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000; else - extentStart = (xboxSs.Value.Layer0EndPSN + 1) * 2 - - ((xboxSs.Value.Extents[e].StartPSN ^ 0xFFFFFF) + 1) - 0x30000; + extentStart = (xboxSs.Value.Layer0EndPSN + 1) * 2 - + ((xboxSs.Value.Extents[e].StartPSN ^ 0xFFFFFF) + 1) - 0x30000; if(xboxSs.Value.Extents[e].EndPSN <= xboxSs.Value.Layer0EndPSN) extentEnd = xboxSs.Value.Extents[e].EndPSN - 0x30000; else - extentEnd = (xboxSs.Value.Layer0EndPSN + 1) * 2 - - ((xboxSs.Value.Extents[e].EndPSN ^ 0xFFFFFF) + 1) - 0x30000; + extentEnd = (xboxSs.Value.Layer0EndPSN + 1) * 2 - + ((xboxSs.Value.Extents[e].EndPSN ^ 0xFFFFFF) + 1) - 0x30000; } // After last extent else { extentStart = blocks; - extentEnd = blocks; + extentEnd = blocks; } if(currentSector > extentEnd) continue; @@ -412,10 +409,10 @@ namespace DiscImageChef.Core.Devices.Dumping if(extentStart - i < blocksToRead) blocksToRead = (uint)(extentStart - i); -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + #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 + #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, totalSize, currentSpeed); @@ -427,7 +424,7 @@ namespace DiscImageChef.Core.Devices.Dumping { mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); - dumpFile.Write(readBuffer); + outputPlugin.WriteSectors(readBuffer, i, blocksToRead); extents.Add(i, blocksToRead, true); } else @@ -436,7 +433,7 @@ namespace DiscImageChef.Core.Devices.Dumping if(stopOnError) return; // TODO: Return more cleanly // Write empty data - dumpFile.Write(new byte[BLOCK_SIZE * blocksToRead]); + outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], i, blocksToRead); for(ulong b = i; b < i + blocksToRead; b++) resume.BadBlocks.Add(b); @@ -448,15 +445,16 @@ namespace DiscImageChef.Core.Devices.Dumping dumpLog.WriteLine("Error reading {0} blocks from block {1}.", blocksToRead, i); string[] senseLines = Sense.PrettifySense(senseBuf).Split(new[] {Environment.NewLine}, StringSplitOptions - .RemoveEmptyEntries); + .RemoveEmptyEntries); foreach(string senseLine in senseLines) dumpLog.WriteLine(senseLine); } - double newSpeed = (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000); + double newSpeed = + (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000); if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; - blocksToRead = saveBlocksToRead; - currentSector = i + 1; - resume.NextBlock = currentSector; + blocksToRead = saveBlocksToRead; + currentSector = i + 1; + resume.NextBlock = currentSector; } for(ulong i = extentStart; i <= extentEnd; i += blocksToRead) @@ -473,10 +471,11 @@ namespace DiscImageChef.Core.Devices.Dumping mhddLog.Write(i, cmdDuration); ibgLog.Write(i, currentSpeed * 1024); - dumpFile.Write(new byte[blocksToRead * 2048]); + // Write empty data + outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], i, blocksToRead); blocksToRead = saveBlocksToRead; extents.Add(i, blocksToRead, true); - currentSector = i + 1; + currentSector = i + 1; resume.NextBlock = currentSector; } @@ -500,12 +499,13 @@ namespace DiscImageChef.Core.Devices.Dumping currentSpeed); mhddLog.Write(middle + currentSector, cmdDuration); - ibgLog.Write(middle + currentSector, currentSpeed * 1024); - dumpFile.Write(new byte[BLOCK_SIZE * blocksToRead]); + ibgLog.Write(middle + currentSector, currentSpeed * 1024); + // Write empty data + outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], middle + currentSector, blocksToRead); extents.Add(currentSector, blocksToRead, true); - currentSector += blocksToRead; - resume.NextBlock = currentSector; + currentSector += blocksToRead; + resume.NextBlock = currentSector; } blocksToRead = saveBlocksToRead; @@ -539,10 +539,10 @@ namespace DiscImageChef.Core.Devices.Dumping if(l0Video + l1Video - l1 < blocksToRead) blocksToRead = (uint)(l0Video + l1Video - l1); -#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + #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 + #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", currentSector, totalSize, currentSpeed); @@ -555,7 +555,7 @@ namespace DiscImageChef.Core.Devices.Dumping { mhddLog.Write(currentSector, cmdDuration); ibgLog.Write(currentSector, currentSpeed * 1024); - dumpFile.Write(readBuffer); + outputPlugin.WriteSectors(readBuffer, currentSector, blocksToRead); extents.Add(currentSector, blocksToRead, true); } else @@ -564,7 +564,7 @@ namespace DiscImageChef.Core.Devices.Dumping if(stopOnError) return; // TODO: Return more cleanly // Write empty data - dumpFile.Write(new byte[BLOCK_SIZE * blocksToRead]); + outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], currentSector, blocksToRead); // TODO: Handle errors in video partition //errored += blocksToRead; @@ -579,10 +579,11 @@ namespace DiscImageChef.Core.Devices.Dumping foreach(string senseLine in senseLines) dumpLog.WriteLine(senseLine); } - double newSpeed = (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000); - if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; - currentSector += blocksToRead; - resume.NextBlock = currentSector; + double newSpeed = + (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000); + if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; + currentSector += blocksToRead; + resume.NextBlock = currentSector; } dumpLog.WriteLine("Unlocking drive (Wxripper)."); @@ -605,8 +606,10 @@ namespace DiscImageChef.Core.Devices.Dumping DicConsole.WriteLine(); mhddLog.Close(); ibgLog.Close(dev, blocks, BLOCK_SIZE, (end - start).TotalSeconds, currentSpeed * 1024, - BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000), devicePath); - dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds); + BLOCK_SIZE * (double)(blocks + 1) / + 1024 / (totalDuration / 1000), devicePath); + dumpLog.WriteLine("Dump finished in {0} seconds.", + (end - start).TotalSeconds); dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.", (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000)); @@ -615,12 +618,14 @@ namespace DiscImageChef.Core.Devices.Dumping { List tmpList = new List(); - foreach(ulong ur in resume.BadBlocks) for(ulong i = ur; i < ur + blocksToRead; i++) tmpList.Add(i); + foreach(ulong ur in resume.BadBlocks) + for(ulong i = ur; i < ur + blocksToRead; i++) + tmpList.Add(i); tmpList.Sort(); - int pass = 0; - bool forward = true; + int pass = 0; + bool forward = true; bool runningPersistent = false; resume.BadBlocks = tmpList; @@ -648,10 +653,11 @@ namespace DiscImageChef.Core.Devices.Dumping { resume.BadBlocks.Remove(badSector); extents.Add(badSector); - dumpFile.WriteAt(readBuffer, badSector, BLOCK_SIZE); + outputPlugin.WriteSector(readBuffer, badSector); dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass); } - else if(runningPersistent) dumpFile.WriteAt(readBuffer, badSector, BLOCK_SIZE); + else if(runningPersistent) + outputPlugin.WriteSector(readBuffer, badSector); } if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0) @@ -664,65 +670,65 @@ namespace DiscImageChef.Core.Devices.Dumping } Modes.ModePage? currentModePage = null; - byte[] md6; - byte[] md10; + byte[] md6; + byte[] md10; if(!runningPersistent && persistent) { if(dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice) { - Modes.ModePage_01_MMC pgMmc = + Modes.ModePage_01_MMC pgMmc = new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x20}; - Modes.DecodedMode md = new Modes.DecodedMode + Modes.DecodedMode md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), - Pages = new[] + Pages = new[] { new Modes.ModePage { - Page = 0x01, - Subpage = 0x00, + Page = 0x01, + Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc) } } }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); + md6 = Modes.EncodeMode6(md, dev.ScsiType); md10 = Modes.EncodeMode10(md, dev.ScsiType); } else { Modes.ModePage_01 pg = new Modes.ModePage_01 { - PS = false, - AWRE = false, - ARRE = false, - TB = true, - RC = false, - EER = true, - PER = false, - DTE = false, - DCR = false, + PS = false, + AWRE = false, + ARRE = false, + TB = true, + RC = false, + EER = true, + PER = false, + DTE = false, + DCR = false, ReadRetryCount = 255 }; Modes.DecodedMode md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), - Pages = new[] + Pages = new[] { new Modes.ModePage { - Page = 0x01, - Subpage = 0x00, + Page = 0x01, + Subpage = 0x00, PageResponse = Modes.EncodeModePage_01(pg) } } }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); + md6 = Modes.EncodeMode6(md, dev.ScsiType); md10 = Modes.EncodeMode10(md, dev.ScsiType); } dumpLog.WriteLine("Sending MODE SELECT to drive."); - sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _); + sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _); if(sense) sense = dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _); runningPersistent = true; @@ -737,9 +743,9 @@ namespace DiscImageChef.Core.Devices.Dumping Modes.DecodedMode md = new Modes.DecodedMode { Header = new Modes.ModeHeader(), - Pages = new[] {currentModePage.Value} + Pages = new[] {currentModePage.Value} }; - md6 = Modes.EncodeMode6(md, dev.ScsiType); + md6 = Modes.EncodeMode6(md, dev.ScsiType); md10 = Modes.EncodeMode10(md, dev.ScsiType); dumpLog.WriteLine("Sending MODE SELECT to drive."); @@ -754,211 +760,64 @@ namespace DiscImageChef.Core.Devices.Dumping resume.BadBlocks.Sort(); currentTry.Extents = ExtentsConverter.ToMetadata(extents); - Checksum dataChk = new Checksum(); - dumpFile.Seek(0, SeekOrigin.Begin); - blocksToRead = 500; - - blocks = totalSize; - - dumpLog.WriteLine("Checksum starts."); - for(ulong i = 0; i < blocks; i += blocksToRead) + foreach(KeyValuePair tag in mediaTags) { - if(aborted) - { - dumpLog.WriteLine("Aborted!"); - break; - } + ret = outputPlugin.WriteMediaTag(tag.Value, tag.Key); + if(ret || force) continue; - 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[BLOCK_SIZE * blocksToRead]; - dumpFile.Read(dataToCheck, 0, (int)(BLOCK_SIZE * blocksToRead)); - dataChk.Update(dataToCheck); - DateTime chkEnd = DateTime.UtcNow; - - double chkDuration = (chkEnd - chkStart).TotalMilliseconds; - totalChkDuration += chkDuration; - - double newSpeed = (double)BLOCK_SIZE * blocksToRead / 1048576 / (chkDuration / 1000); - if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed; + // Cannot write tag to image + dumpLog.WriteLine($"Cannot write tag {tag.Key}."); + throw new ArgumentException(outputPlugin.ErrorMessage); } - DicConsole.WriteLine(); - dumpFile.Close(); - end = DateTime.UtcNow; - dumpLog.WriteLine("Checksum finished in {0} seconds.", (end - start).TotalSeconds); - dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", - (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); + dumpLog.WriteLine("Closing output file."); + DicConsole.WriteLine("Closing output file."); + outputPlugin.Close(); - PluginBase plugins = new PluginBase(); - FiltersList filtersList = new FiltersList(); - IFilter inputFilter = filtersList.GetFilter(outputPrefix + ".iso"); + resume.BadBlocks.Sort(); + currentTry.Extents = ExtentsConverter.ToMetadata(extents); - if(inputFilter == null) + if(aborted) { - DicConsole.ErrorWriteLine("Cannot open file just created, this should not happen."); + dumpLog.WriteLine("Aborted!"); return; } - IMediaImage imageFormat = ImageFormat.Detect(inputFilter); - PartitionType[] xmlFileSysInfo = null; + 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."); - try { if(!imageFormat.Open(inputFilter)) imageFormat = null; } - catch { imageFormat = null; } + DateTime chkStart = DateTime.UtcNow; + CICMMetadataType sidecar = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding); + end = DateTime.UtcNow; - if(imageFormat != null) - { - dumpLog.WriteLine("Getting partitions."); - List partitions = Partitions.GetAll(imageFormat); - Partitions.AddSchemesToStats(partitions); - dumpLog.WriteLine("Found {0} partitions.", partitions.Count); + totalChkDuration = (end - chkStart).TotalMilliseconds; + dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds); + dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.", + (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000)); - if(partitions.Count > 0) - { - xmlFileSysInfo = new PartitionType[partitions.Count]; - for(int i = 0; i < partitions.Count; i++) - { - xmlFileSysInfo[i] = new PartitionType - { - 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 - }; - List lstFs = new List(); - dumpLog.WriteLine("Getting filesystems on partition {0}, starting at {1}, ending at {2}, with type {3}, under scheme {4}.", - i, partitions[i].Start, partitions[i].End, partitions[i].Type, - partitions[i].Scheme); + foreach(KeyValuePair tag in mediaTags) + Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar); - 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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[i].FileSystems = lstFs.ToArray(); - } - } - else - { - dumpLog.WriteLine("Getting filesystem for whole device."); - xmlFileSysInfo = new PartitionType[1]; - xmlFileSysInfo[0] = new PartitionType {EndSector = (int)(blocks - 1), StartSector = 0}; - List lstFs = new List(); - - Partition wholePart = - new Partition {Name = "Whole device", Length = blocks, Size = blocks * BLOCK_SIZE}; - - 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); - } - - if(lstFs.Count > 0) xmlFileSysInfo[0].FileSystems = lstFs.ToArray(); - } - } - - sidecar.OpticalDisc[0].Checksums = dataChk.End().ToArray(); - sidecar.OpticalDisc[0].DumpHardwareArray = resume.Tries.ToArray(); - sidecar.OpticalDisc[0].Image = new ImageType - { - format = "Raw disk image (sector by sector copy)", - Value = outputPrefix + ".iso" - }; sidecar.OpticalDisc[0].Layers = new LayersType { - type = LayersTypeType.OTP, + type = LayersTypeType.OTP, typeSpecified = true, - Sectors = new SectorsType[1] + Sectors = new SectorsType[1] }; sidecar.OpticalDisc[0].Layers.Sectors[0] = new SectorsType {Value = (long)layerBreak}; - sidecar.OpticalDisc[0].Sessions = 1; - sidecar.OpticalDisc[0].Tracks = new[] {1}; - sidecar.OpticalDisc[0].Track = new TrackType[1]; - sidecar.OpticalDisc[0].Track[0] = new TrackType - { - BytesPerSector = (int)BLOCK_SIZE, - Checksums = sidecar.OpticalDisc[0].Checksums, - EndSector = (long)(blocks - 1), - Image = - new ImageType - { - format = "BINARY", - offset = 0, - offsetSpecified = true, - Value = sidecar.OpticalDisc[0].Image.Value - }, - Sequence = new TrackSequenceType {Session = 1, TrackNumber = 1}, - Size = (long)(totalSize * BLOCK_SIZE), - StartSector = 0 - }; - if(xmlFileSysInfo != null) sidecar.OpticalDisc[0].Track[0].FileSystemInformation = xmlFileSysInfo; - sidecar.OpticalDisc[0].Track[0].TrackType1 = TrackTypeTrackType.dvd; - sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); + sidecar.OpticalDisc[0].Sessions = 1; + sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType); Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp); - sidecar.OpticalDisc[0].DiscType = xmlDskTyp; + sidecar.OpticalDisc[0].DiscType = xmlDskTyp; sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp; + foreach(KeyValuePair tag in mediaTags) + if(outputPlugin.SupportedMediaTags.Contains(tag.Key)) + Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar); + if(!aborted) { DicConsole.WriteLine("Writing metadata sidecar"); diff --git a/DiscImageChef.Core/DiscImageChef.Core.csproj b/DiscImageChef.Core/DiscImageChef.Core.csproj index 269fd5da..0b11d565 100644 --- a/DiscImageChef.Core/DiscImageChef.Core.csproj +++ b/DiscImageChef.Core/DiscImageChef.Core.csproj @@ -88,7 +88,6 @@ - diff --git a/DiscImageChef/Commands/DumpMedia.cs b/DiscImageChef/Commands/DumpMedia.cs index cc33aa43..82d330aa 100644 --- a/DiscImageChef/Commands/DumpMedia.cs +++ b/DiscImageChef/Commands/DumpMedia.cs @@ -31,13 +31,17 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Xml.Serialization; using DiscImageChef.Console; +using DiscImageChef.Core; using DiscImageChef.Core.Devices.Dumping; using DiscImageChef.Core.Logging; using DiscImageChef.Devices; +using DiscImageChef.DiscImages; using DiscImageChef.Metadata; namespace DiscImageChef.Commands @@ -46,19 +50,35 @@ namespace DiscImageChef.Commands { internal static void DoDumpMedia(DumpMediaOptions options) { - DicConsole.DebugWriteLine("Dump-Media command", "--debug={0}", options.Debug); - DicConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", options.Verbose); - DicConsole.DebugWriteLine("Dump-Media command", "--device={0}", options.DevicePath); - DicConsole.DebugWriteLine("Dump-Media command", "--output-prefix={0}", options.OutputPrefix); - DicConsole.DebugWriteLine("Dump-Media command", "--raw={0}", options.Raw); - DicConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", options.StopOnError); - DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", options.Force); - DicConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", options.RetryPasses); - DicConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", options.Persistent); - DicConsole.DebugWriteLine("Dump-Media command", "--separate-subchannel={0}", options.SeparateSubchannel); - DicConsole.DebugWriteLine("Dump-Media command", "--resume={0}", options.Resume); - DicConsole.DebugWriteLine("Dump-Media command", "--lead-in={0}", options.LeadIn); - DicConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", options.EncodingName); + // TODO: Be able to cancel hashing + Sidecar.InitProgressEvent += Progress.InitProgress; + Sidecar.UpdateProgressEvent += Progress.UpdateProgress; + Sidecar.EndProgressEvent += Progress.EndProgress; + Sidecar.InitProgressEvent2 += Progress.InitProgress2; + Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2; + Sidecar.EndProgressEvent2 += Progress.EndProgress2; + Sidecar.UpdateStatusEvent += Progress.UpdateStatus; + + DicConsole.DebugWriteLine("Dump-Media command", "--debug={0}", options.Debug); + DicConsole.DebugWriteLine("Dump-Media command", "--verbose={0}", options.Verbose); + DicConsole.DebugWriteLine("Dump-Media command", "--device={0}", options.DevicePath); + DicConsole.DebugWriteLine("Dump-Media command", "--raw={0}", options.Raw); + DicConsole.DebugWriteLine("Dump-Media command", "--stop-on-error={0}", options.StopOnError); + DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", options.Force); + DicConsole.DebugWriteLine("Dump-Media command", "--retry-passes={0}", options.RetryPasses); + DicConsole.DebugWriteLine("Dump-Media command", "--persistent={0}", options.Persistent); + DicConsole.DebugWriteLine("Dump-Media command", "--resume={0}", options.Resume); + DicConsole.DebugWriteLine("Dump-Media command", "--lead-in={0}", options.LeadIn); + DicConsole.DebugWriteLine("Dump-Media command", "--encoding={0}", options.EncodingName); + DicConsole.DebugWriteLine("Dump-Media command", "--output={0}", options.OutputFile); + DicConsole.DebugWriteLine("Dump-Media command", "--format={0}", options.OutputFormat); + DicConsole.DebugWriteLine("Dump-Media command", "--force={0}", options.Force); + DicConsole.DebugWriteLine("Dump-Media command", "--options={0}", options.Options); + + Dictionary parsedOptions = Options.Parse(options.Options); + DicConsole.DebugWriteLine("Dump-Media command", "Parsed options:"); + foreach(KeyValuePair parsedOption in parsedOptions) + DicConsole.DebugWriteLine("Dump-Media command", "{0} = {1}", parsedOption.Key, parsedOption.Value); Encoding encoding = null; @@ -88,13 +108,16 @@ namespace DiscImageChef.Commands Core.Statistics.AddDevice(dev); - Resume resume = null; - XmlSerializer xs = new XmlSerializer(typeof(Resume)); - if(File.Exists(options.OutputPrefix + ".resume.xml") && options.Resume) + string outputPrefix = Path.Combine(Path.GetDirectoryName(options.OutputFile), + Path.GetFileNameWithoutExtension(options.OutputFile)); + + Resume resume = null; + XmlSerializer xs = new XmlSerializer(typeof(Resume)); + if(File.Exists(outputPrefix + ".resume.xml") && options.Resume) try { - StreamReader sr = new StreamReader(options.OutputPrefix + ".resume.xml"); - resume = (Resume)xs.Deserialize(sr); + StreamReader sr = new StreamReader(outputPrefix + ".resume.xml"); + resume = (Resume)xs.Deserialize(sr); sr.Close(); } catch @@ -109,29 +132,74 @@ namespace DiscImageChef.Commands return; } - DumpLog dumpLog = new DumpLog(options.OutputPrefix + ".log", dev); + PluginBase plugins = new PluginBase(); + List candidates = new List(); + + // Try extension + if(string.IsNullOrEmpty(options.OutputFormat)) + candidates.AddRange(plugins.WritableImages.Values.Where(t => + t.KnownExtensions + .Contains(Path.GetExtension(options + .OutputFile)))); + // Try Id + else if(Guid.TryParse(options.OutputFormat, out Guid outId)) + candidates.AddRange(plugins.WritableImages.Values.Where(t => t.Id.Equals(outId))); + // Try name + else + candidates.AddRange(plugins.WritableImages.Values.Where(t => string.Equals(t.Name, options.OutputFormat, + StringComparison + .InvariantCultureIgnoreCase))); + + if(candidates.Count == 0) + { + DicConsole.WriteLine("No plugin supports requested extension."); + return; + } + + if(candidates.Count > 1) + { + DicConsole.WriteLine("More than one plugin supports requested extension."); + return; + } + + IWritableImage outputFormat = candidates[0]; + + DumpLog dumpLog = new DumpLog(outputPrefix + ".log", dev); + + if(options.Verbose) + { + dumpLog.WriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); + DicConsole.VerboseWriteLine("Output image format: {0} ({1}).", outputFormat.Name, outputFormat.Id); + } + else + { + dumpLog.WriteLine("Output image format: {0}.", outputFormat.Name); + DicConsole.WriteLine("Output image format: {0}.", outputFormat.Name); + } switch(dev.Type) { case DeviceType.ATA: - Ata.Dump(dev, options.DevicePath, options.OutputPrefix, options.RetryPasses, options.Force, - options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding); + Ata.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, + options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding, outputPrefix, + options.OutputFile, parsedOptions); break; case DeviceType.MMC: case DeviceType.SecureDigital: - SecureDigital.Dump(dev, options.DevicePath, options.OutputPrefix, options.RetryPasses, - options.Force, options.Raw, options.Persistent, options.StopOnError, ref resume, - ref dumpLog, encoding); + SecureDigital.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, + options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, + encoding, outputPrefix, options.OutputFile, parsedOptions); break; case DeviceType.NVMe: - NvMe.Dump(dev, options.DevicePath, options.OutputPrefix, options.RetryPasses, options.Force, - options.Raw, options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding); + NvMe.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, + options.Persistent, options.StopOnError, ref resume, ref dumpLog, encoding, outputPrefix, + options.OutputFile, parsedOptions); break; case DeviceType.ATAPI: case DeviceType.SCSI: - Scsi.Dump(dev, options.DevicePath, options.OutputPrefix, options.RetryPasses, options.Force, - options.Raw, options.Persistent, options.StopOnError, options.SeparateSubchannel, - ref resume, ref dumpLog, options.LeadIn, encoding); + Scsi.Dump(dev, options.DevicePath, outputFormat, options.RetryPasses, options.Force, options.Raw, + options.Persistent, options.StopOnError, ref resume, ref dumpLog, options.LeadIn, + encoding, outputPrefix, options.OutputFile, parsedOptions); break; default: dumpLog.WriteLine("Unknown device type."); @@ -144,11 +212,10 @@ namespace DiscImageChef.Commands resume.LastWriteDate = DateTime.UtcNow; resume.BadBlocks.Sort(); - if(File.Exists(options.OutputPrefix + ".resume.xml")) File.Delete(options.OutputPrefix + ".resume.xml"); + if(File.Exists(outputPrefix + ".resume.xml")) File.Delete(outputPrefix + ".resume.xml"); - FileStream fs = new FileStream(options.OutputPrefix + ".resume.xml", FileMode.Create, - FileAccess.ReadWrite); - xs = new XmlSerializer(resume.GetType()); + FileStream fs = new FileStream(outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite); + xs = new XmlSerializer(resume.GetType()); xs.Serialize(fs, resume); fs.Close(); } diff --git a/DiscImageChef/Options.cs b/DiscImageChef/Options.cs index 8487b079..e78e57c6 100644 --- a/DiscImageChef/Options.cs +++ b/DiscImageChef/Options.cs @@ -269,9 +269,6 @@ namespace DiscImageChef [Option('i', "device", Required = true, HelpText = "Device path.")] public string DevicePath { get; set; } - [Option('w', "output-prefix", Required = true, HelpText = "Prefix for media dump.")] - public string OutputPrefix { get; set; } - [Option('r', "raw", Default = false, HelpText = "Dump sectors with tags included. For optical media, dump scrambled sectors")] public bool Raw { get; set; } @@ -288,10 +285,6 @@ namespace DiscImageChef [Option("persistent", Default = false, HelpText = "Try to recover partial or incorrect data.")] public bool Persistent { get; set; } - [Option("separate-subchannel", Default = false, - HelpText = "Save subchannel in a separate file. Only applicable to CD/DDCD/GD.")] - public bool SeparateSubchannel { get; set; } - [Option('m', "resume", Default = true, HelpText = "Create/use resume mapfile.")] public bool Resume { get; set; } @@ -300,6 +293,18 @@ namespace DiscImageChef [Option('e', "encoding", Default = null, HelpText = "Name of character encoding to use.")] public string EncodingName { get; set; } + + [Option('o', "output", Required = true, HelpText = "Output image.")] + public string OutputFile { get; set; } + + [Option('t', "format", Default = null, + HelpText = + "Format of the output image, as plugin name or plugin id. If not present, will try to detect it from output image extension.")] + public string OutputFormat { get; set; } + + [Option('O', "options", Default = null, + HelpText = "Comma separated name=value pairs of options to pass to output image plugin")] + public string Options { get; set; } } [Verb("device-report", HelpText = "Tests the device capabilities and creates an XML report of them.")]