2017-05-31 01:00:58 +01:00
|
|
|
|
// /***************************************************************************
|
|
|
|
|
|
// The Disc Image Chef
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
//
|
|
|
|
|
|
// Filename : CompactDisc.cs
|
2017-12-19 03:50:57 +00:00
|
|
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
2017-05-31 01:00:58 +01:00
|
|
|
|
//
|
2017-12-19 03:50:57 +00:00
|
|
|
|
// Component : Core algorithms.
|
2017-05-31 01:00:58 +01:00
|
|
|
|
//
|
|
|
|
|
|
// --[ Description ] ----------------------------------------------------------
|
|
|
|
|
|
//
|
2017-12-19 03:50:57 +00:00
|
|
|
|
// Dumps CDs and DDCDs.
|
2017-05-31 01:00:58 +01:00
|
|
|
|
//
|
|
|
|
|
|
// --[ License ] --------------------------------------------------------------
|
|
|
|
|
|
//
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
|
// it under the terms of the GNU General Public License as
|
|
|
|
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
//
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
//
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2018-12-29 17:34:38 +00:00
|
|
|
|
// Copyright © 2011-2019 Natalia Portillo
|
2017-05-31 01:00:58 +01:00
|
|
|
|
// ****************************************************************************/
|
2017-12-19 03:50:57 +00:00
|
|
|
|
|
2017-05-31 01:00:58 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Xml.Serialization;
|
2018-06-25 19:08:16 +01:00
|
|
|
|
using DiscImageChef.CommonTypes;
|
|
|
|
|
|
using DiscImageChef.CommonTypes.Enums;
|
|
|
|
|
|
using DiscImageChef.CommonTypes.Extents;
|
|
|
|
|
|
using DiscImageChef.CommonTypes.Interfaces;
|
|
|
|
|
|
using DiscImageChef.CommonTypes.Metadata;
|
|
|
|
|
|
using DiscImageChef.CommonTypes.Structs;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
using DiscImageChef.Console;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
using DiscImageChef.Core.Logging;
|
2019-02-12 00:56:02 +00:00
|
|
|
|
using DiscImageChef.Core.Media.Detection;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
using DiscImageChef.Decoders.CD;
|
|
|
|
|
|
using DiscImageChef.Decoders.SCSI;
|
|
|
|
|
|
using DiscImageChef.Decoders.SCSI.MMC;
|
2017-12-21 14:30:38 +00:00
|
|
|
|
using DiscImageChef.Devices;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
using Schemas;
|
2017-12-21 14:30:38 +00:00
|
|
|
|
using MediaType = DiscImageChef.CommonTypes.MediaType;
|
2018-06-25 19:08:16 +01:00
|
|
|
|
using PlatformID = DiscImageChef.CommonTypes.Interop.PlatformID;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
using Session = DiscImageChef.Decoders.CD.Session;
|
2018-06-25 19:08:16 +01:00
|
|
|
|
using TrackType = DiscImageChef.CommonTypes.Enums.TrackType;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
|
|
|
|
|
namespace DiscImageChef.Core.Devices.Dumping
|
|
|
|
|
|
{
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// <summary>
|
2017-12-23 17:41:23 +00:00
|
|
|
|
/// Implement dumping Compact Discs
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// </summary>
|
2018-02-04 22:43:37 +00:00
|
|
|
|
// TODO: Barcode and pregaps
|
2019-04-19 18:54:25 +01:00
|
|
|
|
partial class Dump
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// <summary>
|
2017-12-23 17:41:23 +00:00
|
|
|
|
/// Dumps a compact disc
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dev">Device</param>
|
|
|
|
|
|
/// <param name="devicePath">Path to the device</param>
|
|
|
|
|
|
/// <param name="outputPrefix">Prefix for output data files</param>
|
2018-01-19 01:21:01 +00:00
|
|
|
|
/// <param name="outputPlugin">Plugin for output file</param>
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// <param name="retryPasses">How many times to retry</param>
|
|
|
|
|
|
/// <param name="force">Force to continue dump whenever possible</param>
|
|
|
|
|
|
/// <param name="dumpRaw">Dump scrambled sectors</param>
|
|
|
|
|
|
/// <param name="persistent">Store whatever data the drive returned on error</param>
|
|
|
|
|
|
/// <param name="stopOnError">Stop dump on first error</param>
|
|
|
|
|
|
/// <param name="resume">Information for dump resuming</param>
|
|
|
|
|
|
/// <param name="dumpLog">Dump logger</param>
|
|
|
|
|
|
/// <param name="dskType">Disc type as detected in MMC layer</param>
|
2018-12-31 21:16:52 +00:00
|
|
|
|
/// <param name="dumpFirstTrackPregap">Try to read and dump as much first track pregap as possible</param>
|
2018-01-19 01:21:01 +00:00
|
|
|
|
/// <param name="outputPath">Path to output file</param>
|
|
|
|
|
|
/// <param name="formatOptions">Formats to pass to output file plugin</param>
|
2018-01-20 17:12:01 +00:00
|
|
|
|
/// <param name="encoding">Encoding to use when analyzing dump</param>
|
2017-12-23 01:46:08 +00:00
|
|
|
|
/// <exception cref="NotImplementedException">If trying to dump scrambled sectors</exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">If the resume file is invalid</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException">If the track type is unknown (never)</exception>
|
2019-04-19 18:54:25 +01:00
|
|
|
|
internal void CompactDisc(Device dev, string devicePath,
|
2019-01-20 20:11:10 +00:00
|
|
|
|
IWritableOpticalImage outputPlugin, ushort retryPasses,
|
2018-12-31 21:16:52 +00:00
|
|
|
|
bool force, bool dumpRaw,
|
|
|
|
|
|
bool persistent, bool stopOnError,
|
|
|
|
|
|
ref MediaType dskType,
|
|
|
|
|
|
ref Resume resume, ref DumpLog dumpLog,
|
|
|
|
|
|
bool dumpFirstTrackPregap, Encoding encoding,
|
|
|
|
|
|
string outputPrefix, string outputPath,
|
2018-04-02 23:08:26 +01:00
|
|
|
|
Dictionary<string, string> formatOptions,
|
|
|
|
|
|
CICMMetadataType preSidecar, uint skip,
|
2018-04-10 02:39:41 +01:00
|
|
|
|
bool nometadata, bool notrim)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
uint subSize;
|
|
|
|
|
|
DateTime start;
|
|
|
|
|
|
DateTime end;
|
|
|
|
|
|
bool readcd;
|
2018-06-20 22:22:21 +01:00
|
|
|
|
bool read6 = false, read10 = false, read12 = false, read16 = false;
|
2018-04-02 23:08:26 +01:00
|
|
|
|
bool sense = false;
|
|
|
|
|
|
const uint SECTOR_SIZE = 2352;
|
|
|
|
|
|
FullTOC.CDFullTOC? toc = null;
|
|
|
|
|
|
double totalDuration = 0;
|
|
|
|
|
|
double currentSpeed = 0;
|
|
|
|
|
|
double maxSpeed = double.MinValue;
|
|
|
|
|
|
double minSpeed = double.MaxValue;
|
|
|
|
|
|
uint blocksToRead = 64;
|
|
|
|
|
|
bool aborted = false;
|
|
|
|
|
|
System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
Dictionary<MediaTagType, byte[]> mediaTags = new Dictionary<MediaTagType, byte[]>();
|
|
|
|
|
|
|
|
|
|
|
|
if(dumpRaw)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Raw CD dumping not yet implemented");
|
|
|
|
|
|
DicConsole.ErrorWriteLine("Raw CD dumping not yet implemented");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
dskType = MediaType.CD;
|
2018-06-23 01:31:43 +01:00
|
|
|
|
int sessions = 1;
|
2018-06-19 22:48:51 +01:00
|
|
|
|
|
2017-05-31 01:00:58 +01:00
|
|
|
|
// We discarded all discs that falsify a TOC before requesting a real TOC
|
|
|
|
|
|
// No TOC, no CD (or an empty one)
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading full TOC");
|
2018-02-04 18:51:04 +00:00
|
|
|
|
bool tocSense = dev.ReadRawToc(out byte[] cmdBuf, out byte[] senseBuf, 0, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!tocSense)
|
|
|
|
|
|
{
|
2017-06-08 21:12:05 +01:00
|
|
|
|
toc = FullTOC.Decode(cmdBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(toc.HasValue)
|
|
|
|
|
|
{
|
2018-04-02 23:08:26 +01:00
|
|
|
|
byte[] tmpBuf = new byte[cmdBuf.Length - 2];
|
2017-05-31 01:00:58 +01:00
|
|
|
|
Array.Copy(cmdBuf, 2, tmpBuf, 0, cmdBuf.Length - 2);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
mediaTags.Add(MediaTagType.CD_FullTOC, tmpBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
|
|
|
|
|
// ATIP exists on blank CDs
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading ATIP");
|
2017-12-21 23:00:30 +00:00
|
|
|
|
sense = dev.ReadAtip(out cmdBuf, out senseBuf, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
2017-06-08 21:12:05 +01:00
|
|
|
|
ATIP.CDATIP? atip = ATIP.Decode(cmdBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(atip.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Only CD-R and CD-RW have ATIP
|
|
|
|
|
|
dskType = atip.Value.DiscType ? MediaType.CDRW : MediaType.CDR;
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
tmpBuf = new byte[cmdBuf.Length - 4];
|
2017-05-31 01:00:58 +01:00
|
|
|
|
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
mediaTags.Add(MediaTagType.CD_ATIP, tmpBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading Disc Information");
|
2017-12-19 20:33:03 +00:00
|
|
|
|
sense = dev.ReadDiscInformation(out cmdBuf, out senseBuf,
|
2017-12-23 17:41:23 +00:00
|
|
|
|
MmcDiscInformationDataTypes.DiscInformation, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
2017-12-23 17:41:23 +00:00
|
|
|
|
DiscInformation.StandardDiscInformation? discInfo = DiscInformation.Decode000b(cmdBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(discInfo.HasValue)
|
|
|
|
|
|
if(dskType == MediaType.CD)
|
|
|
|
|
|
switch(discInfo.Value.DiscType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0x10:
|
|
|
|
|
|
dskType = MediaType.CDI;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 0x20:
|
|
|
|
|
|
dskType = MediaType.CDROMXA;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int firstTrackLastSession = 0;
|
|
|
|
|
|
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading Session Information");
|
2017-12-21 23:00:30 +00:00
|
|
|
|
sense = dev.ReadSessionInfo(out cmdBuf, out senseBuf, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
2017-06-08 21:12:05 +01:00
|
|
|
|
Session.CDSessionInfo? session = Session.Decode(cmdBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(session.HasValue)
|
|
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
sessions = session.Value.LastCompleteSession;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
firstTrackLastSession = session.Value.TrackDescriptors[0].TrackNumber;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-23 12:20:17 +01:00
|
|
|
|
if(dskType == MediaType.CD || dskType == MediaType.CDROMXA)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
bool hasDataTrack = false;
|
|
|
|
|
|
bool hasAudioTrack = false;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
bool allFirstSessionTracksAreAudio = true;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
bool hasVideoTrack = false;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-21 17:34:47 +00:00
|
|
|
|
foreach(FullTOC.TrackDataDescriptor track in toc.Value.TrackDescriptors)
|
|
|
|
|
|
{
|
2017-12-22 02:04:18 +00:00
|
|
|
|
if(track.TNO == 1 && ((TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrack ||
|
2017-12-23 17:41:23 +00:00
|
|
|
|
(TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrackIncremental)
|
|
|
|
|
|
) allFirstSessionTracksAreAudio &= firstTrackLastSession != 1;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-22 02:04:18 +00:00
|
|
|
|
if((TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrack ||
|
|
|
|
|
|
(TocControl)(track.CONTROL & 0x0D) == TocControl.DataTrackIncremental)
|
2017-12-21 17:34:47 +00:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
hasDataTrack = true;
|
2018-06-23 12:20:17 +01:00
|
|
|
|
allFirstSessionTracksAreAudio &= track.POINT >= firstTrackLastSession;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
2017-12-21 17:34:47 +00:00
|
|
|
|
else hasAudioTrack = true;
|
|
|
|
|
|
|
|
|
|
|
|
hasVideoTrack |= track.ADR == 4;
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
|
|
|
|
|
if(hasDataTrack && hasAudioTrack && allFirstSessionTracksAreAudio && sessions == 2)
|
2018-04-02 23:08:26 +01:00
|
|
|
|
dskType = MediaType.CDPLUS;
|
|
|
|
|
|
if(!hasDataTrack && hasAudioTrack && sessions == 1) dskType = MediaType.CDDA;
|
|
|
|
|
|
if(hasDataTrack && !hasAudioTrack && sessions == 1) dskType = MediaType.CDROM;
|
|
|
|
|
|
if(hasVideoTrack && !hasDataTrack && sessions == 1) dskType = MediaType.CDV;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading PMA");
|
2017-12-21 23:00:30 +00:00
|
|
|
|
sense = dev.ReadPma(out cmdBuf, out senseBuf, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!sense)
|
2017-06-08 21:12:05 +01:00
|
|
|
|
if(PMA.Decode(cmdBuf).HasValue)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-04-02 23:08:26 +01:00
|
|
|
|
tmpBuf = new byte[cmdBuf.Length - 4];
|
2017-05-31 01:00:58 +01:00
|
|
|
|
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
mediaTags.Add(MediaTagType.CD_PMA, tmpBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Reading CD-Text from Lead-In");
|
2017-12-21 23:00:30 +00:00
|
|
|
|
sense = dev.ReadCdText(out cmdBuf, out senseBuf, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
if(!sense)
|
2017-06-08 21:12:05 +01:00
|
|
|
|
if(CDTextOnLeadIn.Decode(cmdBuf).HasValue)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-04-02 23:08:26 +01:00
|
|
|
|
tmpBuf = new byte[cmdBuf.Length - 4];
|
2017-05-31 01:00:58 +01:00
|
|
|
|
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
mediaTags.Add(MediaTagType.CD_TEXT, tmpBuf);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-12 00:56:02 +00:00
|
|
|
|
// TODO: Add other detectors here
|
|
|
|
|
|
dumpLog.WriteLine("Detecting disc type...");
|
|
|
|
|
|
DicConsole.WriteLine("Detecting disc type...");
|
|
|
|
|
|
byte[] videoNowColorFrame = new byte[9 * 2352];
|
|
|
|
|
|
for(int i = 0; i < 9; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, 2352, 1, MmcSectorTypes.AllTypes, false, false,
|
|
|
|
|
|
true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(sense || dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, 2352, 1, MmcSectorTypes.Cdda, false, false,
|
|
|
|
|
|
true, MmcHeaderCodes.None, true, true, MmcErrorField.None, MmcSubchannel.None,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(sense || !dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
videoNowColorFrame = null;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Array.Copy(cmdBuf, 0, videoNowColorFrame, i * 2352, 2352);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(MMC.IsVideoNowColor(videoNowColorFrame)) dskType = MediaType.VideoNowColor;
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
MmcSubchannel supportedSubchannel = MmcSubchannel.Raw;
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports full raw subchannel reading...");
|
|
|
|
|
|
DicConsole.WriteLine("Checking if drive supports full raw subchannel reading...");
|
|
|
|
|
|
readcd = !dev.ReadCd(out byte[] readBuffer, out senseBuf, 0, SECTOR_SIZE + 96, 1, MmcSectorTypes.AllTypes,
|
|
|
|
|
|
false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
|
|
|
|
|
|
supportedSubchannel, dev.Timeout, out _);
|
|
|
|
|
|
if(readcd)
|
2017-11-26 18:23:50 +00:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Full raw subchannel reading supported...");
|
|
|
|
|
|
DicConsole.WriteLine("Full raw subchannel reading supported...");
|
|
|
|
|
|
subSize = 96;
|
2017-11-26 18:23:50 +00:00
|
|
|
|
}
|
2018-01-20 17:12:01 +00:00
|
|
|
|
else
|
2017-12-23 17:41:23 +00:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
supportedSubchannel = MmcSubchannel.Q16;
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports PQ subchannel reading...");
|
2018-06-19 21:46:57 +01:00
|
|
|
|
DicConsole.WriteLine("Checking if drive supports PQ subchannel reading...");
|
2018-01-20 17:12:01 +00:00
|
|
|
|
readcd = !dev.ReadCd(out readBuffer, out senseBuf, 0, SECTOR_SIZE + 16, 1, MmcSectorTypes.AllTypes,
|
|
|
|
|
|
false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
|
|
|
|
|
|
supportedSubchannel, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("PQ subchannel reading supported...");
|
|
|
|
|
|
dumpLog.WriteLine("WARNING: If disc says CD+G, CD+EG, CD-MIDI, CD Graphics or CD Enhanced Graphics, dump will be incorrect!");
|
|
|
|
|
|
DicConsole.WriteLine("PQ subchannel reading supported...");
|
|
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("WARNING: If disc says CD+G, CD+EG, CD-MIDI, CD Graphics or CD Enhanced Graphics, dump will be incorrect!");
|
|
|
|
|
|
subSize = 16;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
supportedSubchannel = MmcSubchannel.None;
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports reading without subchannel...");
|
2018-06-19 21:46:57 +01:00
|
|
|
|
DicConsole.WriteLine("Checking if drive supports reading without subchannel...");
|
2018-01-20 17:12:01 +00:00
|
|
|
|
readcd = !dev.ReadCd(out readBuffer, out senseBuf, 0, SECTOR_SIZE, 1, MmcSectorTypes.AllTypes,
|
|
|
|
|
|
false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
|
|
|
|
|
|
supportedSubchannel, dev.Timeout, out _);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(!readcd)
|
|
|
|
|
|
{
|
2018-06-19 22:17:20 +01:00
|
|
|
|
dumpLog.WriteLine("Drive does not support READ CD, trying SCSI READ commands...");
|
|
|
|
|
|
DicConsole.ErrorWriteLine("Drive does not support READ CD, trying SCSI READ commands...");
|
|
|
|
|
|
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports READ(6)...");
|
|
|
|
|
|
DicConsole.WriteLine("Checking if drive supports READ(6)...");
|
|
|
|
|
|
read6 = !dev.Read6(out readBuffer, out senseBuf, 0, 2048, 1, dev.Timeout, out _);
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports READ(10)...");
|
|
|
|
|
|
DicConsole.WriteLine("Checking if drive supports READ(10)...");
|
|
|
|
|
|
read10 = !dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, 0, 2048, 0, 1,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
dumpLog.WriteLine("Checking if drive supports READ(12)...");
|
|
|
|
|
|
DicConsole.WriteLine("Checking if drive supports READ(12)...");
|
2018-06-20 22:22:21 +01:00
|
|
|
|
read12 = !dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, 2048, 0, 1,
|
|
|
|
|
|
false, dev.Timeout, out _);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
dumpLog.WriteLine("Checking if drive supports READ(16)...");
|
|
|
|
|
|
DicConsole.WriteLine("Checking if drive supports READ(16)...");
|
|
|
|
|
|
read16 = !dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, 0, 2048, 0, 1, false,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(!read6 && !read10 && !read12 && !read16)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Cannot read from disc, not continuing...");
|
|
|
|
|
|
DicConsole.ErrorWriteLine("Cannot read from disc, not continuing...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2018-06-19 22:48:51 +01:00
|
|
|
|
|
|
|
|
|
|
if(read6)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Drive supports READ(6)...");
|
|
|
|
|
|
DicConsole.WriteLine("Drive supports READ(6)...");
|
|
|
|
|
|
}
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
if(read10)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Drive supports READ(10)...");
|
|
|
|
|
|
DicConsole.WriteLine("Drive supports READ(10)...");
|
|
|
|
|
|
}
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
if(read12)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Drive supports READ(12)...");
|
|
|
|
|
|
DicConsole.WriteLine("Drive supports READ(12)...");
|
|
|
|
|
|
}
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
if(read16)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Drive supports READ(16)...");
|
|
|
|
|
|
DicConsole.WriteLine("Drive supports READ(16)...");
|
|
|
|
|
|
}
|
2018-01-20 17:12:01 +00:00
|
|
|
|
}
|
2017-11-26 18:23:50 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Drive can only read without subchannel...");
|
|
|
|
|
|
dumpLog.WriteLine("WARNING: If disc says CD+G, CD+EG, CD-MIDI, CD Graphics or CD Enhanced Graphics, dump will be incorrect!");
|
|
|
|
|
|
DicConsole.WriteLine("Drive can only read without subchannel...");
|
|
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("WARNING: If disc says CD+G, CD+EG, CD-MIDI, CD Graphics or CD Enhanced Graphics, dump will be incorrect!");
|
|
|
|
|
|
subSize = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-11-26 18:23:50 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Check if output format supports subchannels
|
|
|
|
|
|
if(!outputPlugin.SupportedSectorTags.Contains(SectorTagType.CdSectorSubchannel) &&
|
|
|
|
|
|
supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Output format does not support subchannels, {0}continuing...",
|
|
|
|
|
|
force ? "" : "not ");
|
|
|
|
|
|
dumpLog.WriteLine("Output format does not support subchannels, {0}continuing...", force ? "" : "not ");
|
|
|
|
|
|
|
|
|
|
|
|
if(!force) return;
|
|
|
|
|
|
|
|
|
|
|
|
supportedSubchannel = MmcSubchannel.None;
|
|
|
|
|
|
subSize = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TrackSubchannelType subType;
|
|
|
|
|
|
|
|
|
|
|
|
switch(supportedSubchannel)
|
|
|
|
|
|
{
|
|
|
|
|
|
case MmcSubchannel.None:
|
|
|
|
|
|
subType = TrackSubchannelType.None;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case MmcSubchannel.Raw:
|
|
|
|
|
|
subType = TrackSubchannelType.Raw;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case MmcSubchannel.Q16:
|
|
|
|
|
|
subType = TrackSubchannelType.Q16;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
DicConsole.WriteLine("Handling subchannel type {0} not supported, exiting...", supportedSubchannel);
|
|
|
|
|
|
dumpLog.WriteLine("Handling subchannel type {0} not supported, exiting...", supportedSubchannel);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint blockSize = SECTOR_SIZE + subSize;
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Building track map...");
|
|
|
|
|
|
dumpLog.WriteLine("Building track map...");
|
|
|
|
|
|
|
2018-02-04 22:06:14 +00:00
|
|
|
|
List<Track> trackList = new List<Track>();
|
|
|
|
|
|
long lastSector = 0;
|
|
|
|
|
|
Dictionary<byte, byte> trackFlags = new Dictionary<byte, byte>();
|
|
|
|
|
|
TrackType firstTrackType = TrackType.Audio;
|
2018-06-23 01:31:43 +01:00
|
|
|
|
Dictionary<int, long> leadOutStarts = new Dictionary<int, long>();
|
2018-02-04 22:06:14 +00:00
|
|
|
|
|
|
|
|
|
|
if(toc.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
FullTOC.TrackDataDescriptor[] sortedTracks =
|
|
|
|
|
|
toc.Value.TrackDescriptors.OrderBy(track => track.POINT).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(FullTOC.TrackDataDescriptor trk in sortedTracks.Where(trk => trk.ADR == 1 || trk.ADR == 4))
|
2018-04-02 23:08:26 +01:00
|
|
|
|
if(trk.POINT >= 0x01 && trk.POINT <= 0x63)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
trackList.Add(new Track
|
|
|
|
|
|
{
|
|
|
|
|
|
TrackSequence = trk.POINT,
|
|
|
|
|
|
TrackSession = trk.SessionNumber,
|
2018-04-02 23:08:26 +01:00
|
|
|
|
TrackType =
|
2018-02-04 22:06:14 +00:00
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
|
|
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
|
|
|
|
|
|
? TrackType.Data
|
|
|
|
|
|
: TrackType.Audio,
|
|
|
|
|
|
TrackStartSector =
|
2018-11-24 13:47:51 +00:00
|
|
|
|
(ulong)(trk.PHOUR * 3600 * 75 + trk.PMIN * 60 * 75 + trk.PSEC * 75 +
|
|
|
|
|
|
trk.PFRAME - 150),
|
2018-02-04 22:06:14 +00:00
|
|
|
|
TrackBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackRawBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackSubchannelType = subType
|
|
|
|
|
|
});
|
|
|
|
|
|
trackFlags.Add(trk.POINT, trk.CONTROL);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(trk.POINT == 0xA2)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
int phour, pmin, psec, pframe;
|
|
|
|
|
|
if(trk.PFRAME == 0)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
pframe = 74;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-02-04 22:06:14 +00:00
|
|
|
|
if(trk.PSEC == 0)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
psec = 59;
|
|
|
|
|
|
|
|
|
|
|
|
if(trk.PMIN == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
pmin = 59;
|
|
|
|
|
|
phour = trk.PHOUR - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
pmin = trk.PMIN - 1;
|
|
|
|
|
|
phour = trk.PHOUR;
|
|
|
|
|
|
}
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
psec = trk.PSEC - 1;
|
|
|
|
|
|
pmin = trk.PMIN;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
phour = trk.PHOUR;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
pframe = trk.PFRAME - 1;
|
|
|
|
|
|
psec = trk.PSEC;
|
|
|
|
|
|
pmin = trk.PMIN;
|
|
|
|
|
|
phour = trk.PHOUR;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
2018-02-04 22:06:14 +00:00
|
|
|
|
|
|
|
|
|
|
lastSector = phour * 3600 * 75 + pmin * 60 * 75 + psec * 75 + pframe - 150;
|
2018-06-23 01:31:43 +01:00
|
|
|
|
leadOutStarts.Add(trk.SessionNumber, lastSector + 1);
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
2018-02-04 22:06:14 +00:00
|
|
|
|
else if(trk.POINT == 0xA0 && trk.ADR == 1)
|
2017-12-21 07:08:26 +00:00
|
|
|
|
{
|
2018-02-04 20:24:48 +00:00
|
|
|
|
switch(trk.PSEC)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0x10:
|
|
|
|
|
|
dskType = MediaType.CDI;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 0x20:
|
2018-06-23 12:20:17 +01:00
|
|
|
|
if(dskType == MediaType.CD) dskType = MediaType.CDROMXA;
|
2018-02-04 20:24:48 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-02-04 22:06:14 +00:00
|
|
|
|
firstTrackType =
|
|
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
|
|
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
|
|
|
|
|
|
? TrackType.Data
|
|
|
|
|
|
: TrackType.Audio;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2018-02-04 18:51:04 +00:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
DicConsole.WriteLine("Cannot read RAW TOC, requesting processed one...");
|
|
|
|
|
|
dumpLog.WriteLine("Cannot read RAW TOC, requesting processed one...");
|
2018-02-04 18:51:04 +00:00
|
|
|
|
tocSense = dev.ReadToc(out cmdBuf, out senseBuf, false, 0, dev.Timeout, out _);
|
2018-02-04 22:06:14 +00:00
|
|
|
|
|
|
|
|
|
|
TOC.CDTOC? oldToc = TOC.Decode(cmdBuf);
|
|
|
|
|
|
if((tocSense || !oldToc.HasValue) && !force)
|
2018-02-04 18:51:04 +00:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000...");
|
|
|
|
|
|
dumpLog.WriteLine("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000...");
|
2018-02-04 18:51:04 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-02-04 22:06:14 +00:00
|
|
|
|
foreach(TOC.CDTOCTrackDataDescriptor trk in oldToc
|
|
|
|
|
|
.Value.TrackDescriptors.OrderBy(t => t.TrackNumber)
|
2018-04-02 23:08:26 +01:00
|
|
|
|
.Where(trk => trk.ADR == 1 || trk.ADR == 4))
|
|
|
|
|
|
if(trk.TrackNumber >= 0x01 && trk.TrackNumber <= 0x63)
|
2018-02-04 22:06:14 +00:00
|
|
|
|
{
|
|
|
|
|
|
trackList.Add(new Track
|
|
|
|
|
|
{
|
|
|
|
|
|
TrackSequence = trk.TrackNumber,
|
|
|
|
|
|
TrackSession = 1,
|
2018-04-02 23:08:26 +01:00
|
|
|
|
TrackType =
|
2018-02-04 22:06:14 +00:00
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
|
|
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
|
|
|
|
|
|
? TrackType.Data
|
|
|
|
|
|
: TrackType.Audio,
|
|
|
|
|
|
TrackStartSector = trk.TrackStartAddress,
|
|
|
|
|
|
TrackBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackRawBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackSubchannelType = subType
|
|
|
|
|
|
});
|
|
|
|
|
|
trackFlags.Add(trk.TrackNumber, trk.CONTROL);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(trk.TrackNumber == 0xAA)
|
2018-02-04 18:51:04 +00:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
firstTrackType =
|
2018-02-04 18:51:04 +00:00
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
|
|
|
|
|
|
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
|
|
|
|
|
|
? TrackType.Data
|
2018-02-04 22:06:14 +00:00
|
|
|
|
: TrackType.Audio;
|
|
|
|
|
|
lastSector = trk.TrackStartAddress - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(trackList.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("No tracks found, adding a single track from 0 to Lead-Out");
|
|
|
|
|
|
dumpLog.WriteLine("No tracks found, adding a single track from 0 to Lead-Out");
|
|
|
|
|
|
|
|
|
|
|
|
trackList.Add(new Track
|
|
|
|
|
|
{
|
|
|
|
|
|
TrackSequence = 1,
|
|
|
|
|
|
TrackSession = 1,
|
|
|
|
|
|
TrackType = firstTrackType,
|
|
|
|
|
|
TrackStartSector = 0,
|
|
|
|
|
|
TrackBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackRawBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackSubchannelType = subType
|
|
|
|
|
|
});
|
|
|
|
|
|
trackFlags.Add(1, (byte)(firstTrackType == TrackType.Audio ? 0 : 4));
|
2018-02-04 18:51:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-02-04 18:51:38 +00:00
|
|
|
|
if(lastSector == 0)
|
|
|
|
|
|
{
|
2018-06-19 22:48:51 +01:00
|
|
|
|
sense = dev.ReadCapacity16(out readBuffer, out senseBuf, dev.Timeout, out _);
|
|
|
|
|
|
if(!sense)
|
2018-02-04 22:06:14 +00:00
|
|
|
|
{
|
2018-06-19 22:48:51 +01:00
|
|
|
|
byte[] temp = new byte[8];
|
|
|
|
|
|
|
|
|
|
|
|
Array.Copy(cmdBuf, 0, temp, 0, 8);
|
|
|
|
|
|
Array.Reverse(temp);
|
|
|
|
|
|
lastSector = (long)BitConverter.ToUInt64(temp, 0);
|
2018-06-20 22:22:21 +01:00
|
|
|
|
blockSize = (uint)((cmdBuf[5] << 24) + (cmdBuf[5] << 16) + (cmdBuf[6] << 8) + cmdBuf[7]);
|
2018-06-19 22:48:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCapacity(out cmdBuf, out senseBuf, dev.Timeout, out _);
|
|
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
lastSector = (cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + cmdBuf[3];
|
|
|
|
|
|
blockSize = (uint)((cmdBuf[5] << 24) + (cmdBuf[5] << 16) + (cmdBuf[6] << 8) + cmdBuf[7]);
|
2018-06-19 22:48:51 +01:00
|
|
|
|
}
|
2018-02-04 22:06:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
if(lastSector <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!force)
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors...");
|
|
|
|
|
|
dumpLog.WriteLine("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before...");
|
|
|
|
|
|
dumpLog.WriteLine("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before...");
|
|
|
|
|
|
lastSector = 360000;
|
|
|
|
|
|
}
|
2018-02-04 18:51:38 +00:00
|
|
|
|
}
|
2018-02-04 22:06:14 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
Track[] tracks = trackList.ToArray();
|
|
|
|
|
|
for(int t = 1; t < tracks.Length; t++) tracks[t - 1].TrackEndSector = tracks[t].TrackStartSector - 1;
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
tracks[tracks.Length - 1].TrackEndSector = (ulong)lastSector;
|
|
|
|
|
|
ulong blocks = (ulong)(lastSector + 1);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
|
|
|
|
|
if(blocks == 0)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
DicConsole.ErrorWriteLine("Cannot dump blank media.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-06-23 01:31:43 +01:00
|
|
|
|
ExtentsULong leadOutExtents = new ExtentsULong();
|
|
|
|
|
|
|
|
|
|
|
|
if(leadOutStarts.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Solving lead-outs...");
|
|
|
|
|
|
foreach(KeyValuePair<int, long> leadOuts in leadOutStarts)
|
|
|
|
|
|
for(int i = 0; i < tracks.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(tracks[i].TrackSession != leadOuts.Key) continue;
|
|
|
|
|
|
|
2018-06-23 15:31:28 +01:00
|
|
|
|
if(tracks[i].TrackEndSector >= (ulong)leadOuts.Value)
|
|
|
|
|
|
tracks[i].TrackEndSector = (ulong)leadOuts.Value - 1;
|
2018-06-23 01:31:43 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ExtentsULong dataExtents = new ExtentsULong();
|
|
|
|
|
|
foreach(Track trk in tracks) dataExtents.Add(trk.TrackStartSector, trk.TrackEndSector);
|
|
|
|
|
|
|
|
|
|
|
|
Tuple<ulong, ulong>[] dataExtentsArray = dataExtents.ToArray();
|
|
|
|
|
|
for(int i = 0; i < dataExtentsArray.Length - 1; i++)
|
|
|
|
|
|
leadOutExtents.Add(dataExtentsArray[i].Item2 + 1, dataExtentsArray[i + 1].Item1 - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Check if output format supports all disc tags we have retrieved so far
|
|
|
|
|
|
foreach(MediaTagType tag in mediaTags.Keys)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(outputPlugin.SupportedMediaTags.Contains(tag)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Output format does not support {0}, {1}continuing...", tag, force ? "" : "not ");
|
|
|
|
|
|
dumpLog.WriteLine("Output format does not support {0}, {1}continuing...", tag, force ? "" : "not ");
|
|
|
|
|
|
|
|
|
|
|
|
if(!force) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-27 21:48:57 +01:00
|
|
|
|
// Check for hidden data before start of track 1
|
|
|
|
|
|
if(tracks.First(t => t.TrackSequence == 1).TrackStartSector > 0 && readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("First track starts after sector 0, checking for a hidden track...");
|
|
|
|
|
|
DicConsole.WriteLine("First track starts after sector 0, checking for a hidden track...");
|
|
|
|
|
|
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, 0, blockSize, 1, MmcSectorTypes.AllTypes, false, false,
|
|
|
|
|
|
true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(dev.Error || sense)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Could not read sector 0, continuing...", dev.LastError);
|
|
|
|
|
|
DicConsole.WriteLine("Could not read sector 0, continuing...", dev.LastError);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] syncMark = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
|
|
|
|
|
byte[] cdiMark = {0x01, 0x43, 0x44, 0x2D};
|
|
|
|
|
|
byte[] testMark = new byte[12];
|
|
|
|
|
|
Array.Copy(readBuffer, 0, testMark, 0, 12);
|
|
|
|
|
|
|
|
|
|
|
|
bool hiddenData = syncMark.SequenceEqual(testMark) &&
|
|
|
|
|
|
(readBuffer[0xF] == 0 || readBuffer[0xF] == 1 || readBuffer[0xF] == 2);
|
|
|
|
|
|
|
|
|
|
|
|
if(hiddenData && readBuffer[0xF] == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, 16, blockSize, 1, MmcSectorTypes.AllTypes,
|
|
|
|
|
|
false, false, true, MmcHeaderCodes.AllHeaders, true, true,
|
|
|
|
|
|
MmcErrorField.None, supportedSubchannel, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(!dev.Error && !sense)
|
|
|
|
|
|
{
|
|
|
|
|
|
testMark = new byte[4];
|
|
|
|
|
|
Array.Copy(readBuffer, 24, testMark, 0, 4);
|
|
|
|
|
|
if(cdiMark.SequenceEqual(testMark)) dskType = MediaType.CDIREADY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
List<Track> trkList = new List<Track>
|
|
|
|
|
|
{
|
|
|
|
|
|
new Track
|
|
|
|
|
|
{
|
|
|
|
|
|
TrackSequence = 0,
|
|
|
|
|
|
TrackSession = 1,
|
|
|
|
|
|
TrackType = hiddenData ? TrackType.Data : TrackType.Audio,
|
|
|
|
|
|
TrackStartSector = 0,
|
|
|
|
|
|
TrackBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackRawBytesPerSector = (int)SECTOR_SIZE,
|
|
|
|
|
|
TrackSubchannelType = subType,
|
|
|
|
|
|
TrackEndSector = tracks.First(t => t.TrackSequence == 1).TrackStartSector - 1
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
trkList.AddRange(tracks);
|
|
|
|
|
|
tracks = trkList.ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Check mode for tracks
|
|
|
|
|
|
for(int t = 0; t < tracks.Length; t++)
|
|
|
|
|
|
{
|
2018-06-19 22:17:20 +01:00
|
|
|
|
if(!readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
tracks[t].TrackType = TrackType.CdMode1;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-19 22:48:51 +01:00
|
|
|
|
if(tracks[t].TrackType == TrackType.Audio) continue;
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Checking mode for track {0}...", tracks[t].TrackSequence);
|
|
|
|
|
|
DicConsole.WriteLine("Checking mode for track {0}...", tracks[t].TrackSequence);
|
|
|
|
|
|
|
|
|
|
|
|
readcd = !dev.ReadCd(out readBuffer, out senseBuf, (uint)tracks[t].TrackStartSector, blockSize, 1,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true,
|
|
|
|
|
|
MmcErrorField.None, supportedSubchannel, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(!readcd)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Unable to guess mode for track {0}, continuing...", tracks[t].TrackSequence);
|
|
|
|
|
|
DicConsole.WriteLine("Unable to guess mode for track {0}, continuing...", tracks[t].TrackSequence);
|
|
|
|
|
|
continue;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
|
|
|
|
|
switch(readBuffer[15])
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
case 1:
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} is MODE1", tracks[t].TrackSequence);
|
|
|
|
|
|
dumpLog.WriteLine("Track {0} is MODE1", tracks[t].TrackSequence);
|
|
|
|
|
|
tracks[t].TrackType = TrackType.CdMode1;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
2018-07-27 21:48:57 +01:00
|
|
|
|
if(dskType == MediaType.CDI || dskType == MediaType.CDIREADY)
|
2018-06-24 10:57:58 +01:00
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} is MODE2", tracks[t].TrackSequence);
|
|
|
|
|
|
dumpLog.WriteLine("Track {0} is MODE2", tracks[t].TrackSequence);
|
|
|
|
|
|
tracks[t].TrackType = TrackType.CdMode2Formless;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if((readBuffer[0x012] & 0x20) == 0x20) // mode 2 form 2
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} is MODE2 FORM 2", tracks[t].TrackSequence);
|
|
|
|
|
|
dumpLog.WriteLine("Track {0} is MODE2 FORM 2", tracks[t].TrackSequence);
|
|
|
|
|
|
tracks[t].TrackType = TrackType.CdMode2Form2;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} is MODE2 FORM 1", tracks[t].TrackSequence);
|
|
|
|
|
|
dumpLog.WriteLine("Track {0} is MODE2 FORM 1", tracks[t].TrackSequence);
|
|
|
|
|
|
tracks[t].TrackType = TrackType.CdMode2Form1;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} is unknown mode {1}", tracks[t].TrackSequence, readBuffer[15]);
|
|
|
|
|
|
dumpLog.WriteLine("Track {0} is unknown mode {1}", tracks[t].TrackSequence, readBuffer[15]);
|
|
|
|
|
|
break;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-11-24 14:23:35 +00:00
|
|
|
|
bool supportsLongSectors = true;
|
|
|
|
|
|
|
2018-11-24 13:47:51 +00:00
|
|
|
|
if(outputPlugin.Id == new Guid("12345678-AAAA-BBBB-CCCC-123456789000"))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(tracks.Length > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Output format does not support more than 1 track, not continuing...");
|
|
|
|
|
|
dumpLog.WriteLine("Output format does not support more than 1 track, not continuing...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(tracks.Any(t => t.TrackType == TrackType.Audio))
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Output format does not support audio tracks, not continuing...");
|
|
|
|
|
|
dumpLog.WriteLine("Output format does not support audio tracks, not continuing...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(tracks.Any(t => t.TrackType != TrackType.CdMode1))
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Output format only supports MODE 1 tracks, not continuing...");
|
|
|
|
|
|
dumpLog.WriteLine("Output format only supports MODE 1 tracks, not continuing...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2018-11-24 14:23:35 +00:00
|
|
|
|
|
|
|
|
|
|
supportsLongSectors = false;
|
2018-11-24 13:47:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
// Check if something prevents from dumping the first track pregap
|
|
|
|
|
|
if(dumpFirstTrackPregap && readcd)
|
2017-11-29 15:14:21 +00:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(dev.PlatformId == PlatformID.FreeBSD)
|
|
|
|
|
|
{
|
2018-12-31 21:16:52 +00:00
|
|
|
|
dumpLog.WriteLine("FreeBSD panics when reading CD first track pregap, see upstream bug #224253. {0}continuing",
|
2018-01-20 17:12:01 +00:00
|
|
|
|
force ? "" : "Not ");
|
|
|
|
|
|
DicConsole
|
2018-12-31 21:16:52 +00:00
|
|
|
|
.ErrorWriteLine("FreeBSD panics when reading CD first track pregap, see upstream bug #224253. {0}continuing",
|
2018-01-20 17:12:01 +00:00
|
|
|
|
force ? "" : "Not ");
|
2017-11-29 15:14:21 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(!force) return;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
dumpFirstTrackPregap = false;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
if(!outputPlugin.SupportedMediaTags.Contains(MediaTagType.CD_FirstTrackPregap))
|
2018-01-20 17:12:01 +00:00
|
|
|
|
{
|
2018-12-31 21:16:52 +00:00
|
|
|
|
DicConsole.WriteLine("Output format does not support CD first track pregap, {0}continuing...",
|
2018-01-20 17:12:01 +00:00
|
|
|
|
force ? "" : "not ");
|
2018-12-31 21:16:52 +00:00
|
|
|
|
dumpLog.WriteLine("Output format does not support CD first track pregap, {0}continuing...",
|
2018-01-20 17:12:01 +00:00
|
|
|
|
force ? "" : "not ");
|
|
|
|
|
|
|
|
|
|
|
|
if(!force) return;
|
|
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
dumpFirstTrackPregap = false;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-06-20 05:48:09 +01:00
|
|
|
|
|
2018-02-01 01:20:41 +00:00
|
|
|
|
DumpHardwareType currentTry = null;
|
|
|
|
|
|
ExtentsULong extents = null;
|
|
|
|
|
|
ResumeSupport.Process(true, true, 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...");
|
|
|
|
|
|
|
2019-01-27 17:47:40 +00:00
|
|
|
|
DateTime timeSpeedStart = DateTime.UtcNow;
|
|
|
|
|
|
ulong sectorSpeedStart = 0;
|
|
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
// Try to read the first track pregap
|
|
|
|
|
|
if(dumpFirstTrackPregap && readcd)
|
2017-12-18 21:31:41 +00:00
|
|
|
|
{
|
2018-12-31 21:16:52 +00:00
|
|
|
|
DicConsole.WriteLine("Trying to read first track pregap...");
|
|
|
|
|
|
bool gotFirstTrackPregap = false;
|
|
|
|
|
|
int firstTrackPregapSectorsGood = 0;
|
|
|
|
|
|
MemoryStream firstTrackPregapMs = new MemoryStream();
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-18 21:31:41 +00:00
|
|
|
|
readBuffer = null;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
dumpLog.WriteLine("Reading first track pregap");
|
|
|
|
|
|
for(int firstTrackPregapBlock = -150; firstTrackPregapBlock < 0 && resume.NextBlock == 0;
|
|
|
|
|
|
firstTrackPregapBlock++)
|
2017-11-20 05:07:16 +00:00
|
|
|
|
{
|
2017-12-18 21:31:41 +00:00
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
DicConsole.Write("\rTrying to read first track pregap sector {0} ({1:F3} MiB/sec.)",
|
|
|
|
|
|
firstTrackPregapBlock, currentSpeed);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)firstTrackPregapBlock, blockSize, 1,
|
2017-12-18 21:31:41 +00:00
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
|
2018-01-20 17:12:01 +00:00
|
|
|
|
true, MmcErrorField.None, supportedSubchannel, dev.Timeout,
|
2017-12-18 21:31:41 +00:00
|
|
|
|
out double cmdDuration);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-18 21:31:41 +00:00
|
|
|
|
if(!sense && !dev.Error)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-12-31 21:16:52 +00:00
|
|
|
|
firstTrackPregapMs.Write(readBuffer, 0, (int)blockSize);
|
|
|
|
|
|
gotFirstTrackPregap = true;
|
|
|
|
|
|
firstTrackPregapSectorsGood++;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
2017-12-18 21:31:41 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Write empty data
|
2018-12-31 21:16:52 +00:00
|
|
|
|
if(gotFirstTrackPregap) firstTrackPregapMs.Write(new byte[blockSize], 0, (int)blockSize);
|
2017-12-18 21:31:41 +00:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2019-01-27 17:47:40 +00:00
|
|
|
|
sectorSpeedStart++;
|
|
|
|
|
|
|
|
|
|
|
|
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
|
|
|
|
|
|
if(elapsed < 1) continue;
|
|
|
|
|
|
|
|
|
|
|
|
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
|
|
|
|
|
|
sectorSpeedStart = 0;
|
|
|
|
|
|
timeSpeedStart = DateTime.UtcNow;
|
2017-12-18 21:31:41 +00:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
if(firstTrackPregapSectorsGood > 0)
|
|
|
|
|
|
mediaTags.Add(MediaTagType.CD_FirstTrackPregap, firstTrackPregapMs.ToArray());
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-18 21:31:41 +00:00
|
|
|
|
DicConsole.WriteLine();
|
2018-12-31 21:16:52 +00:00
|
|
|
|
DicConsole.WriteLine("Got {0} first track pregap sectors.", firstTrackPregapSectorsGood);
|
|
|
|
|
|
dumpLog.WriteLine("Got {0} first track pregap sectors.", firstTrackPregapSectorsGood);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
2018-12-31 21:16:52 +00:00
|
|
|
|
firstTrackPregapMs.Close();
|
2017-12-18 21:31:41 +00:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Try how many blocks are readable at once
|
2017-05-31 01:00:58 +01:00
|
|
|
|
while(true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
2017-12-19 20:33:03 +00:00
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, 0, blockSize, blocksToRead,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
|
2018-01-20 17:12:01 +00:00
|
|
|
|
true, MmcErrorField.None, supportedSubchannel, dev.Timeout, out _);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
if(dev.Error || sense) blocksToRead /= 2;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read16)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, 0, blockSize, 0,
|
|
|
|
|
|
blocksToRead, false, dev.Timeout, out _);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
if(dev.Error || sense) blocksToRead /= 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(read12)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0,
|
|
|
|
|
|
blocksToRead, false, dev.Timeout, out _);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
if(dev.Error || sense) blocksToRead /= 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(read10)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, 0, blockSize, 0,
|
|
|
|
|
|
(ushort)blocksToRead, dev.Timeout, out _);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
if(dev.Error || sense) blocksToRead /= 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(read6)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read6(out readBuffer, out senseBuf, 0, blockSize, (byte)blocksToRead, dev.Timeout,
|
|
|
|
|
|
out _);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
if(dev.Error || sense) blocksToRead /= 2;
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-19 20:33:03 +00:00
|
|
|
|
if(!dev.Error || blocksToRead == 1) break;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-29 15:15:39 +00:00
|
|
|
|
if(dev.Error || sense)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-07-27 21:48:57 +01:00
|
|
|
|
dumpLog.WriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead);
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
|
|
|
|
|
|
dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead);
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType);
|
|
|
|
|
|
dumpLog.WriteLine("Media identified as {0}.", dskType);
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
|
|
|
|
|
|
DicConsole.WriteLine("Device can read {0} blocks at a time.", blocksToRead);
|
|
|
|
|
|
DicConsole.WriteLine("Device reports {0} bytes per logical block.", blockSize);
|
|
|
|
|
|
DicConsole.WriteLine("SCSI device type: {0}.", dev.ScsiType);
|
|
|
|
|
|
DicConsole.WriteLine("Media identified as {0}.", dskType);
|
|
|
|
|
|
|
2017-12-21 23:00:30 +00:00
|
|
|
|
MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, blocksToRead);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", 0x0008);
|
2018-11-24 14:23:35 +00:00
|
|
|
|
bool ret = outputPlugin.Create(outputPath, dskType, formatOptions, blocks,
|
|
|
|
|
|
supportsLongSectors ? blockSize : 2048);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2017-11-20 05:07:16 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Send tracklist to output plugin. This may fail if subchannel is set but unsupported.
|
|
|
|
|
|
ret = outputPlugin.SetTracks(tracks.ToList());
|
|
|
|
|
|
if(!ret && supportedSubchannel == MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Error sending tracks to output image, not continuing.");
|
|
|
|
|
|
dumpLog.WriteLine(outputPlugin.ErrorMessage);
|
|
|
|
|
|
DicConsole.ErrorWriteLine("Error sending tracks to output image, not continuing.");
|
|
|
|
|
|
DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2017-11-20 05:07:16 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// If a subchannel is supported, check if output plugin allows us to write it.
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-06-22 08:08:38 +01:00
|
|
|
|
dev.ReadCd(out readBuffer, out senseBuf, 0, blockSize, 1, MmcSectorTypes.AllTypes, false, false, true,
|
|
|
|
|
|
MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, supportedSubchannel, dev.Timeout,
|
|
|
|
|
|
out _);
|
2017-11-20 05:07:16 +00:00
|
|
|
|
|
2018-01-25 23:31:27 +00:00
|
|
|
|
byte[] tmpBuf = new byte[subSize];
|
2018-01-20 17:12:01 +00:00
|
|
|
|
Array.Copy(readBuffer, SECTOR_SIZE, tmpBuf, 0, subSize);
|
|
|
|
|
|
|
|
|
|
|
|
ret = outputPlugin.WriteSectorTag(tmpBuf, 0, SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
|
|
|
|
|
|
if(!ret)
|
2017-06-07 23:25:06 +01:00
|
|
|
|
{
|
2018-07-27 21:48:57 +01:00
|
|
|
|
DicConsole.ErrorWriteLine("Error writing subchannel to output image, {0}continuing...",
|
|
|
|
|
|
force ? "" : "not ");
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Error writing subchannel to output image, {0}continuing...",
|
|
|
|
|
|
force ? "" : "not ");
|
2018-07-27 21:48:57 +01:00
|
|
|
|
DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage);
|
|
|
|
|
|
dumpLog.WriteLine(outputPlugin.ErrorMessage);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
|
|
|
|
|
if(!force) return;
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
supportedSubchannel = MmcSubchannel.None;
|
|
|
|
|
|
subSize = 0;
|
|
|
|
|
|
blockSize = SECTOR_SIZE + subSize;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
for(int t = 0; t < tracks.Length; t++) tracks[t].TrackSubchannelType = TrackSubchannelType.None;
|
2018-04-02 23:08:26 +01:00
|
|
|
|
ret = outputPlugin.SetTracks(tracks.ToList());
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(!ret)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Error sending tracks to output image, not continuing.");
|
|
|
|
|
|
dumpLog.WriteLine(outputPlugin.ErrorMessage);
|
|
|
|
|
|
DicConsole.ErrorWriteLine("Error sending tracks to output image, not continuing.");
|
|
|
|
|
|
DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2017-06-07 23:25:06 +01:00
|
|
|
|
}
|
2018-01-20 17:12:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set track flags
|
2018-02-04 22:06:14 +00:00
|
|
|
|
foreach(KeyValuePair<byte, byte> kvp in trackFlags)
|
2018-01-20 17:12:01 +00:00
|
|
|
|
{
|
2018-02-04 22:06:14 +00:00
|
|
|
|
Track track = tracks.FirstOrDefault(t => t.TrackSequence == kvp.Key);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
|
|
|
|
|
if(track.TrackSequence == 0) continue;
|
|
|
|
|
|
|
|
|
|
|
|
dumpLog.WriteLine("Setting flags for track {0}...", track.TrackSequence);
|
|
|
|
|
|
DicConsole.WriteLine("Setting flags for track {0}...", track.TrackSequence);
|
2018-02-04 22:06:14 +00:00
|
|
|
|
outputPlugin.WriteSectorTag(new[] {kvp.Value}, track.TrackStartSector, SectorTagType.CdTrackFlags);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
}
|
2017-06-07 23:25:06 +01:00
|
|
|
|
|
2018-02-04 22:43:37 +00:00
|
|
|
|
// Set MCN
|
|
|
|
|
|
sense = dev.ReadMcn(out string mcn, out _, out _, dev.Timeout, out _);
|
|
|
|
|
|
if(!sense && mcn != null && mcn != "0000000000000")
|
|
|
|
|
|
if(outputPlugin.WriteMediaTag(Encoding.ASCII.GetBytes(mcn), MediaTagType.CD_MCN))
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole.WriteLine("Setting disc Media Catalogue Number to {0}", mcn);
|
|
|
|
|
|
dumpLog.WriteLine("Setting disc Media Catalogue Number to {0}", mcn);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set ISRCs
|
|
|
|
|
|
foreach(Track trk in tracks)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadIsrc((byte)trk.TrackSequence, out string isrc, out _, out _, dev.Timeout, out _);
|
|
|
|
|
|
if(sense || isrc == null || isrc == "000000000000") continue;
|
|
|
|
|
|
|
2018-06-20 22:22:21 +01:00
|
|
|
|
if(!outputPlugin.WriteSectorTag(Encoding.ASCII.GetBytes(isrc), trk.TrackStartSector,
|
|
|
|
|
|
SectorTagType.CdTrackIsrc)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine("Setting ISRC for track {0} to {1}", trk.TrackSequence, isrc);
|
|
|
|
|
|
dumpLog.WriteLine("Setting ISRC for track {0} to {1}", trk.TrackSequence, isrc);
|
2018-02-04 22:43:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(resume.NextBlock > 0) dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-02-02 15:32:53 +00:00
|
|
|
|
double imageWriteDuration = 0;
|
2018-02-02 18:44:28 +00:00
|
|
|
|
|
|
|
|
|
|
if(skip < blocksToRead) skip = blocksToRead;
|
2018-04-10 02:39:41 +01:00
|
|
|
|
bool newTrim = false;
|
2018-02-02 18:44:28 +00:00
|
|
|
|
|
2018-06-23 01:31:43 +01:00
|
|
|
|
#if DEBUG
|
|
|
|
|
|
foreach(Track trk in tracks)
|
|
|
|
|
|
DicConsole.WriteLine("Track {0} starts at LBA {1} and ends at LBA {2}", trk.TrackSequence,
|
|
|
|
|
|
trk.TrackStartSector, trk.TrackEndSector);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2018-07-27 21:48:57 +01:00
|
|
|
|
if(dskType == MediaType.CDIREADY)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them.");
|
|
|
|
|
|
DicConsole.WriteLine("There will be thousand of errors between track 0 and track 1, that is normal and you can ignore them.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Start reading
|
2019-01-27 17:47:40 +00:00
|
|
|
|
start = DateTime.UtcNow;
|
|
|
|
|
|
currentSpeed = 0;
|
|
|
|
|
|
sectorSpeedStart = 0;
|
|
|
|
|
|
timeSpeedStart = DateTime.UtcNow;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
for(int t = 0; t < tracks.Length; t++)
|
|
|
|
|
|
{
|
2018-07-27 21:48:57 +01:00
|
|
|
|
dumpLog.WriteLine("Reading track {0}", tracks[t].TrackSequence);
|
2018-06-23 01:31:43 +01:00
|
|
|
|
if(resume.NextBlock < tracks[t].TrackStartSector) resume.NextBlock = tracks[t].TrackStartSector;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
for(ulong i = resume.NextBlock; i <= tracks[t].TrackEndSector; i += blocksToRead)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
2017-06-20 05:48:09 +01:00
|
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
2017-06-07 22:37:05 +01:00
|
|
|
|
break;
|
2017-06-20 05:48:09 +01:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-06-07 22:37:05 +01:00
|
|
|
|
double cmdDuration = 0;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
if(tracks[t].TrackEndSector + 1 - i < blocksToRead)
|
2018-01-20 17:12:01 +00:00
|
|
|
|
blocksToRead = (uint)(tracks[t].TrackEndSector + 1 - i);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
|
2017-12-19 20:33:03 +00:00
|
|
|
|
if(currentSpeed > maxSpeed && currentSpeed != 0) maxSpeed = currentSpeed;
|
|
|
|
|
|
if(currentSpeed < minSpeed && currentSpeed != 0) minSpeed = currentSpeed;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
#pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-12-19 20:33:03 +00:00
|
|
|
|
DicConsole.Write("\rReading sector {0} of {1} at track {3} ({2:F3} MiB/sec.)", i, blocks,
|
2018-07-27 21:48:57 +01:00
|
|
|
|
currentSpeed, tracks[t].TrackSequence);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-06-07 22:37:05 +01:00
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
2017-12-19 20:33:03 +00:00
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)i, blockSize, blocksToRead,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
|
2018-01-20 17:12:01 +00:00
|
|
|
|
true, MmcErrorField.None, supportedSubchannel, dev.Timeout, out cmdDuration);
|
2017-06-07 22:37:05 +01:00
|
|
|
|
totalDuration += cmdDuration;
|
|
|
|
|
|
}
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read16)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, i, blockSize, 0,
|
|
|
|
|
|
blocksToRead, false, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read12)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, blocksToRead, false, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read10)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, (ushort)blocksToRead, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read6)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read6(out readBuffer, out senseBuf, (uint)i, blockSize, (byte)blocksToRead,
|
|
|
|
|
|
dev.Timeout, out cmdDuration);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-06-07 22:37:05 +01:00
|
|
|
|
if(!sense && !dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
mhddLog.Write(i, cmdDuration);
|
|
|
|
|
|
ibgLog.Write(i, currentSpeed * 1024);
|
2017-06-20 05:48:09 +01:00
|
|
|
|
extents.Add(i, blocksToRead, true);
|
2018-02-02 15:32:53 +00:00
|
|
|
|
DateTime writeStart = DateTime.Now;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
2018-01-25 23:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[SECTOR_SIZE * blocksToRead];
|
|
|
|
|
|
byte[] sub = new byte[subSize * blocksToRead];
|
|
|
|
|
|
|
|
|
|
|
|
for(int b = 0; b < blocksToRead; b++)
|
2017-06-07 23:25:06 +01:00
|
|
|
|
{
|
2018-01-25 23:31:27 +00:00
|
|
|
|
Array.Copy(readBuffer, (int)(0 + b * blockSize), data, SECTOR_SIZE * b,
|
|
|
|
|
|
SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(SECTOR_SIZE + b * blockSize), sub, subSize * b,
|
|
|
|
|
|
subSize);
|
2017-06-07 23:25:06 +01:00
|
|
|
|
}
|
2018-01-25 23:31:27 +00:00
|
|
|
|
|
2018-02-01 07:53:25 +00:00
|
|
|
|
outputPlugin.WriteSectorsLong(data, i, blocksToRead);
|
2018-01-25 23:31:27 +00:00
|
|
|
|
outputPlugin.WriteSectorsTag(sub, i, blocksToRead, SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
2018-11-24 14:23:35 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(supportsLongSectors) outputPlugin.WriteSectorsLong(readBuffer, i, blocksToRead);
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(readBuffer.Length % 2352 == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[2048 * blocksToRead];
|
|
|
|
|
|
|
|
|
|
|
|
for(int b = 0; b < blocksToRead; b++)
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(16 + b * blockSize), data, 2048 * b, 2048);
|
|
|
|
|
|
|
|
|
|
|
|
outputPlugin.WriteSectors(data, i, blocksToRead);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectorsLong(readBuffer, i, blocksToRead);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-02-02 15:32:53 +00:00
|
|
|
|
|
|
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: Reset device after X errors
|
2017-12-19 20:33:03 +00:00
|
|
|
|
if(stopOnError) return; // TODO: Return more cleanly
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-02-03 02:53:38 +00:00
|
|
|
|
if(i + skip > blocks) skip = (uint)(blocks - i);
|
2018-02-04 22:06:14 +00:00
|
|
|
|
|
2017-06-07 22:37:05 +01:00
|
|
|
|
// Write empty data
|
2018-02-02 15:32:53 +00:00
|
|
|
|
DateTime writeStart = DateTime.Now;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
2017-06-07 23:25:06 +01:00
|
|
|
|
{
|
2018-02-02 18:44:28 +00:00
|
|
|
|
outputPlugin.WriteSectorsLong(new byte[SECTOR_SIZE * skip], i, skip);
|
2018-04-02 23:08:26 +01:00
|
|
|
|
outputPlugin.WriteSectorsTag(new byte[subSize * skip], i, skip,
|
2018-01-20 17:12:01 +00:00
|
|
|
|
SectorTagType.CdSectorSubchannel);
|
2017-06-07 23:25:06 +01:00
|
|
|
|
}
|
2018-11-24 14:23:35 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(supportsLongSectors) outputPlugin.WriteSectorsLong(new byte[blockSize * skip], i, skip);
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(readBuffer.Length % 2352 == 0)
|
|
|
|
|
|
outputPlugin.WriteSectors(new byte[2048 * skip], i, skip);
|
|
|
|
|
|
else outputPlugin.WriteSectorsLong(new byte[blockSize * skip], i, skip);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-02-02 18:44:28 +00:00
|
|
|
|
|
2018-02-02 15:32:53 +00:00
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-02-02 18:44:28 +00:00
|
|
|
|
for(ulong b = i; b < i + skip; b++) resume.BadBlocks.Add(b);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2017-12-23 17:41:23 +00:00
|
|
|
|
DicConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf));
|
2017-12-21 23:00:30 +00:00
|
|
|
|
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2017-06-07 22:37:05 +01:00
|
|
|
|
ibgLog.Write(i, 0);
|
2018-02-02 18:44:28 +00:00
|
|
|
|
dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i);
|
2018-04-10 02:39:41 +01:00
|
|
|
|
i += skip - blocksToRead;
|
|
|
|
|
|
newTrim = true;
|
2017-06-07 22:37:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-27 17:47:40 +00:00
|
|
|
|
sectorSpeedStart += blocksToRead;
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
resume.NextBlock = i + blocksToRead;
|
2019-01-27 17:47:40 +00:00
|
|
|
|
|
|
|
|
|
|
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
|
|
|
|
|
|
if(elapsed < 1) continue;
|
|
|
|
|
|
|
|
|
|
|
|
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
|
|
|
|
|
|
sectorSpeedStart = 0;
|
|
|
|
|
|
timeSpeedStart = DateTime.UtcNow;
|
2017-11-26 18:23:50 +00:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-06-23 01:31:43 +01:00
|
|
|
|
// TODO: Enable when underlying images support lead-outs
|
|
|
|
|
|
/*
|
|
|
|
|
|
if(persistent)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Reading lead-outs");
|
|
|
|
|
|
|
|
|
|
|
|
foreach(Tuple<ulong, ulong> leadout in leadOutExtents.ToArray())
|
|
|
|
|
|
for(ulong i = leadout.Item1; i <= leadout.Item2; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double cmdDuration = 0;
|
|
|
|
|
|
|
|
|
|
|
|
#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} at lead-out ({1:F3} MiB/sec.)", i, blocks,
|
|
|
|
|
|
currentSpeed);
|
|
|
|
|
|
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)i, blockSize, 1,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
|
|
|
|
|
|
true, true, MmcErrorField.None, supportedSubchannel, dev.Timeout,
|
|
|
|
|
|
out cmdDuration);
|
|
|
|
|
|
totalDuration += cmdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(read16)
|
|
|
|
|
|
sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, i, blockSize, 0, 1,
|
|
|
|
|
|
false, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read12)
|
|
|
|
|
|
sense = dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, 1, false, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read10)
|
|
|
|
|
|
sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, 1, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read6)
|
|
|
|
|
|
sense = dev.Read6(out readBuffer, out senseBuf, (uint)i, blockSize, 1, dev.Timeout,
|
|
|
|
|
|
out cmdDuration);
|
|
|
|
|
|
|
|
|
|
|
|
if(!sense && !dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
mhddLog.Write(i, cmdDuration);
|
|
|
|
|
|
ibgLog.Write(i, currentSpeed * 1024);
|
|
|
|
|
|
extents.Add(i, blocksToRead, true);
|
|
|
|
|
|
leadOutExtents.Remove(i);
|
|
|
|
|
|
DateTime writeStart = DateTime.Now;
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[SECTOR_SIZE * blocksToRead];
|
|
|
|
|
|
byte[] sub = new byte[subSize * blocksToRead];
|
|
|
|
|
|
|
|
|
|
|
|
for(int b = 0; b < blocksToRead; b++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(0 + b * blockSize), data, SECTOR_SIZE * b,
|
|
|
|
|
|
SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(SECTOR_SIZE + b * blockSize), sub, subSize * b,
|
|
|
|
|
|
subSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
outputPlugin.WriteSectorsLong(data, i, blocksToRead);
|
|
|
|
|
|
outputPlugin.WriteSectorsTag(sub, i, blocksToRead, SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectors(readBuffer, i, blocksToRead);
|
|
|
|
|
|
|
|
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: Reset device after X errors
|
|
|
|
|
|
if(stopOnError) return; // TODO: Return more cleanly
|
|
|
|
|
|
|
|
|
|
|
|
// Write empty data
|
|
|
|
|
|
DateTime writeStart = DateTime.Now;
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
outputPlugin.WriteSectorsLong(new byte[SECTOR_SIZE * skip], i, 1);
|
|
|
|
|
|
outputPlugin.WriteSectorsTag(new byte[subSize * skip], i, 1,
|
|
|
|
|
|
SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectors(new byte[blockSize * skip], i, 1);
|
|
|
|
|
|
|
|
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
|
|
|
|
|
|
|
|
|
|
|
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
|
|
|
|
|
|
|
|
|
|
|
|
ibgLog.Write(i, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double newSpeed =
|
|
|
|
|
|
(double)blockSize * blocksToRead / 1048576 / (cmdDuration / 1000);
|
|
|
|
|
|
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
|
|
|
|
|
|
resume.NextBlock = i + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
2017-05-31 01:00:58 +01:00
|
|
|
|
DicConsole.WriteLine();
|
|
|
|
|
|
end = DateTime.UtcNow;
|
|
|
|
|
|
mhddLog.Close();
|
2017-12-19 20:33:03 +00:00
|
|
|
|
ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
|
2018-04-02 23:08:26 +01:00
|
|
|
|
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
|
|
|
|
|
|
devicePath);
|
|
|
|
|
|
dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
|
2017-12-20 17:26:28 +00:00
|
|
|
|
(double)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
|
2018-02-02 15:32:53 +00:00
|
|
|
|
dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
|
|
|
|
|
|
(double)blockSize * (double)(blocks + 1) / 1024 / imageWriteDuration);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-04-10 02:39:41 +01:00
|
|
|
|
#region Compact Disc Error trimming
|
|
|
|
|
|
if(resume.BadBlocks.Count > 0 && !aborted && !notrim && newTrim)
|
|
|
|
|
|
{
|
|
|
|
|
|
start = DateTime.UtcNow;
|
|
|
|
|
|
dumpLog.WriteLine("Trimming bad sectors");
|
|
|
|
|
|
|
|
|
|
|
|
ulong[] tmpArray = resume.BadBlocks.ToArray();
|
|
|
|
|
|
foreach(ulong badSector in tmpArray)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.Write("\rTrimming sector {0}", badSector);
|
|
|
|
|
|
|
2018-06-20 22:22:21 +01:00
|
|
|
|
double cmdDuration = 0;
|
2018-06-22 08:08:38 +01:00
|
|
|
|
|
2018-04-10 02:39:41 +01:00
|
|
|
|
if(readcd)
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)badSector, blockSize, 1,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
|
2018-06-22 08:08:38 +01:00
|
|
|
|
true, MmcErrorField.None, supportedSubchannel, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read16)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, badSector, blockSize, 0,
|
|
|
|
|
|
blocksToRead, false, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read12)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)badSector,
|
|
|
|
|
|
blockSize, 0, blocksToRead, false, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read10)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)badSector,
|
|
|
|
|
|
blockSize, 0, (ushort)blocksToRead, dev.Timeout, out cmdDuration);
|
2018-06-19 22:17:20 +01:00
|
|
|
|
else if(read6)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.Read6(out readBuffer, out senseBuf, (uint)badSector, blockSize, (byte)blocksToRead,
|
|
|
|
|
|
dev.Timeout, out cmdDuration);
|
|
|
|
|
|
|
|
|
|
|
|
totalDuration += cmdDuration;
|
2018-04-10 02:39:41 +01:00
|
|
|
|
|
|
|
|
|
|
if(sense || dev.Error) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if(!sense && !dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
resume.BadBlocks.Remove(badSector);
|
|
|
|
|
|
extents.Add(badSector);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[SECTOR_SIZE];
|
|
|
|
|
|
byte[] sub = new byte[subSize];
|
|
|
|
|
|
Array.Copy(readBuffer, 0, data, 0, SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, SECTOR_SIZE, sub, 0, subSize);
|
|
|
|
|
|
outputPlugin.WriteSectorLong(data, badSector);
|
|
|
|
|
|
outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
2018-11-24 14:23:35 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(supportsLongSectors) outputPlugin.WriteSectorLong(readBuffer, badSector);
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(readBuffer.Length % 2352 == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[2048];
|
|
|
|
|
|
|
|
|
|
|
|
for(int b = 0; b < blocksToRead; b++) Array.Copy(readBuffer, 16, data, 0, 2048);
|
|
|
|
|
|
|
|
|
|
|
|
outputPlugin.WriteSector(data, badSector);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectorLong(readBuffer, badSector);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-04-10 02:39:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine();
|
|
|
|
|
|
end = DateTime.UtcNow;
|
|
|
|
|
|
dumpLog.WriteLine("Trimmming finished in {0} seconds.", (end - start).TotalSeconds);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion Compact Disc Error trimming
|
|
|
|
|
|
|
2017-05-31 01:00:58 +01:00
|
|
|
|
#region Compact Disc Error handling
|
2018-04-09 20:21:42 +01:00
|
|
|
|
if(resume.BadBlocks.Count > 0 && !aborted && retryPasses > 0)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-04-10 02:47:08 +01:00
|
|
|
|
int pass = 1;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
bool forward = true;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
bool runningPersistent = false;
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
Modes.ModePage? currentModePage = null;
|
|
|
|
|
|
byte[] md6;
|
|
|
|
|
|
byte[] md10;
|
|
|
|
|
|
|
|
|
|
|
|
if(persistent)
|
|
|
|
|
|
{
|
2018-04-10 03:37:52 +01:00
|
|
|
|
Modes.ModePage_01_MMC pgMmc;
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
2018-04-10 03:37:52 +01:00
|
|
|
|
sense = dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
|
|
|
|
|
|
dev.Timeout, out _);
|
|
|
|
|
|
if(sense)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
sense = dev.ModeSense10(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
|
|
|
|
|
|
dev.Timeout, out _);
|
2018-04-10 03:37:52 +01:00
|
|
|
|
|
|
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
|
|
|
|
|
Modes.DecodedMode? dcMode10 =
|
|
|
|
|
|
Modes.DecodeMode10(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
2018-04-10 03:37:52 +01:00
|
|
|
|
if(dcMode10.HasValue)
|
|
|
|
|
|
foreach(Modes.ModePage modePage in dcMode10.Value.Pages)
|
2018-06-20 22:22:21 +01:00
|
|
|
|
if(modePage.Page == 0x01 && modePage.Subpage == 0x00)
|
|
|
|
|
|
currentModePage = modePage;
|
2018-04-10 03:37:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Modes.DecodedMode? dcMode6 =
|
|
|
|
|
|
Modes.DecodeMode6(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);
|
|
|
|
|
|
|
|
|
|
|
|
if(dcMode6.HasValue)
|
|
|
|
|
|
foreach(Modes.ModePage modePage in dcMode6.Value.Pages)
|
|
|
|
|
|
if(modePage.Page == 0x01 && modePage.Subpage == 0x00)
|
|
|
|
|
|
currentModePage = modePage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(currentModePage == null)
|
|
|
|
|
|
{
|
2018-06-20 22:22:21 +01:00
|
|
|
|
pgMmc = new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 32, Parameter = 0x00};
|
2018-04-10 03:37:52 +01:00
|
|
|
|
currentModePage = new Modes.ModePage
|
|
|
|
|
|
{
|
2018-11-24 13:47:51 +00:00
|
|
|
|
Page = 0x01, Subpage = 0x00, PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
|
2018-04-10 03:37:52 +01:00
|
|
|
|
};
|
|
|
|
|
|
}
|
2018-06-20 22:22:21 +01:00
|
|
|
|
|
|
|
|
|
|
pgMmc = new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x20};
|
2018-04-02 23:08:26 +01:00
|
|
|
|
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);
|
|
|
|
|
|
|
2018-04-10 03:37:52 +01:00
|
|
|
|
dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
|
2018-04-02 23:08:26 +01:00
|
|
|
|
sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
|
|
|
|
|
|
if(sense) sense = dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(sense)
|
|
|
|
|
|
{
|
|
|
|
|
|
DicConsole
|
|
|
|
|
|
.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
|
|
|
|
|
|
DicConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
|
|
|
|
|
|
dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
|
|
|
|
|
|
}
|
|
|
|
|
|
else runningPersistent = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-19 20:33:03 +00:00
|
|
|
|
cdRepeatRetry:
|
2018-04-03 22:45:55 +01:00
|
|
|
|
ulong[] tmpArray = resume.BadBlocks.ToArray();
|
|
|
|
|
|
List<ulong> sectorsNotEvenPartial = new List<ulong>();
|
2017-05-31 01:00:58 +01:00
|
|
|
|
foreach(ulong badSector in tmpArray)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
2017-06-20 05:48:09 +01:00
|
|
|
|
{
|
2017-12-21 14:30:38 +00:00
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
2017-11-20 05:07:16 +00:00
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
2017-05-31 01:00:58 +01:00
|
|
|
|
break;
|
2017-06-20 05:48:09 +01:00
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-04-10 02:47:08 +01:00
|
|
|
|
DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass,
|
2017-12-19 20:33:03 +00:00
|
|
|
|
forward ? "forward" : "reverse",
|
|
|
|
|
|
runningPersistent ? "recovering partial data, " : "");
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
2018-01-25 23:31:27 +00:00
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)badSector, blockSize, 1,
|
2017-12-19 20:33:03 +00:00
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
|
2018-01-20 17:12:01 +00:00
|
|
|
|
true, MmcErrorField.None, supportedSubchannel, dev.Timeout,
|
2017-12-23 17:41:23 +00:00
|
|
|
|
out double cmdDuration);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
totalDuration += cmdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-03 22:45:55 +01:00
|
|
|
|
if(sense || dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!runningPersistent) continue;
|
|
|
|
|
|
|
|
|
|
|
|
FixedSense? decSense = Sense.DecodeFixed(senseBuf);
|
|
|
|
|
|
|
|
|
|
|
|
// MEDIUM ERROR, retry with ignore error below
|
|
|
|
|
|
if(decSense.HasValue && decSense.Value.ASC == 0x11)
|
|
|
|
|
|
if(!sectorsNotEvenPartial.Contains(badSector))
|
|
|
|
|
|
sectorsNotEvenPartial.Add(badSector);
|
|
|
|
|
|
}
|
2017-12-21 06:06:19 +00:00
|
|
|
|
|
|
|
|
|
|
if(!sense && !dev.Error)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2017-12-21 06:06:19 +00:00
|
|
|
|
resume.BadBlocks.Remove(badSector);
|
|
|
|
|
|
extents.Add(badSector);
|
|
|
|
|
|
dumpLog.WriteLine("Correctly retried sector {0} in pass {1}.", badSector, pass);
|
2018-04-03 22:45:55 +01:00
|
|
|
|
sectorsNotEvenPartial.Remove(badSector);
|
2017-12-21 06:06:19 +00:00
|
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
2017-12-21 06:06:19 +00:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
byte[] data = new byte[SECTOR_SIZE];
|
|
|
|
|
|
byte[] sub = new byte[subSize];
|
|
|
|
|
|
Array.Copy(readBuffer, 0, data, 0, SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, SECTOR_SIZE, sub, 0, subSize);
|
2018-02-03 02:23:26 +00:00
|
|
|
|
outputPlugin.WriteSectorLong(data, badSector);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
2018-02-03 02:23:26 +00:00
|
|
|
|
else outputPlugin.WriteSectorLong(readBuffer, badSector);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-20 05:48:09 +01:00
|
|
|
|
if(pass < retryPasses && !aborted && resume.BadBlocks.Count > 0)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
|
|
|
|
|
pass++;
|
|
|
|
|
|
forward = !forward;
|
2017-06-20 05:48:09 +01:00
|
|
|
|
resume.BadBlocks.Sort();
|
|
|
|
|
|
resume.BadBlocks.Reverse();
|
2017-05-31 01:00:58 +01:00
|
|
|
|
goto cdRepeatRetry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-03 22:45:55 +01:00
|
|
|
|
// Try to ignore read errors, on some drives this allows to recover partial even if damaged data
|
|
|
|
|
|
if(persistent && sectorsNotEvenPartial.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Modes.ModePage_01_MMC pgMmc =
|
|
|
|
|
|
new Modes.ModePage_01_MMC {PS = false, ReadRetryCount = 255, Parameter = 0x01};
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2018-04-10 03:37:52 +01:00
|
|
|
|
dumpLog.WriteLine("Sending MODE SELECT to drive (ignore error correction).");
|
2018-04-03 22:45:55 +01:00
|
|
|
|
sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
|
|
|
|
|
|
if(sense) sense = dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
|
|
|
|
|
|
|
|
|
|
|
|
if(!sense)
|
|
|
|
|
|
{
|
|
|
|
|
|
runningPersistent = true;
|
|
|
|
|
|
DicConsole.WriteLine();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(ulong badSector in sectorsNotEvenPartial)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.Write("\rTrying to get partial data for sector {0}", badSector);
|
|
|
|
|
|
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)badSector, blockSize, 1,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true,
|
|
|
|
|
|
MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
|
|
|
|
|
|
supportedSubchannel, dev.Timeout, out double cmdDuration);
|
|
|
|
|
|
totalDuration += cmdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-20 22:22:21 +01:00
|
|
|
|
if(sense || dev.Error) continue;
|
2018-04-03 22:45:55 +01:00
|
|
|
|
|
2018-06-20 22:22:21 +01:00
|
|
|
|
dumpLog.WriteLine("Got partial data for sector {0} in pass {1}.", badSector, pass);
|
|
|
|
|
|
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[SECTOR_SIZE];
|
|
|
|
|
|
byte[] sub = new byte[subSize];
|
|
|
|
|
|
Array.Copy(readBuffer, 0, data, 0, SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, SECTOR_SIZE, sub, 0, subSize);
|
|
|
|
|
|
outputPlugin.WriteSectorLong(data, badSector);
|
|
|
|
|
|
outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel);
|
2018-04-03 22:45:55 +01:00
|
|
|
|
}
|
2018-06-20 22:22:21 +01:00
|
|
|
|
else outputPlugin.WriteSectorLong(readBuffer, badSector);
|
2018-04-03 22:45:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
if(runningPersistent && currentModePage.HasValue)
|
2017-05-31 01:00:58 +01:00
|
|
|
|
{
|
2018-06-23 01:31:43 +01:00
|
|
|
|
// TODO: Enable when underlying images support lead-outs
|
|
|
|
|
|
/*
|
|
|
|
|
|
dumpLog.WriteLine("Retrying lead-outs");
|
|
|
|
|
|
|
|
|
|
|
|
foreach(Tuple<ulong, ulong> leadout in leadOutExtents.ToArray())
|
|
|
|
|
|
for(ulong i = leadout.Item1; i <= leadout.Item2; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double cmdDuration = 0;
|
|
|
|
|
|
|
|
|
|
|
|
#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} at lead-out ({1:F3} MiB/sec.)", i, blocks,
|
|
|
|
|
|
currentSpeed);
|
|
|
|
|
|
|
|
|
|
|
|
if(readcd)
|
|
|
|
|
|
{
|
|
|
|
|
|
sense = dev.ReadCd(out readBuffer, out senseBuf, (uint)i, blockSize, 1,
|
|
|
|
|
|
MmcSectorTypes.AllTypes, false, false, true,
|
|
|
|
|
|
MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
|
|
|
|
|
|
supportedSubchannel, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
totalDuration += cmdDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(read16)
|
|
|
|
|
|
sense = dev.Read16(out readBuffer, out senseBuf, 0, false, true, false, i, blockSize, 0,
|
|
|
|
|
|
1, false, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read12)
|
|
|
|
|
|
sense = dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, 1, false, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read10)
|
|
|
|
|
|
sense = dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i,
|
|
|
|
|
|
blockSize, 0, 1, dev.Timeout, out cmdDuration);
|
|
|
|
|
|
else if(read6)
|
|
|
|
|
|
sense = dev.Read6(out readBuffer, out senseBuf, (uint)i, blockSize, 1, dev.Timeout,
|
|
|
|
|
|
out cmdDuration);
|
|
|
|
|
|
|
|
|
|
|
|
if(!sense && !dev.Error)
|
|
|
|
|
|
{
|
|
|
|
|
|
mhddLog.Write(i, cmdDuration);
|
|
|
|
|
|
ibgLog.Write(i, currentSpeed * 1024);
|
|
|
|
|
|
extents.Add(i, blocksToRead, true);
|
|
|
|
|
|
leadOutExtents.Remove(i);
|
|
|
|
|
|
DateTime writeStart = DateTime.Now;
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] data = new byte[SECTOR_SIZE * blocksToRead];
|
|
|
|
|
|
byte[] sub = new byte[subSize * blocksToRead];
|
|
|
|
|
|
|
|
|
|
|
|
for(int b = 0; b < blocksToRead; b++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(0 + b * blockSize), data, SECTOR_SIZE * b,
|
|
|
|
|
|
SECTOR_SIZE);
|
|
|
|
|
|
Array.Copy(readBuffer, (int)(SECTOR_SIZE + b * blockSize), sub, subSize * b,
|
|
|
|
|
|
subSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
outputPlugin.WriteSectorsLong(data, i, blocksToRead);
|
|
|
|
|
|
outputPlugin.WriteSectorsTag(sub, i, blocksToRead,
|
|
|
|
|
|
SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectors(readBuffer, i, blocksToRead);
|
|
|
|
|
|
|
|
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: Reset device after X errors
|
|
|
|
|
|
if(stopOnError) return; // TODO: Return more cleanly
|
|
|
|
|
|
|
|
|
|
|
|
// Write empty data
|
|
|
|
|
|
DateTime writeStart = DateTime.Now;
|
|
|
|
|
|
if(supportedSubchannel != MmcSubchannel.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
outputPlugin.WriteSectorsLong(new byte[SECTOR_SIZE * skip], i, 1);
|
|
|
|
|
|
outputPlugin.WriteSectorsTag(new byte[subSize * skip], i, 1,
|
|
|
|
|
|
SectorTagType.CdSectorSubchannel);
|
|
|
|
|
|
}
|
|
|
|
|
|
else outputPlugin.WriteSectors(new byte[blockSize * skip], i, 1);
|
|
|
|
|
|
|
|
|
|
|
|
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
|
|
|
|
|
|
|
|
|
|
|
|
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
|
|
|
|
|
|
|
|
|
|
|
|
ibgLog.Write(i, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double newSpeed =
|
|
|
|
|
|
(double)blockSize * blocksToRead / 1048576 / (cmdDuration / 1000);
|
|
|
|
|
|
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
|
|
|
|
|
|
}
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2017-12-21 14:30:38 +00:00
|
|
|
|
Modes.DecodedMode md = new Modes.DecodedMode
|
2017-06-08 21:12:05 +01:00
|
|
|
|
{
|
2018-11-24 13:47:51 +00:00
|
|
|
|
Header = new Modes.ModeHeader(), Pages = new[] {currentModePage.Value}
|
2017-06-08 21:12:05 +01:00
|
|
|
|
};
|
2018-01-20 17:12:01 +00:00
|
|
|
|
md6 = Modes.EncodeMode6(md, dev.ScsiType);
|
2017-12-21 14:30:38 +00:00
|
|
|
|
md10 = Modes.EncodeMode10(md, dev.ScsiType);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-04-10 03:37:52 +01:00
|
|
|
|
dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
|
2017-12-21 23:00:30 +00:00
|
|
|
|
sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
|
|
|
|
|
|
if(sense) dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine();
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion Compact Disc Error handling
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Write media tags to image
|
|
|
|
|
|
if(!aborted)
|
|
|
|
|
|
foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags)
|
2017-06-07 22:37:05 +01:00
|
|
|
|
{
|
2018-01-20 17:12:01 +00:00
|
|
|
|
ret = outputPlugin.WriteMediaTag(tag.Value, tag.Key);
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(ret || force) continue;
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
// Cannot write tag to image
|
|
|
|
|
|
dumpLog.WriteLine($"Cannot write tag {tag.Key}.");
|
|
|
|
|
|
throw new ArgumentException(outputPlugin.ErrorMessage);
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-02-01 01:20:41 +00:00
|
|
|
|
resume.BadBlocks.Sort();
|
2018-02-01 15:16:29 +00:00
|
|
|
|
foreach(ulong bad in resume.BadBlocks) dumpLog.WriteLine("Sector {0} could not be read.", bad);
|
2018-02-01 01:20:41 +00:00
|
|
|
|
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
|
|
|
|
|
|
|
2018-01-28 20:29:46 +00:00
|
|
|
|
outputPlugin.SetDumpHardware(resume.Tries);
|
2018-01-28 21:18:52 +00:00
|
|
|
|
if(preSidecar != null) outputPlugin.SetCicmMetadata(preSidecar);
|
2018-01-20 17:12:01 +00:00
|
|
|
|
dumpLog.WriteLine("Closing output file.");
|
|
|
|
|
|
DicConsole.WriteLine("Closing output file.");
|
2018-02-02 15:32:53 +00:00
|
|
|
|
DateTime closeStart = DateTime.Now;
|
2018-01-20 17:12:01 +00:00
|
|
|
|
outputPlugin.Close();
|
2018-02-02 15:32:53 +00:00
|
|
|
|
DateTime closeEnd = DateTime.Now;
|
|
|
|
|
|
dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
if(aborted)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Aborted!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-02-02 22:03:19 +00:00
|
|
|
|
double totalChkDuration = 0;
|
|
|
|
|
|
if(!nometadata)
|
|
|
|
|
|
{
|
|
|
|
|
|
dumpLog.WriteLine("Creating sidecar.");
|
|
|
|
|
|
FiltersList filters = new FiltersList();
|
|
|
|
|
|
IFilter filter = filters.GetFilter(outputPath);
|
|
|
|
|
|
IMediaImage inputPlugin = ImageFormat.Detect(filter);
|
|
|
|
|
|
if(!inputPlugin.Open(filter)) throw new ArgumentException("Could not open created image.");
|
|
|
|
|
|
|
|
|
|
|
|
DateTime chkStart = DateTime.UtcNow;
|
|
|
|
|
|
CICMMetadataType sidecar = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding);
|
2018-04-02 23:08:26 +01:00
|
|
|
|
end = DateTime.UtcNow;
|
2018-02-02 22:03:19 +00:00
|
|
|
|
|
2018-04-02 23:08:26 +01:00
|
|
|
|
totalChkDuration = (end - chkStart).TotalMilliseconds;
|
2018-02-02 22:03:19 +00:00
|
|
|
|
dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);
|
|
|
|
|
|
dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
|
|
|
|
|
|
(double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));
|
|
|
|
|
|
|
|
|
|
|
|
if(preSidecar != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
preSidecar.OpticalDisc = sidecar.OpticalDisc;
|
|
|
|
|
|
sidecar = preSidecar;
|
|
|
|
|
|
}
|
2017-06-07 22:37:05 +01:00
|
|
|
|
|
2018-02-02 22:03:19 +00:00
|
|
|
|
List<(ulong start, string type)> filesystems = new List<(ulong start, string type)>();
|
|
|
|
|
|
if(sidecar.OpticalDisc[0].Track != null)
|
|
|
|
|
|
filesystems.AddRange(from xmlTrack in sidecar.OpticalDisc[0].Track
|
|
|
|
|
|
where xmlTrack.FileSystemInformation != null
|
|
|
|
|
|
from partition in xmlTrack.FileSystemInformation
|
|
|
|
|
|
where partition.FileSystems != null
|
|
|
|
|
|
from fileSystem in partition.FileSystems
|
|
|
|
|
|
select ((ulong)partition.StartSector, fileSystem.Type));
|
|
|
|
|
|
|
|
|
|
|
|
if(filesystems.Count > 0)
|
|
|
|
|
|
foreach(var filesystem in filesystems.Select(o => new {o.start, o.type}).Distinct())
|
|
|
|
|
|
dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
|
|
|
|
|
|
|
|
|
|
|
|
sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
|
2018-06-25 19:08:16 +01:00
|
|
|
|
CommonTypes.Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp,
|
|
|
|
|
|
out string xmlDskSubTyp);
|
2018-02-02 22:03:19 +00:00
|
|
|
|
sidecar.OpticalDisc[0].DiscType = xmlDskTyp;
|
|
|
|
|
|
sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp;
|
|
|
|
|
|
sidecar.OpticalDisc[0].DumpHardwareArray = resume.Tries.ToArray();
|
2017-12-19 20:33:03 +00:00
|
|
|
|
|
2018-02-02 22:03:19 +00:00
|
|
|
|
foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags)
|
|
|
|
|
|
if(outputPlugin.SupportedMediaTags.Contains(tag.Key))
|
2019-04-19 18:54:25 +01:00
|
|
|
|
AddMediaTagToSidecar(outputPath, tag, ref sidecar);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
|
2018-02-02 22:03:19 +00:00
|
|
|
|
DicConsole.WriteLine("Writing metadata sidecar");
|
|
|
|
|
|
|
|
|
|
|
|
FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);
|
2018-01-28 21:18:52 +00:00
|
|
|
|
|
2018-02-02 22:03:19 +00:00
|
|
|
|
XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
|
|
|
|
|
|
xmlSer.Serialize(xmlFs, sidecar);
|
|
|
|
|
|
xmlFs.Close();
|
|
|
|
|
|
}
|
2018-01-20 17:12:01 +00:00
|
|
|
|
|
|
|
|
|
|
DicConsole.WriteLine();
|
|
|
|
|
|
|
2018-02-02 15:32:53 +00:00
|
|
|
|
DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming, {3:F3} writing, {4:F3} closing).",
|
2018-02-02 18:44:28 +00:00
|
|
|
|
(end - start).TotalSeconds, totalDuration / 1000,
|
|
|
|
|
|
totalChkDuration / 1000,
|
|
|
|
|
|
imageWriteDuration, (closeEnd - closeStart).TotalSeconds);
|
2019-04-17 12:04:09 -06:00
|
|
|
|
DicConsole.WriteLine("Average speed: {0:F3} MiB/sec.",
|
2018-01-20 17:12:01 +00:00
|
|
|
|
(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();
|
2017-06-20 05:48:09 +01:00
|
|
|
|
|
2018-01-20 17:12:01 +00:00
|
|
|
|
Statistics.AddMedia(dskType, true);
|
2017-05-31 01:00:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
|
}
|