Merge branch 'devel' into rebecca/first-lba-broken-workaround

This commit is contained in:
2024-10-20 00:59:38 +01:00
committed by GitHub
2265 changed files with 423246 additions and 126945 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -27,25 +27,27 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Devices;
using Schemas;
using Humanizer;
using Humanizer.Bytes;
using Track = Aaru.CommonTypes.Structs.Track;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -54,13 +56,9 @@ partial class Dump
/// <returns><c>true</c> if it contains Yellow Book data, <c>false</c> otherwise</returns>
static bool IsData(byte[] sector)
{
if(sector?.Length != 2352)
return false;
if(sector?.Length != 2352) return false;
byte[] syncMark =
{
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
};
byte[] syncMark = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
var testMark = new byte[12];
Array.Copy(sector, 0, testMark, 0, 12);
@@ -77,13 +75,9 @@ partial class Dump
{
offset = 0;
if(sector?.Length != 2352)
return false;
if(sector?.Length != 2352) return false;
byte[] syncMark =
{
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
};
byte[] syncMark = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
var testMark = new byte[12];
@@ -146,7 +140,7 @@ partial class Dump
/// <param name="mcn">Disc media catalogue number</param>
/// <param name="subchannelExtents">List of subchannels not yet dumped correctly</param>
/// <param name="smallestPregapLbaPerTrack">List of smallest pregap relative address per track</param>
void ReadCdiReady(uint blockSize, ref double currentSpeed, DumpHardwareType currentTry, ExtentsULong extents,
void ReadCdiReady(uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents,
IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed,
MhddLog mhddLog, ref double minSpeed, uint subSize, MmcSubchannel supportedSubchannel,
ref double totalDuration, Track[] tracks, SubchannelLog subLog, MmcSubchannel desiredSubchannel,
@@ -154,24 +148,22 @@ partial class Dump
bool cdiReadyReadAsAudio, int offsetBytes, int sectorsForOffset,
Dictionary<byte, int> smallestPregapLbaPerTrack)
{
ulong sectorSpeedStart = 0; // Used to calculate correct speed
DateTime timeSpeedStart = DateTime.UtcNow; // Time of start for speed calculation
bool sense; // Sense indicator
byte[] cmdBuf; // Data buffer
byte[] senseBuf; // Sense buffer
double cmdDuration; // Command execution time
const uint sectorSize = 2352; // Full sector size
ulong sectorSpeedStart = 0; // Used to calculate correct speed
bool sense; // Sense indicator
byte[] cmdBuf; // Data buffer
byte[] senseBuf; // Sense buffer
double cmdDuration; // Command execution time
const uint sectorSize = 2352; // Full sector size
Track firstTrack = tracks.FirstOrDefault();
uint blocksToRead; // How many sectors to read at once
var outputOptical = _outputPlugin as IWritableOpticalImage;
if(firstTrack is null)
return;
if(firstTrack is null) return;
if(cdiReadyReadAsAudio)
{
_dumpLog.WriteLine("Setting speed to 8x for CD-i Ready reading as audio.");
UpdateStatus?.Invoke("Setting speed to 8x for CD-i Ready reading as audio.");
_dumpLog.WriteLine(Localization.Core.Setting_speed_to_8x_for_CD_i_Ready_reading_as_audio);
UpdateStatus?.Invoke(Localization.Core.Setting_speed_to_8x_for_CD_i_Ready_reading_as_audio);
_dev.SetCdSpeed(out _, RotationalControl.ClvAndImpureCav, 1416, 0, _dev.Timeout, out _);
}
@@ -183,8 +175,8 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
@@ -193,10 +185,10 @@ partial class Dump
blocksToRead = _maximumReadable;
if(blocksToRead == 1 && cdiReadyReadAsAudio)
blocksToRead += (uint)sectorsForOffset;
if(blocksToRead == 1 && cdiReadyReadAsAudio) blocksToRead += (uint)sectorsForOffset;
if(cdiReadyReadAsAudio)
{
if(offsetBytes < 0)
{
if(i == 0)
@@ -204,50 +196,97 @@ partial class Dump
else
firstSectorToRead -= (uint)sectorsForOffset;
}
}
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i,
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
(long)blocks);
sense = _dev.ReadCd(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true,
MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
_speedStopwatch.Start();
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
firstSectorToRead,
blockSize,
blocksToRead,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
totalDuration += cmdDuration;
_speedStopwatch.Stop();
double elapsed;
// Overcome the track mode change drive error
if(sense)
{
for(uint r = 0; r < _maximumReadable; r++)
{
UpdateProgress?.Invoke($"Reading sector {i + r} of {blocks} ({currentSpeed:F3} MiB/sec.)",
(long)i + r, (long)blocks);
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i + r,
blocks,
ByteSize.FromMegabytes(currentSpeed)
.Per(_oneSecond)
.Humanize()),
(long)i + r,
(long)blocks);
sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)(i + r), blockSize, (uint)sectorsForOffset + 1,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
_speedStopwatch.Start();
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
(uint)(i + r),
blockSize,
(uint)sectorsForOffset + 1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
totalDuration += cmdDuration;
_speedStopwatch.Stop();
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
mhddLog.Write(i + r, cmdDuration);
ibgLog.Write(i + r, currentSpeed * 1024);
extents.Add(i + r, 1, true);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(cdiReadyReadAsAudio)
FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel,
ref blocksToRead, subSize, ref cmdBuf, blockSize, false);
{
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref blocksToRead,
subSize,
ref cmdBuf,
blockSize,
false);
}
if(supportedSubchannel != MmcSubchannel.None)
{
@@ -258,16 +297,30 @@ partial class Dump
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
if(cdiReadyReadAsAudio)
data = Sector.Scramble(data);
if(cdiReadyReadAsAudio) data = Sector.Scramble(data);
outputOptical.WriteSectorsLong(data, i + r, 1);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel, sub, i + r, 1, subLog, isrcs, 1, ref mcn, tracks,
subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel,
_fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true,
out List<ulong> newPregapSectors);
desiredSubchannel,
sub,
i + r,
1,
subLog,
isrcs,
1,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out List<ulong> _);
// Set tracks and go back
if(indexesChanged)
@@ -281,7 +334,9 @@ partial class Dump
else
outputOptical.WriteSectorsLong(cmdBuf, i + r, 1);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
_mediaGraph?.PaintSectorGood(i + r);
}
else
{
@@ -289,40 +344,52 @@ partial class Dump
leadOutExtents.Add(i + r, firstTrack.EndSector);
UpdateStatus?.
Invoke($"Adding CD-i Ready hole from LBA {i + r} to {firstTrack.EndSector} inclusive.");
UpdateStatus?.Invoke(string.Format(Localization.Core
.Adding_CD_i_Ready_hole_from_LBA_0_to_1_inclusive,
i + r,
firstTrack.EndSector));
_dumpLog.WriteLine("Adding CD-i Ready hole from LBA {0} to {1} inclusive.", i + r,
_dumpLog.WriteLine(Localization.Core.Adding_CD_i_Ready_hole_from_LBA_0_to_1_inclusive,
i + r,
firstTrack.EndSector);
break;
}
_writeStopwatch.Stop();
sectorSpeedStart += r;
_resume.NextBlock = i + r;
elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
}
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
if(cdiReadyReadAsAudio)
FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead,
subSize, ref cmdBuf, blockSize, false);
{
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref blocksToRead,
subSize,
ref cmdBuf,
blockSize,
false);
}
mhddLog.Write(i, cmdDuration);
ibgLog.Write(i, currentSpeed * 1024);
extents.Add(i, blocksToRead, true);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(supportedSubchannel != MmcSubchannel.None)
{
@@ -347,9 +414,24 @@ partial class Dump
outputOptical.WriteSectorsLong(data, i, blocksToRead);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel, sub, i, blocksToRead, subLog, isrcs, 1, ref mcn, tracks,
subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel,
_fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true,
desiredSubchannel,
sub,
i,
blocksToRead,
subLog,
isrcs,
1,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out List<ulong> newPregapSectors);
// Set tracks and go back
@@ -357,16 +439,14 @@ partial class Dump
{
outputOptical.SetTracks(tracks.ToList());
foreach(ulong newPregapSector in newPregapSectors)
_resume.BadBlocks.Add(newPregapSector);
foreach(ulong newPregapSector in newPregapSectors) _resume.BadBlocks.Add(newPregapSector);
if(i >= blocksToRead)
i -= blocksToRead;
else
i = 0;
if(i > 0)
i--;
if(i > 0) i--;
continue;
}
@@ -391,7 +471,9 @@ partial class Dump
outputOptical.WriteSectorsLong(cmdBuf, i, blocksToRead);
}
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
_mediaGraph?.PaintSectorsGood(i, blocksToRead);
}
else
{
@@ -402,20 +484,21 @@ partial class Dump
break;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
_speedStopwatch.Stop();
EndProgress?.Invoke();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -27,30 +27,31 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.Checksums;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Console;
using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
using Schemas;
using Track = Aaru.CommonTypes.Structs.Track;
using TrackType = Aaru.CommonTypes.Enums.TrackType;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Retried errored sectors in CompactDisc</summary>
@@ -72,7 +73,7 @@ partial class Dump
/// <param name="mcn">Disc media catalogue number</param>
/// <param name="subchannelExtents">List of subchannels not yet dumped correctly</param>
/// <param name="smallestPregapLbaPerTrack">List of smallest pregap relative address per track</param>
void RetryCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry, ExtentsULong extents,
void RetryCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardware currentTry, ExtentsULong extents,
int offsetBytes, bool readcd, int sectorsForOffset, uint subSize,
MmcSubchannel supportedSubchannel, ref double totalDuration, SubchannelLog subLog,
MmcSubchannel desiredSubchannel, Track[] tracks, Dictionary<byte, string> isrcs,
@@ -87,30 +88,15 @@ partial class Dump
PlextorSubchannel supportedPlextorSubchannel;
var outputOptical = _outputPlugin as IWritableOpticalImage;
switch(supportedSubchannel)
{
case MmcSubchannel.None:
supportedPlextorSubchannel = PlextorSubchannel.None;
supportedPlextorSubchannel = supportedSubchannel switch
{
MmcSubchannel.None => PlextorSubchannel.None,
MmcSubchannel.Raw => PlextorSubchannel.Pack,
MmcSubchannel.Q16 => PlextorSubchannel.Q16,
_ => PlextorSubchannel.None
};
break;
case MmcSubchannel.Raw:
supportedPlextorSubchannel = PlextorSubchannel.Pack;
break;
case MmcSubchannel.Q16:
supportedPlextorSubchannel = PlextorSubchannel.Q16;
break;
default:
supportedPlextorSubchannel = PlextorSubchannel.None;
break;
}
if(_resume.BadBlocks.Count <= 0 ||
_aborted ||
_retryPasses <= 0)
return;
if(_resume.BadBlocks.Count <= 0 || _aborted || _retryPasses <= 0) return;
var pass = 1;
var forward = true;
@@ -124,12 +110,25 @@ partial class Dump
{
Modes.ModePage_01_MMC pgMmc;
sense = _dev.ModeSense6(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
sense = _dev.ModeSense6(out cmdBuf,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(sense)
Modes.DecodedMode? dcMode6 = null;
if(!sense) dcMode6 = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);
if(sense || dcMode6 is null)
{
sense = _dev.ModeSense10(out cmdBuf, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
sense = _dev.ModeSense10(out cmdBuf,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(!sense)
@@ -137,19 +136,23 @@ partial class Dump
Modes.DecodedMode? dcMode10 = Modes.DecodeMode10(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);
if(dcMode10?.Pages != null)
{
foreach(Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage =>
modePage.Page == 0x01 && modePage.Subpage == 0x00))
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
}
}
}
else
{
Modes.DecodedMode? dcMode6 = Modes.DecodeMode6(cmdBuf, PeripheralDeviceTypes.MultiMediaDevice);
if(dcMode6?.Pages != null)
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage.Page == 0x01 &&
modePage.Subpage == 0x00))
if(dcMode6.Value.Pages != null)
{
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage is
{
Page: 0x01, Subpage: 0x00
}))
currentModePage = modePage;
}
}
if(currentModePage == null)
@@ -179,35 +182,35 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
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);
UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
_dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
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) sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
if(sense)
{
UpdateStatus?.
Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
UpdateStatus?.Invoke(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
AaruConsole.DebugWriteLine(Localization.Core.Error_0, Sense.PrettifySense(senseBuf));
_dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
_dumpLog.WriteLine(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
}
else
runningPersistent = true;
@@ -215,8 +218,8 @@ partial class Dump
InitProgress?.Invoke();
cdRepeatRetry:
ulong[] tmpArray = _resume.BadBlocks.ToArray();
var sectorsNotEvenPartial = new List<ulong>();
ulong[] tmpArray = _resume.BadBlocks.ToArray();
List<ulong> sectorsNotEvenPartial = [];
for(var i = 0; i < tmpArray.Length; i++)
{
@@ -225,34 +228,61 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
forward ? "forward" : "reverse",
runningPersistent ? "recovering partial data, " : ""));
if(forward)
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_forward,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_forward,
badSector,
pass));
}
else
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_reverse,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_reverse,
badSector,
pass));
}
Track track = tracks.OrderBy(t => t.StartSector).LastOrDefault(t => badSector >= t.StartSector);
byte sectorsToReRead = 1;
var badSectorToReRead = (uint)badSector;
if(_fixOffset &&
audioExtents.Contains(badSector) &&
offsetBytes != 0)
if(_fixOffset && audioExtents.Contains(badSector) && offsetBytes != 0)
{
if(offsetBytes > 0)
badSectorToReRead -= (uint)sectorsForOffset;
if(offsetBytes < 0)
{
if(badSectorToReRead == 0)
badSectorToReRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1
else
badSectorToReRead -= (uint)sectorsForOffset;
}
sectorsToReRead = (byte)(sectorsForOffset + 1);
sectorsToReRead += (byte)sectorsForOffset;
}
if(_supportsPlextorD8 && audioExtents.Contains(badSector))
{
sense = ReadPlextorWithSubchannel(out cmdBuf, out senseBuf, badSectorToReRead, blockSize,
sectorsToReRead, supportedPlextorSubchannel, out cmdDuration);
sense = ReadPlextorWithSubchannel(out cmdBuf,
out senseBuf,
badSectorToReRead,
blockSize,
sectorsToReRead,
supportedPlextorSubchannel,
out cmdDuration);
totalDuration += cmdDuration;
}
@@ -260,21 +290,45 @@ partial class Dump
{
if(audioExtents.Contains(badSector))
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToReRead, blockSize, sectorsToReRead,
MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false,
MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
badSectorToReRead,
blockSize,
sectorsToReRead,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
if(sense)
{
DecodedSense? decSense = Sense.Decode(senseBuf);
// Try to workaround firmware
if(decSense?.ASC == 0x11 && decSense?.ASCQ == 0x05 ||
decSense?.ASC == 0x64)
if(decSense is { ASC: 0x11, ASCQ: 0x05 } || decSense?.ASC == 0x64)
{
sense = _dev.ReadCd(out cmdBuf, out _, badSectorToReRead, blockSize, sectorsToReRead,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
sense = _dev.ReadCd(out cmdBuf,
out _,
badSectorToReRead,
blockSize,
sectorsToReRead,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out double cmdDuration2);
cmdDuration += cmdDuration2;
@@ -283,24 +337,121 @@ partial class Dump
}
else
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToReRead, blockSize, sectorsToReRead,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
badSectorToReRead,
blockSize,
sectorsToReRead,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
if(sense)
{
DecodedSense? decSense = Sense.Decode(senseBuf);
// Try to workaround firmware
if(decSense?.ASC == 0x11 && decSense?.ASCQ == 0x05 ||
decSense?.ASC == 0x64)
if(decSense is { ASC: 0x11, ASCQ: 0x05 } || decSense?.ASC == 0x64)
{
sense = _dev.ReadCd(out cmdBuf, out _, badSectorToReRead, blockSize, sectorsToReRead,
MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true,
false, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
byte scrambledSectorsToReRead = sectorsToReRead;
uint scrambledBadSectorToReRead = badSectorToReRead;
// Contrary to normal read, this must always be offset fixed, because it's data not audio
if(offsetBytes != 0)
{
if(offsetBytes < 0)
{
if(scrambledBadSectorToReRead == 0)
scrambledBadSectorToReRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1
else
scrambledBadSectorToReRead -= (uint)sectorsForOffset;
}
scrambledSectorsToReRead += (byte)sectorsForOffset;
}
sense = _dev.ReadCd(out cmdBuf,
out _,
scrambledBadSectorToReRead,
blockSize,
scrambledSectorsToReRead,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out double cmdDuration2);
cmdDuration += cmdDuration2;
if(!sense)
{
uint scrambledBlocksToReRead = scrambledSectorsToReRead;
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref scrambledBlocksToReRead,
subSize,
ref cmdBuf,
blockSize,
false);
// Descramble
cmdBuf = Sector.Scramble(cmdBuf);
// Check valid sector
CdChecksums.CheckCdSector(cmdBuf,
out bool? correctEccP,
out bool? correctEccQ,
out bool? correctEdc);
// Check mode, set sense if EDC/ECC validity is not correct
switch(cmdBuf[15] & 0x03)
{
case 0:
for(var c = 16; c < 2352; c++)
{
if(cmdBuf[c] == 0x00) continue;
sense = true;
break;
}
break;
case 1:
sense = correctEdc != true || correctEccP != true || correctEccQ != true;
break;
case 2:
if((cmdBuf[18] & 0x20) != 0x20)
{
if(correctEccP != true) sense = true;
if(correctEccQ != true) sense = true;
}
if(correctEdc != true) sense = true;
break;
}
}
}
}
}
@@ -312,35 +463,44 @@ partial class Dump
{
_errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf);
if(!runningPersistent)
continue;
if(!runningPersistent) continue;
DecodedSense? decSense = Sense.Decode(senseBuf);
// MEDIUM ERROR, retry with ignore error below
if(decSense is { ASC: 0x11 })
if(!sectorsNotEvenPartial.Contains(badSector))
sectorsNotEvenPartial.Add(badSector);
{
if(!sectorsNotEvenPartial.Contains(badSector)) sectorsNotEvenPartial.Add(badSector);
}
}
// Because one block has been partially used to fix the offset
if(_fixOffset &&
audioExtents.Contains(badSector) &&
offsetBytes != 0)
if(_fixOffset && audioExtents.Contains(badSector) && offsetBytes != 0)
{
uint blocksToRead = sectorsToReRead;
FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead, subSize,
ref cmdBuf, blockSize, false);
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref blocksToRead,
subSize,
ref cmdBuf,
blockSize,
false);
}
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
UpdateStatus?.Invoke($"Correctly retried sector {badSector} in pass {pass}.");
_dumpLog.WriteLine("Correctly retried sector {0} in pass {1}.", badSector, pass);
_mediaGraph?.PaintSectorGood(badSector);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_sector_0_in_pass_1,
badSector,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_sector_0_in_pass_1, badSector, pass);
sectorsNotEvenPartial.Remove(badSector);
}
else
@@ -350,26 +510,37 @@ partial class Dump
{
var data = new byte[sectorSize];
var sub = new byte[subSize];
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
if(supportsLongSectors)
outputOptical.WriteSectorLong(data, badSector);
else
outputOptical.WriteSector(Sector.GetUserData(data), badSector);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel,
sub, badSector, 1, subLog, isrcs,
(byte)track.Sequence, ref mcn, tracks,
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel,
sub,
badSector,
1,
subLog,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition, outputOptical,
_fixSubchannel, _fixSubchannelCrc,
_dumpLog, UpdateStatus,
smallestPregapLbaPerTrack, true, out _);
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
// Set tracks and go back
if(!indexesChanged)
continue;
if(!indexesChanged) continue;
outputOptical.SetTracks(tracks.ToList());
i--;
@@ -383,16 +554,13 @@ partial class Dump
}
}
if(pass < _retryPasses &&
!_aborted &&
_resume.BadBlocks.Count > 0)
if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0)
{
pass++;
forward = !forward;
_resume.BadBlocks.Sort();
if(!forward)
_resume.BadBlocks.Reverse();
if(!forward) _resume.BadBlocks.Reverse();
goto cdRepeatRetry;
}
@@ -419,25 +587,24 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
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);
_dumpLog.WriteLine("Sending MODE SELECT to drive (ignore error correction).");
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_ignore_error_correction);
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) sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
if(!sense)
{
@@ -452,20 +619,33 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke($"Trying to get partial data for sector {badSector}");
PulseProgress?.Invoke(string.Format(Localization.Core.Trying_to_get_partial_data_for_sector_0,
badSector));
Track track = tracks.OrderBy(t => t.StartSector).LastOrDefault(t => badSector >= t.StartSector);
if(readcd)
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)badSector, blockSize, 1,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
(uint)badSector,
blockSize,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
totalDuration += cmdDuration;
@@ -478,14 +658,14 @@ partial class Dump
continue;
}
_dumpLog.WriteLine("Got partial data for sector {0} in pass {1}.", badSector, pass);
_dumpLog.WriteLine(Localization.Core.Got_partial_data_for_sector_0_in_pass_1, badSector, pass);
if(supportedSubchannel != MmcSubchannel.None)
{
var data = new byte[sectorSize];
var sub = new byte[subSize];
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
if(supportsLongSectors)
outputOptical.WriteSectorLong(data, badSector);
@@ -493,13 +673,28 @@ partial class Dump
outputOptical.WriteSector(Sector.GetUserData(data), badSector);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel, sub, badSector, 1, subLog, isrcs, (byte)track.Sequence, ref mcn,
tracks, subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel,
_fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true, out _);
desiredSubchannel,
sub,
badSector,
1,
subLog,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
// Set tracks and go back
if(!indexesChanged)
continue;
if(!indexesChanged) continue;
outputOptical.SetTracks(tracks.ToList());
i--;
@@ -522,20 +717,16 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
currentModePage.Value
}
Pages = [currentModePage.Value]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
_dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);
if(sense)
_dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
if(sense) _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
}
EndProgress?.Invoke();
@@ -565,36 +756,17 @@ partial class Dump
PlextorSubchannel supportedPlextorSubchannel;
var outputOptical = _outputPlugin as IWritableOpticalImage;
if(supportedSubchannel == MmcSubchannel.None ||
desiredSubchannel == MmcSubchannel.None)
return;
if(supportedSubchannel == MmcSubchannel.None || desiredSubchannel == MmcSubchannel.None) return;
switch(supportedSubchannel)
{
case MmcSubchannel.None:
supportedPlextorSubchannel = PlextorSubchannel.None;
supportedPlextorSubchannel = supportedSubchannel switch
{
MmcSubchannel.Raw => PlextorSubchannel.All,
MmcSubchannel.Q16 => PlextorSubchannel.Q16,
MmcSubchannel.Rw => PlextorSubchannel.Pack,
_ => PlextorSubchannel.None
};
break;
case MmcSubchannel.Raw:
supportedPlextorSubchannel = PlextorSubchannel.All;
break;
case MmcSubchannel.Q16:
supportedPlextorSubchannel = PlextorSubchannel.Q16;
break;
case MmcSubchannel.Rw:
supportedPlextorSubchannel = PlextorSubchannel.Pack;
break;
default:
supportedPlextorSubchannel = PlextorSubchannel.None;
break;
}
if(_aborted)
return;
if(_aborted) return;
var pass = 1;
var forward = true;
@@ -603,12 +775,11 @@ partial class Dump
cdRepeatRetry:
_resume.BadSubchannels = new List<int>();
_resume.BadSubchannels = [];
_resume.BadSubchannels.AddRange(subchannelExtents);
_resume.BadSubchannels.Sort();
if(!forward)
_resume.BadSubchannels.Reverse();
if(!forward) _resume.BadSubchannels.Reverse();
int[] tmpArray = _resume.BadSubchannels.ToArray();
@@ -620,29 +791,52 @@ partial class Dump
if(_aborted)
{
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.
Invoke($"Retrying sector {badSector} subchannel, pass {pass}, {(forward ? "forward" : "reverse")}");
PulseProgress?.Invoke(forward
? string.Format(Localization.Core.Retrying_sector_0_subchannel_pass_1_forward,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_subchannel_pass_1_reverse,
badSector,
pass));
uint startSector = badSector - 2;
if(_supportsPlextorD8)
{
sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, startSector, subSize, 5,
supportedPlextorSubchannel, 0, out cmdDuration);
sense = _dev.PlextorReadCdDa(out cmdBuf,
out senseBuf,
startSector,
subSize,
5,
supportedPlextorSubchannel,
0,
out cmdDuration);
totalDuration += cmdDuration;
}
else if(readcd)
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, startSector, subSize, 5,
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
startSector,
subSize,
5,
track.Type == TrackType.Audio ? MmcSectorTypes.Cdda : MmcSectorTypes.AllTypes,
false, false, false, MmcHeaderCodes.None, false, false, MmcErrorField.None,
supportedSubchannel, _dev.Timeout, out cmdDuration);
false,
false,
false,
MmcHeaderCodes.None,
false,
false,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
totalDuration += cmdDuration;
}
@@ -654,22 +848,37 @@ partial class Dump
continue;
}
Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel, cmdBuf, badSector, 5,
subLog, isrcs, (byte)track.Sequence, ref mcn, tracks,
subchannelExtents, _fixSubchannelPosition, outputOptical,
_fixSubchannel, _fixSubchannelCrc, _dumpLog, UpdateStatus,
smallestPregapLbaPerTrack, true, out _);
Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel,
cmdBuf,
badSector,
5,
subLog,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
if(subchannelExtents.Contains(bs))
continue;
if(subchannelExtents.Contains(bs)) continue;
UpdateStatus?.Invoke($"Correctly retried sector {badSector} subchannel in pass {pass}.");
_dumpLog.WriteLine("Correctly retried sector {0} subchannel in pass {1}.", badSector, pass);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_sector_0_subchannel_in_pass_1,
badSector,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_sector_0_subchannel_in_pass_1, badSector, pass);
}
if(pass < _retryPasses &&
!_aborted &&
subchannelExtents.Count > 0)
if(pass < _retryPasses && !_aborted && subchannelExtents.Count > 0)
{
pass++;
forward = !forward;

View File

@@ -27,25 +27,28 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Logging;
using Aaru.Devices;
using Schemas;
using Humanizer;
using Humanizer.Bytes;
using Track = Aaru.CommonTypes.Structs.Track;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -75,7 +78,10 @@ partial class Dump
/// <param name="mcn">Disc media catalogue number</param>
/// <param name="subchannelExtents">List of subchannels not yet dumped correctly</param>
/// <param name="smallestPregapLbaPerTrack">List of smallest pregap relative address per track</param>
void DumpCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardwareType currentTry, ExtentsULong extents,
// TODO: Use it
[SuppressMessage("ReSharper", "UnusedMember.Local")]
void DumpCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents,
IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed,
MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16,
bool readcd, MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration,
@@ -89,62 +95,113 @@ partial class Dump
byte[] senseBuf = null;
var outputOptical = _outputPlugin as IWritableOpticalImage;
UpdateStatus?.Invoke("Reading lead-outs");
_dumpLog.WriteLine("Reading lead-outs");
UpdateStatus?.Invoke(Localization.Core.Reading_lead_outs);
_dumpLog.WriteLine(Localization.Core.Reading_lead_outs);
InitProgress?.Invoke();
foreach((ulong item1, ulong item2) in leadOutExtents.ToArray())
{
for(ulong i = item1; i <= item2; i++)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
double cmdDuration = 0;
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
PulseProgress?.Invoke($"Reading sector {i} at lead-out ({currentSpeed:F3} MiB/sec.)");
PulseProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_at_lead_out_1,
i,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()));
if(readcd)
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, MmcSectorTypes.AllTypes, false,
false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
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 cmdBuf, out senseBuf, 0, false, true, false, i, blockSize, 0, 1, false,
_dev.Timeout, out cmdDuration);
{
sense = _dev.Read16(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
i,
blockSize,
0,
1,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read12)
sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0,
1, false, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read12(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
(uint)i,
blockSize,
0,
1,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read10)
sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0,
1, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read10(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
(uint)i,
blockSize,
0,
1,
_dev.Timeout,
out cmdDuration);
}
else if(read6)
sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, _dev.Timeout, out cmdDuration);
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration);
ibgLog.Write(i, currentSpeed * 1024);
extents.Add(i, _maximumReadable, true);
leadOutExtents.Remove(i);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(supportedSubchannel != MmcSubchannel.None)
{
@@ -161,9 +218,25 @@ partial class Dump
outputOptical.WriteSectorsLong(data, i, _maximumReadable);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel, sub, i, _maximumReadable, subLog, isrcs, 0xAA, ref mcn, tracks,
subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel,
_fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true, out _);
desiredSubchannel,
sub,
i,
_maximumReadable,
subLog,
isrcs,
0xAA,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
// Set tracks and go back
if(indexesChanged)
@@ -177,43 +250,45 @@ partial class Dump
else
outputOptical.WriteSectors(cmdBuf, i, _maximumReadable);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
}
else
{
_errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
// Write empty data
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(supportedSubchannel != MmcSubchannel.None)
{
outputOptical.WriteSectorsLong(new byte[sectorSize * _skip], i, 1);
outputOptical.WriteSectorsTag(new byte[subSize * _skip], i, 1,
outputOptical.WriteSectorsTag(new byte[subSize * _skip],
i,
1,
SectorTagType.CdSectorSubchannel);
}
else
outputOptical.WriteSectors(new byte[blockSize * _skip], i, 1);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
}
_writeStopwatch.Stop();
double newSpeed = (double)blockSize * _maximumReadable / 1048576 / (cmdDuration / 1000);
if(!double.IsInfinity(newSpeed))
currentSpeed = newSpeed;
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
_resume.NextBlock = i + 1;
}
}
EndProgress?.Invoke();
}
@@ -244,7 +319,10 @@ partial class Dump
/// <param name="mcn">Disc media catalogue number</param>
/// <param name="subchannelExtents">List of subchannels not yet dumped correctly</param>
/// <param name="smallestPregapLbaPerTrack">List of smallest pregap relative address per track</param>
void RetryCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardwareType currentTry, ExtentsULong extents,
// TODO: Use it
[SuppressMessage("ReSharper", "UnusedMember.Local")]
void RetryCdLeadOuts(uint blockSize, ref double currentSpeed, DumpHardware currentTry, ExtentsULong extents,
IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed,
MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16,
bool readcd, MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration,
@@ -263,56 +341,107 @@ partial class Dump
InitProgress?.Invoke();
foreach((ulong item1, ulong item2) in leadOutExtents.ToArray())
{
for(ulong i = item1; i <= item2; i++)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
double cmdDuration = 0;
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
PulseProgress?.Invoke($"Reading sector {i} at lead-out ({currentSpeed:F3} MiB/sec.)");
PulseProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_at_lead_out_1,
i,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()));
if(readcd)
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, MmcSectorTypes.AllTypes, false,
false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None,
supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
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 cmdBuf, out senseBuf, 0, false, true, false, i, blockSize, 0, 1, false,
_dev.Timeout, out cmdDuration);
{
sense = _dev.Read16(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
i,
blockSize,
0,
1,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read12)
sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0,
1, false, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read12(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
(uint)i,
blockSize,
0,
1,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read10)
sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0,
1, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read10(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
(uint)i,
blockSize,
0,
1,
_dev.Timeout,
out cmdDuration);
}
else if(read6)
sense = _dev.Read6(out cmdBuf, out senseBuf, (uint)i, blockSize, 1, _dev.Timeout, out cmdDuration);
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration);
mhddLog.Write(i, cmdDuration, _maximumReadable);
ibgLog.Write(i, currentSpeed * 1024);
extents.Add(i, _maximumReadable, true);
leadOutExtents.Remove(i);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(supportedSubchannel != MmcSubchannel.None)
{
@@ -329,9 +458,25 @@ partial class Dump
outputOptical.WriteSectorsLong(data, i, _maximumReadable);
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel, sub, i, _maximumReadable, subLog, isrcs, 0xAA, ref mcn, tracks,
subchannelExtents, _fixSubchannelPosition, outputOptical, _fixSubchannel,
_fixSubchannelCrc, _dumpLog, UpdateStatus, smallestPregapLbaPerTrack, true, out _);
desiredSubchannel,
sub,
i,
_maximumReadable,
subLog,
isrcs,
0xAA,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
// Set tracks and go back
if(indexesChanged)
@@ -345,42 +490,46 @@ partial class Dump
else
outputOptical.WriteSectors(cmdBuf, i, _maximumReadable);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
}
else
{
_errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
// Write empty data
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
if(supportedSubchannel != MmcSubchannel.None)
{
outputOptical.WriteSectorsLong(new byte[sectorSize * _skip], i, 1);
if(desiredSubchannel != MmcSubchannel.None)
outputOptical.WriteSectorsTag(new byte[subSize * _skip], i, 1,
{
outputOptical.WriteSectorsTag(new byte[subSize * _skip],
i,
1,
SectorTagType.CdSectorSubchannel);
}
}
else
outputOptical.WriteSectors(new byte[blockSize * _skip], i, 1);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
ibgLog.Write(i, 0);
}
_writeStopwatch.Stop();
double newSpeed = (double)blockSize * _maximumReadable / 1048576 / (cmdDuration / 1000);
if(!double.IsInfinity(newSpeed))
currentSpeed = newSpeed;
if(!double.IsInfinity(newSpeed)) currentSpeed = newSpeed;
}
}
EndProgress?.Invoke();
}

View File

@@ -27,20 +27,18 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Fix offset in audio/scrambled sectors</summary>
@@ -57,8 +55,7 @@ partial class Dump
ref uint blocksToRead, uint subSize, ref byte[] cmdBuf, uint blockSize,
bool failedCrossingLeadOut)
{
if(cmdBuf.Length == 0)
return;
if(cmdBuf.Length == 0) return;
int offsetFix = offsetBytes < 0 ? (int)(sectorSize * sectorsForOffset + offsetBytes) : offsetBytes;
@@ -73,7 +70,7 @@ partial class Dump
for(var b = 0; b < blocksToRead; b++)
{
Array.Copy(cmdBuf, (int)(0 + b * blockSize), data, sectorSize * b, sectorSize);
Array.Copy(cmdBuf, (int)(sectorSize + b * blockSize), sub, subSize * b, subSize);
Array.Copy(cmdBuf, (int)(sectorSize + b * blockSize), sub, subSize * b, subSize);
}
if(failedCrossingLeadOut)
@@ -100,7 +97,7 @@ partial class Dump
for(var b = 0; b < blocksToRead; b++)
{
Array.Copy(data, sectorSize * b, cmdBuf, (int)(0 + b * blockSize), sectorSize);
Array.Copy(sub, subSize * b, cmdBuf, (int)(sectorSize + b * blockSize), subSize);
Array.Copy(sub, subSize * b, cmdBuf, (int)(sectorSize + b * blockSize), subSize);
}
}
else

View File

@@ -27,14 +27,14 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Reads a sector using Plextor's D8h READ CDDA command with subchannel</summary>
@@ -46,8 +46,8 @@ partial class Dump
/// <param name="supportedPlextorSubchannel">Supported subchannel type</param>
/// <param name="cmdDuration">Time spent sending commands to the drive</param>
/// <returns><c>true</c> if an error occured, <c>false</c> otherwise</returns>
bool ReadPlextorWithSubchannel(out byte[] cmdBuf, out byte[] senseBuf, uint firstSectorToRead, uint blockSize,
uint blocksToRead, PlextorSubchannel supportedPlextorSubchannel,
bool ReadPlextorWithSubchannel(out byte[] cmdBuf, out byte[] senseBuf, uint firstSectorToRead, uint blockSize,
uint blocksToRead, PlextorSubchannel supportedPlextorSubchannel,
out double cmdDuration)
{
bool sense;
@@ -55,58 +55,95 @@ partial class Dump
if(supportedPlextorSubchannel == PlextorSubchannel.None)
{
sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead,
supportedPlextorSubchannel, 0, out cmdDuration);
sense = _dev.PlextorReadCdDa(out cmdBuf,
out senseBuf,
firstSectorToRead,
blockSize,
blocksToRead,
supportedPlextorSubchannel,
0,
out cmdDuration);
if(!sense)
return false;
if(!sense) return false;
// As a workaround for some firmware bugs, seek far away.
_dev.PlextorReadCdDa(out _, out senseBuf, firstSectorToRead - 32, blockSize, blocksToRead,
supportedPlextorSubchannel, 0, out _);
_dev.PlextorReadCdDa(out _,
out senseBuf,
firstSectorToRead - 32,
blockSize,
blocksToRead,
supportedPlextorSubchannel,
0,
out _);
sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, firstSectorToRead, blockSize, blocksToRead,
supportedPlextorSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.PlextorReadCdDa(out cmdBuf,
out senseBuf,
firstSectorToRead,
blockSize,
blocksToRead,
supportedPlextorSubchannel,
_dev.Timeout,
out cmdDuration);
return sense;
}
byte[] subBuf;
uint subSize = supportedPlextorSubchannel == PlextorSubchannel.Q16 ? 16u : 96u;
if(supportedPlextorSubchannel is PlextorSubchannel.Q16 or PlextorSubchannel.Pack)
{
sense = _dev.PlextorReadCdDa(out cmdBuf, out senseBuf, firstSectorToRead, 2352 + subSize, blocksToRead,
supportedPlextorSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.PlextorReadCdDa(out cmdBuf,
out senseBuf,
firstSectorToRead,
2352 + subSize,
blocksToRead,
supportedPlextorSubchannel,
_dev.Timeout,
out cmdDuration);
if(!sense)
return false;
if(!sense) return false;
}
// As a workaround for some firmware bugs, seek far away.
_dev.PlextorReadCdDa(out _, out senseBuf, firstSectorToRead - 32, blockSize, blocksToRead,
supportedPlextorSubchannel, 0, out _);
_dev.PlextorReadCdDa(out _,
out senseBuf,
firstSectorToRead - 32,
blockSize,
blocksToRead,
supportedPlextorSubchannel,
0,
out _);
sense = _dev.PlextorReadCdDa(out byte[] dataBuf, out senseBuf, firstSectorToRead, 2352, blocksToRead,
PlextorSubchannel.None, 0, out cmdDuration);
sense = _dev.PlextorReadCdDa(out byte[] dataBuf,
out senseBuf,
firstSectorToRead,
2352,
blocksToRead,
PlextorSubchannel.None,
0,
out cmdDuration);
if(sense)
return true;
if(sense) return true;
sense = _dev.PlextorReadCdDa(out subBuf, out senseBuf, firstSectorToRead, subSize, blocksToRead,
supportedPlextorSubchannel == PlextorSubchannel.Pack ? PlextorSubchannel.All
: supportedPlextorSubchannel, 0, out cmdDuration);
sense = _dev.PlextorReadCdDa(out byte[] subBuf,
out senseBuf,
firstSectorToRead,
subSize,
blocksToRead,
supportedPlextorSubchannel == PlextorSubchannel.Pack
? PlextorSubchannel.All
: supportedPlextorSubchannel,
0,
out cmdDuration);
if(sense)
return true;
if(sense) return true;
cmdBuf = new byte[2352 * blocksToRead + subSize * blocksToRead];
for(var b = 0; b < blocksToRead; b++)
{
Array.Copy(dataBuf, 2352 * b, cmdBuf, (2352 + subSize) * b, 2352);
Array.Copy(subBuf, subSize * b, cmdBuf, (2352 + subSize) * b + 2352, subSize);
Array.Copy(dataBuf, 2352 * b, cmdBuf, (2352 + subSize) * b, 2352);
Array.Copy(subBuf, subSize * b, cmdBuf, (2352 + subSize) * b + 2352, subSize);
}
return false;

File diff suppressed because it is too large Load Diff

View File

@@ -27,11 +27,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
@@ -42,57 +40,64 @@ using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
void HandleCdrRunOutSectors(ulong blocks, MmcSubchannel desiredSubchannel, ExtentsULong extents,
HashSet<int> subchannelExtents, SubchannelLog subLog, bool supportsLongSectors,
Dictionary<byte, byte> trackFlags, Track[] tracks)
{
List<ulong> runOutSectors = new();
List<ulong> runOutSectors = [];
if(_outputPlugin is not IWritableOpticalImage outputOptical)
return;
if(_outputPlugin is not IWritableOpticalImage outputOptical) return;
// Count how many run end sectors are detected as bad blocks
for(ulong i = blocks - 1; i > blocks - 1 - _ignoreCdrRunOuts; i--)
{
if(_resume.BadBlocks.Contains(i))
runOutSectors.Add(i);
else
break;
}
if(runOutSectors.Count == 0)
return;
if(runOutSectors.Count == 0) return;
_dumpLog.WriteLine($"{runOutSectors.Count} sectors at the end of the disc are unreadable. This is normal in CD-R(W) discs as these sectors are created by burning software as part of the recording process. Empty ones will be generated and stored in the image.");
_dumpLog.WriteLine(string.Format(Localization.Core._0_sectors_at_the_end_of_the_disc_are_unreadable,
runOutSectors.Count));
UpdateStatus?.
Invoke($"{runOutSectors.Count} sectors at the end of the disc are unreadable. This is normal in CD-R(W) discs as these sectors are created by burning software as part of the recording process. Empty ones will be generated and stored in the image.");
UpdateStatus?.Invoke(string.Format(Localization.Core._0_sectors_at_the_end_of_the_disc_are_unreadable,
runOutSectors.Count));
foreach(ulong s in runOutSectors)
{
Track track = tracks.FirstOrDefault(t => t.StartSector <= s && t.EndSector >= s);
if(track is null)
continue;
if(track is null) continue;
var sector = new byte[2352];
switch(track.Type)
{
case TrackType.Audio: break;
case TrackType.Audio:
break;
case TrackType.Data:
sector = new byte[2048];
break;
case TrackType.CdMode1: break;
case TrackType.CdMode2Formless: break;
case TrackType.CdMode2Form1: break;
case TrackType.CdMode2Form2: break;
default: continue;
case TrackType.CdMode1:
break;
case TrackType.CdMode2Formless:
break;
case TrackType.CdMode2Form1:
break;
case TrackType.CdMode2Form2:
break;
default:
continue;
}
if(track.Type != TrackType.Audio &&
track.Type != TrackType.Data)
if(track.Type != TrackType.Audio && track.Type != TrackType.Data)
{
SectorBuilder sb = new();
sb.ReconstructPrefix(ref sector, track.Type, (long)s);
@@ -107,8 +112,7 @@ partial class Dump
_resume.BadBlocks.Remove(s);
extents.Add(s);
if(desiredSubchannel == MmcSubchannel.None)
continue;
if(desiredSubchannel == MmcSubchannel.None) continue;
// Hidden track
ulong trackStart;

View File

@@ -27,21 +27,19 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using Aaru.CommonTypes;
using Aaru.Core.Logging;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Check if the drive can read RW raw subchannel</summary>
@@ -52,11 +50,24 @@ partial class Dump
/// <returns><c>true</c> if read correctly, <c>false</c> otherwise</returns>
public static bool SupportsRwSubchannel(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, uint lba)
{
dumpLog?.WriteLine("Checking if drive supports full raw subchannel reading...");
updateStatus?.Invoke("Checking if drive supports full raw subchannel reading...");
dumpLog?.WriteLine(Localization.Core.Checking_if_drive_supports_full_raw_subchannel_reading);
updateStatus?.Invoke(Localization.Core.Checking_if_drive_supports_full_raw_subchannel_reading);
return !dev.ReadCd(out _, out _, lba, 2352 + 96, 1, MmcSectorTypes.AllTypes, false, false, true,
MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Raw, dev.Timeout,
return !dev.ReadCd(out _,
out _,
lba,
2352 + 96,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.Raw,
dev.Timeout,
out _);
}
@@ -68,11 +79,24 @@ partial class Dump
/// <returns><c>true</c> if read correctly, <c>false</c> otherwise</returns>
public static bool SupportsPqSubchannel(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, uint lba)
{
dumpLog?.WriteLine("Checking if drive supports PQ subchannel reading...");
updateStatus?.Invoke("Checking if drive supports PQ subchannel reading...");
dumpLog?.WriteLine(Localization.Core.Checking_if_drive_supports_PQ_subchannel_reading);
updateStatus?.Invoke(Localization.Core.Checking_if_drive_supports_PQ_subchannel_reading);
return !dev.ReadCd(out _, out _, lba, 2352 + 16, 1, MmcSectorTypes.AllTypes, false, false, true,
MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout,
return !dev.ReadCd(out _,
out _,
lba,
2352 + 16,
1,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
MmcSubchannel.Q16,
dev.Timeout,
out _);
}
}

View File

@@ -27,17 +27,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using Aaru.CommonTypes;
@@ -46,6 +42,8 @@ using Aaru.Decoders.CD;
using Aaru.Decoders.SCSI.MMC;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Reads media tags from Compact Disc media</summary>
@@ -54,7 +52,7 @@ partial class Dump
/// <param name="sessions">Sessions</param>
/// <param name="firstTrackLastSession">First track in last session</param>
void ReadCdTags(ref MediaType mediaType, Dictionary<MediaTagType, byte[]> mediaTags, out int sessions,
out int firstTrackLastSession)
out int firstTrackLastSession)
{
byte[] cmdBuf; // Data buffer
bool sense; // Sense indicator
@@ -63,8 +61,8 @@ partial class Dump
firstTrackLastSession = 1;
// ATIP exists on blank CDs
_dumpLog.WriteLine("Reading ATIP");
UpdateStatus?.Invoke("Reading ATIP");
_dumpLog.WriteLine(Localization.Core.Reading_ATIP);
UpdateStatus?.Invoke(Localization.Core.Reading_ATIP);
sense = _dev.ReadAtip(out cmdBuf, out _, _dev.Timeout, out _);
if(!sense)
@@ -79,48 +77,48 @@ partial class Dump
tmpBuf = new byte[cmdBuf.Length - 4];
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);
mediaTags.Add(MediaTagType.CD_ATIP, tmpBuf);
_mediaGraph?.PaintRecordableInformationGood();
}
}
_dumpLog.WriteLine("Reading Disc Information");
UpdateStatus?.Invoke("Reading Disc Information");
_dumpLog.WriteLine(Localization.Core.Reading_Disc_Information);
UpdateStatus?.Invoke(Localization.Core.Reading_Disc_Information);
sense = _dev.ReadDiscInformation(out cmdBuf, out _, MmcDiscInformationDataTypes.DiscInformation, _dev.Timeout,
sense = _dev.ReadDiscInformation(out cmdBuf,
out _,
MmcDiscInformationDataTypes.DiscInformation,
_dev.Timeout,
out _);
if(!sense)
{
DiscInformation.StandardDiscInformation? discInfo = DiscInformation.Decode000b(cmdBuf);
if(discInfo.HasValue &&
mediaType == MediaType.CD)
switch(discInfo.Value.DiscType)
{
case 0x10:
mediaType = MediaType.CDI;
break;
case 0x20:
mediaType = MediaType.CDROMXA;
break;
}
if(discInfo.HasValue && mediaType == MediaType.CD)
{
mediaType = discInfo.Value.DiscType switch
{
0x10 => MediaType.CDI,
0x20 => MediaType.CDROMXA,
_ => mediaType
};
}
}
_dumpLog.WriteLine("Reading PMA");
UpdateStatus?.Invoke("Reading PMA");
_dumpLog.WriteLine(Localization.Core.Reading_PMA);
UpdateStatus?.Invoke(Localization.Core.Reading_PMA);
sense = _dev.ReadPma(out cmdBuf, out _, _dev.Timeout, out _);
if(!sense &&
PMA.Decode(cmdBuf).HasValue)
if(!sense && PMA.Decode(cmdBuf).HasValue)
{
tmpBuf = new byte[cmdBuf.Length - 4];
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);
mediaTags.Add(MediaTagType.CD_PMA, tmpBuf);
_mediaGraph?.PaintRecordableInformationGood();
}
_dumpLog.WriteLine("Reading Session Information");
UpdateStatus?.Invoke("Reading Session Information");
_dumpLog.WriteLine(Localization.Core.Reading_Session_Information);
UpdateStatus?.Invoke(Localization.Core.Reading_Session_Information);
sense = _dev.ReadSessionInfo(out cmdBuf, out _, _dev.Timeout, out _);
if(!sense)
@@ -134,12 +132,11 @@ partial class Dump
}
}
_dumpLog.WriteLine("Reading CD-Text from Lead-In");
UpdateStatus?.Invoke("Reading CD-Text from Lead-In");
_dumpLog.WriteLine(Localization.Core.Reading_CD_Text_from_Lead_In);
UpdateStatus?.Invoke(Localization.Core.Reading_CD_Text_from_Lead_In);
sense = _dev.ReadCdText(out cmdBuf, out _, _dev.Timeout, out _);
if(sense || !CDTextOnLeadIn.Decode(cmdBuf).HasValue)
return;
if(sense || !CDTextOnLeadIn.Decode(cmdBuf).HasValue) return;
tmpBuf = new byte[cmdBuf.Length - 4];
Array.Copy(cmdBuf, 4, tmpBuf, 0, cmdBuf.Length - 4);

View File

@@ -27,15 +27,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -46,6 +44,8 @@ using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>Reads the TOC, processes it, returns the track list and last sector</summary>
@@ -60,24 +60,24 @@ partial class Dump
/// <param name="trackFlags">Track flags</param>
/// <param name="updateStatus">Update status handler</param>
/// <returns>List of tracks</returns>
public static Track[] GetCdTracks(Device dev, DumpLog dumpLog, bool force, out long lastSector,
Dictionary<int, long> leadOutStarts, Dictionary<MediaTagType, byte[]> mediaTags,
ErrorMessageHandler stoppingErrorMessage, out FullTOC.CDFullTOC? toc,
public static Track[] GetCdTracks(Device dev, DumpLog dumpLog, bool force, out long lastSector,
Dictionary<int, long> leadOutStarts, Dictionary<MediaTagType, byte[]> mediaTags,
ErrorMessageHandler stoppingErrorMessage, out FullTOC.CDFullTOC? toc,
Dictionary<byte, byte> trackFlags, UpdateStatusHandler updateStatus)
{
byte[] cmdBuf; // Data buffer
const uint sectorSize = 2352; // Full sector size
bool sense; // Sense indicator
var trackList = new List<Track>(); // Tracks in disc
byte[] tmpBuf; // Temporary buffer
byte[] cmdBuf; // Data buffer
const uint sectorSize = 2352; // Full sector size
bool sense; // Sense indicator
List<Track> trackList = []; // Tracks in disc
byte[] tmpBuf; // Temporary buffer
toc = null;
lastSector = 0;
TrackType leadoutTrackType = TrackType.Audio;
// We discarded all discs that falsify a TOC before requesting a real TOC
// No TOC, no CD (or an empty one)
dumpLog?.WriteLine("Reading full TOC");
updateStatus?.Invoke("Reading full TOC");
dumpLog?.WriteLine(Localization.Core.Reading_full_TOC);
updateStatus?.Invoke(Localization.Core.Reading_full_TOC);
sense = dev.ReadRawToc(out cmdBuf, out _, 0, dev.Timeout, out _);
if(!sense)
@@ -92,8 +92,8 @@ partial class Dump
}
}
updateStatus?.Invoke("Building track map...");
dumpLog?.WriteLine("Building track map...");
updateStatus?.Invoke(Localization.Core.Building_track_map);
dumpLog?.WriteLine(Localization.Core.Building_track_map);
if(toc.HasValue)
{
@@ -101,126 +101,143 @@ partial class Dump
toc.Value.TrackDescriptors.OrderBy(track => track.POINT).ToArray();
foreach(FullTOC.TrackDataDescriptor trk in sortedTracks.Where(trk => trk.ADR is 1 or 4))
if(trk.POINT >= 0x01 &&
trk.POINT <= 0x63)
{
switch(trk.POINT)
{
trackList.Add(new Track
{
Sequence = trk.POINT,
Session = trk.SessionNumber,
Type = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data
: TrackType.Audio,
StartSector =
(ulong)(trk.PHOUR * 3600 * 75 + trk.PMIN * 60 * 75 + trk.PSEC * 75 + trk.PFRAME - 150),
BytesPerSector = (int)sectorSize,
RawBytesPerSector = (int)sectorSize
});
trackFlags?.Add(trk.POINT, trk.CONTROL);
}
else if(trk.POINT == 0xA2)
{
int phour, pmin, psec, pframe;
if(trk.PFRAME == 0)
{
pframe = 74;
if(trk.PSEC == 0)
case >= 0x01 and <= 0x63:
trackList.Add(new Track
{
psec = 59;
Sequence = trk.POINT,
Session = trk.SessionNumber,
Type = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
? TrackType.Data
: TrackType.Audio,
StartSector =
(ulong)(trk.PHOUR * 3600 * 75 + trk.PMIN * 60 * 75 + trk.PSEC * 75 + trk.PFRAME - 150),
BytesPerSector = (int)sectorSize,
RawBytesPerSector = (int)sectorSize
});
if(trk.PMIN == 0)
trackFlags?.Add(trk.POINT, trk.CONTROL);
break;
case 0xA2:
{
int phour, pmin, psec, pframe;
if(trk.PFRAME == 0)
{
pframe = 74;
if(trk.PSEC == 0)
{
pmin = 59;
phour = trk.PHOUR - 1;
psec = 59;
if(trk.PMIN == 0)
{
pmin = 59;
phour = trk.PHOUR - 1;
}
else
{
pmin = trk.PMIN - 1;
phour = trk.PHOUR;
}
}
else
{
pmin = trk.PMIN - 1;
psec = trk.PSEC - 1;
pmin = trk.PMIN;
phour = trk.PHOUR;
}
}
else
{
psec = trk.PSEC - 1;
pmin = trk.PMIN;
phour = trk.PHOUR;
pframe = trk.PFRAME - 1;
psec = trk.PSEC;
pmin = trk.PMIN;
phour = trk.PHOUR;
}
}
else
{
pframe = trk.PFRAME - 1;
psec = trk.PSEC;
pmin = trk.PMIN;
phour = trk.PHOUR;
}
lastSector = phour * 3600 * 75 + pmin * 60 * 75 + psec * 75 + pframe - 150;
leadOutStarts?.Add(trk.SessionNumber, lastSector + 1);
lastSector = phour * 3600 * 75 + pmin * 60 * 75 + psec * 75 + pframe - 150;
leadOutStarts?.Add(trk.SessionNumber, lastSector + 1);
break;
}
case 0xA0 when trk.ADR == 1:
leadoutTrackType =
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
? TrackType.Data
: TrackType.Audio;
break;
}
else if(trk.POINT == 0xA0 &&
trk.ADR == 1)
leadoutTrackType =
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data
: TrackType.Audio;
}
}
else
{
updateStatus?.Invoke("Cannot read RAW TOC, requesting processed one...");
dumpLog?.WriteLine("Cannot read RAW TOC, requesting processed one...");
updateStatus?.Invoke(Localization.Core.Cannot_read_RAW_TOC_requesting_processed_one);
dumpLog?.WriteLine(Localization.Core.Cannot_read_RAW_TOC_requesting_processed_one);
sense = dev.ReadToc(out cmdBuf, out _, false, 0, dev.Timeout, out _);
TOC.CDTOC? oldToc = TOC.Decode(cmdBuf);
if((sense || !oldToc.HasValue) &&
!force)
if((sense || !oldToc.HasValue) && !force)
{
dumpLog?.WriteLine("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000...");
dumpLog?.WriteLine(Localization.Core
.Could_not_read_TOC_if_you_want_to_continue_use_force_and_will_try_from_LBA_0_to_360000);
stoppingErrorMessage?.
Invoke("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000...");
stoppingErrorMessage?.Invoke(Localization.Core
.Could_not_read_TOC_if_you_want_to_continue_use_force_and_will_try_from_LBA_0_to_360000);
return null;
}
if(oldToc.HasValue)
foreach(TOC.CDTOCTrackDataDescriptor trk in oldToc.Value.TrackDescriptors.OrderBy(t => t.TrackNumber).
Where(trk => trk.ADR is 1 or 4))
if(trk.TrackNumber >= 0x01 &&
trk.TrackNumber <= 0x63)
{
foreach(TOC.CDTOCTrackDataDescriptor trk in oldToc.Value.TrackDescriptors.OrderBy(t => t.TrackNumber)
.Where(trk => trk.ADR is 1 or 4))
{
switch(trk.TrackNumber)
{
trackList.Add(new Track
{
Sequence = trk.TrackNumber,
Session = 1,
Type = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data
: TrackType.Audio,
StartSector = trk.TrackStartAddress,
BytesPerSector = (int)sectorSize,
RawBytesPerSector = (int)sectorSize
});
case >= 0x01 and <= 0x63:
trackList.Add(new Track
{
Sequence = trk.TrackNumber,
Session = 1,
Type = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
? TrackType.Data
: TrackType.Audio,
StartSector = trk.TrackStartAddress,
BytesPerSector = (int)sectorSize,
RawBytesPerSector = (int)sectorSize
});
trackFlags?.Add(trk.TrackNumber, trk.CONTROL);
}
else if(trk.TrackNumber == 0xAA)
{
leadoutTrackType =
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data
: TrackType.Audio;
trackFlags?.Add(trk.TrackNumber, trk.CONTROL);
lastSector = trk.TrackStartAddress - 1;
break;
case 0xAA:
leadoutTrackType =
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental
? TrackType.Data
: TrackType.Audio;
lastSector = trk.TrackStartAddress - 1;
break;
}
}
}
}
if(trackList.Count == 0)
{
updateStatus?.Invoke("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");
updateStatus?.Invoke(Localization.Core.No_tracks_found_adding_a_single_track_from_zero_to_Lead_Out);
dumpLog?.WriteLine(Localization.Core.No_tracks_found_adding_a_single_track_from_zero_to_Lead_Out);
trackList.Add(new Track
{
@@ -235,8 +252,7 @@ partial class Dump
trackFlags?.Add(1, (byte)(leadoutTrackType == TrackType.Audio ? 0 : 4));
}
if(lastSector != 0)
return trackList.ToArray();
if(lastSector != 0) return trackList.ToArray();
sense = dev.ReadCapacity16(out cmdBuf, out _, dev.Timeout, out _);
@@ -252,27 +268,24 @@ partial class Dump
{
sense = dev.ReadCapacity(out cmdBuf, out _, dev.Timeout, out _);
if(!sense)
lastSector = ((cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + cmdBuf[3]) & 0xFFFFFFFF;
if(!sense) lastSector = (cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + cmdBuf[3] & 0xFFFFFFFF;
}
if(lastSector > 0)
return trackList.ToArray();
if(lastSector > 0) return trackList.ToArray();
if(!force)
{
stoppingErrorMessage?.
Invoke("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors...");
stoppingErrorMessage?.Invoke(Localization.Core
.Could_not_find_Lead_Out_if_you_want_to_continue_use_force_option);
dumpLog?.WriteLine("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors...");
dumpLog?.WriteLine(Localization.Core.Could_not_find_Lead_Out_if_you_want_to_continue_use_force_option);
return null;
}
updateStatus?.
Invoke("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before...");
updateStatus?.Invoke(Localization.Core.WARNING_Could_not_find_Lead_Out_start_will_try_to_read_up_to);
dumpLog?.WriteLine("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before...");
dumpLog?.WriteLine(Localization.Core.WARNING_Could_not_find_Lead_Out_start_will_try_to_read_up_to);
lastSector = 360000;
return trackList.ToArray();

View File

@@ -27,26 +27,29 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.Checksums;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Logging;
using Aaru.Decoders.CD;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
using Schemas;
using Humanizer;
using Humanizer.Localisation;
using Track = Aaru.CommonTypes.Structs.Track;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -74,15 +77,13 @@ partial class Dump
/// <param name="mcn">Disc media catalogue number</param>
/// <param name="subchannelExtents">List of subchannels not yet dumped correctly</param>
/// <param name="smallestPregapLbaPerTrack">List of smallest pregap relative address per track</param>
void TrimCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry, ExtentsULong extents,
void TrimCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardware currentTry, ExtentsULong extents,
bool newTrim, int offsetBytes, bool read6, bool read10, bool read12, bool read16, bool readcd,
int sectorsForOffset, uint subSize, MmcSubchannel supportedSubchannel, bool supportsLongSectors,
ref double totalDuration, SubchannelLog subLog, MmcSubchannel desiredSubchannel, Track[] tracks,
Dictionary<byte, string> isrcs, ref string mcn, HashSet<int> subchannelExtents,
Dictionary<byte, int> smallestPregapLbaPerTrack)
{
DateTime start;
DateTime end;
var sense = true; // Sense indicator
byte[] cmdBuf = null; // Data buffer
double cmdDuration = 0; // Command execution time
@@ -91,36 +92,20 @@ partial class Dump
byte[] senseBuf = null;
var outputOptical = _outputPlugin as IWritableOpticalImage;
switch(supportedSubchannel)
{
case MmcSubchannel.None:
supportedPlextorSubchannel = PlextorSubchannel.None;
supportedPlextorSubchannel = supportedSubchannel switch
{
MmcSubchannel.None => PlextorSubchannel.None,
MmcSubchannel.Raw => PlextorSubchannel.Pack,
MmcSubchannel.Q16 => PlextorSubchannel.Q16,
_ => PlextorSubchannel.None
};
break;
case MmcSubchannel.Raw:
supportedPlextorSubchannel = PlextorSubchannel.Pack;
if(_resume.BadBlocks.Count <= 0 || _aborted || !_trim || !newTrim) return;
break;
case MmcSubchannel.Q16:
supportedPlextorSubchannel = PlextorSubchannel.Q16;
break;
default:
supportedPlextorSubchannel = PlextorSubchannel.None;
break;
}
if(_resume.BadBlocks.Count <= 0 ||
_aborted ||
!_trim ||
!newTrim)
return;
start = DateTime.UtcNow;
UpdateStatus?.Invoke("Trimming skipped sectors");
_dumpLog.WriteLine("Trimming skipped sectors");
UpdateStatus?.Invoke(Localization.Core.Trimming_skipped_sectors);
_dumpLog.WriteLine(Localization.Core.Trimming_skipped_sectors);
InitProgress?.Invoke();
_trimStopwatch.Restart();
trimStart:
ulong[] tmpArray = _resume.BadBlocks.ToArray();
@@ -132,51 +117,85 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke($"Trimming sector {badSector}");
PulseProgress?.Invoke(string.Format(Localization.Core.Trimming_sector_0, badSector));
Track track = tracks.OrderBy(t => t.StartSector).LastOrDefault(t => badSector >= t.StartSector);
byte sectorsToTrim = 1;
var badSectorToRead = (uint)badSector;
if(_fixOffset &&
audioExtents.Contains(badSector) &&
offsetBytes != 0)
if(_fixOffset && audioExtents.Contains(badSector) && offsetBytes != 0)
{
if(offsetBytes > 0)
badSectorToRead -= (uint)sectorsForOffset;
if(offsetBytes < 0)
{
if(badSectorToRead == 0)
badSectorToRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1
else
badSectorToRead -= (uint)sectorsForOffset;
}
sectorsToTrim = (byte)(sectorsForOffset + 1);
sectorsToTrim += (byte)sectorsForOffset;
}
if(_supportsPlextorD8 && audioExtents.Contains(badSector))
sense = ReadPlextorWithSubchannel(out cmdBuf, out senseBuf, badSectorToRead, blockSize, sectorsToTrim,
supportedPlextorSubchannel, out cmdDuration);
{
sense = ReadPlextorWithSubchannel(out cmdBuf,
out senseBuf,
badSectorToRead,
blockSize,
sectorsToTrim,
supportedPlextorSubchannel,
out cmdDuration);
}
else if(readcd)
{
if(audioExtents.Contains(badSector))
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToRead, blockSize, sectorsToTrim,
MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false,
MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
badSectorToRead,
blockSize,
sectorsToTrim,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
if(sense)
{
DecodedSense? decSense = Sense.Decode(senseBuf);
// Try to workaround firmware
if(decSense?.ASC == 0x11 && decSense?.ASCQ == 0x05 ||
decSense?.ASC == 0x64)
if(decSense is { ASC: 0x11, ASCQ: 0x05 } || decSense?.ASC == 0x64)
{
sense = _dev.ReadCd(out cmdBuf, out _, badSectorToRead, blockSize, sectorsToTrim,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders,
true, true, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
sense = _dev.ReadCd(out cmdBuf,
out _,
badSectorToRead,
blockSize,
sectorsToTrim,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out double cmdDuration2);
cmdDuration += cmdDuration2;
@@ -185,23 +204,121 @@ partial class Dump
}
else
{
sense = _dev.ReadCd(out cmdBuf, out senseBuf, badSectorToRead, blockSize, sectorsToTrim,
MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
true, MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);
sense = _dev.ReadCd(out cmdBuf,
out senseBuf,
badSectorToRead,
blockSize,
sectorsToTrim,
MmcSectorTypes.AllTypes,
false,
false,
true,
MmcHeaderCodes.AllHeaders,
true,
true,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out cmdDuration);
if(sense)
{
DecodedSense? decSense = Sense.Decode(senseBuf);
// Try to workaround firmware
if(decSense?.ASC == 0x64)
if(decSense is { ASC: 0x11, ASCQ: 0x05 } || decSense?.ASC == 0x64)
{
sense = _dev.ReadCd(out cmdBuf, out _, badSectorToRead, blockSize, sectorsToTrim,
MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true,
false, MmcErrorField.None, supportedSubchannel, _dev.Timeout,
byte scrambledSectorsToTrim = sectorsToTrim;
uint scrambledBadSectorToRead = badSectorToRead;
// Contrary to normal read, this must always be offset fixed, because it's data not audio
if(offsetBytes != 0)
{
if(offsetBytes < 0)
{
if(scrambledBadSectorToRead == 0)
scrambledBadSectorToRead = uint.MaxValue - (uint)(sectorsForOffset - 1); // -1
else
scrambledBadSectorToRead -= (uint)sectorsForOffset;
}
scrambledSectorsToTrim += (byte)sectorsForOffset;
}
sense = _dev.ReadCd(out cmdBuf,
out _,
scrambledBadSectorToRead,
blockSize,
scrambledSectorsToTrim,
MmcSectorTypes.Cdda,
false,
false,
false,
MmcHeaderCodes.None,
true,
false,
MmcErrorField.None,
supportedSubchannel,
_dev.Timeout,
out double cmdDuration2);
cmdDuration += cmdDuration2;
if(!sense)
{
uint scrambledBlocksToRead = scrambledSectorsToTrim;
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref scrambledBlocksToRead,
subSize,
ref cmdBuf,
blockSize,
false);
// Descramble
cmdBuf = Sector.Scramble(cmdBuf);
// Check valid sector
CdChecksums.CheckCdSector(cmdBuf,
out bool? correctEccP,
out bool? correctEccQ,
out bool? correctEdc);
// Check mode, set sense if EDC/ECC validity is not correct
switch(cmdBuf[15] & 0x03)
{
case 0:
for(var c = 16; c < 2352; c++)
{
if(cmdBuf[c] == 0x00) continue;
sense = true;
break;
}
break;
case 1:
sense = correctEdc != true || correctEccP != true || correctEccQ != true;
break;
case 2:
if((cmdBuf[18] & 0x20) != 0x20)
{
if(correctEccP != true) sense = true;
if(correctEccQ != true) sense = true;
}
if(correctEdc != true) sense = true;
break;
}
}
}
}
}
@@ -209,17 +326,64 @@ partial class Dump
totalDuration += cmdDuration;
}
else if(read16)
sense = _dev.Read16(out cmdBuf, out senseBuf, 0, false, true, false, badSectorToRead, blockSize, 0,
sectorsToTrim, false, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read16(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
badSectorToRead,
blockSize,
0,
sectorsToTrim,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read12)
sense = _dev.Read12(out cmdBuf, out senseBuf, 0, false, true, false, false, badSectorToRead, blockSize,
0, sectorsToTrim, false, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read12(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
badSectorToRead,
blockSize,
0,
sectorsToTrim,
false,
_dev.Timeout,
out cmdDuration);
}
else if(read10)
sense = _dev.Read10(out cmdBuf, out senseBuf, 0, false, true, false, false, badSectorToRead, blockSize,
0, sectorsToTrim, _dev.Timeout, out cmdDuration);
{
sense = _dev.Read10(out cmdBuf,
out senseBuf,
0,
false,
true,
false,
false,
badSectorToRead,
blockSize,
0,
sectorsToTrim,
_dev.Timeout,
out cmdDuration);
}
else if(read6)
sense = _dev.Read6(out cmdBuf, out senseBuf, badSectorToRead, blockSize, sectorsToTrim, _dev.Timeout,
{
sense = _dev.Read6(out cmdBuf,
out senseBuf,
badSectorToRead,
blockSize,
sectorsToTrim,
_dev.Timeout,
out cmdDuration);
}
totalDuration += cmdDuration;
@@ -230,30 +394,35 @@ partial class Dump
continue;
}
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
_mediaGraph?.PaintSectorGood(badSector);
}
// Because one block has been partially used to fix the offset
if(_fixOffset &&
audioExtents.Contains(badSector) &&
offsetBytes != 0)
if(_fixOffset && audioExtents.Contains(badSector) && offsetBytes != 0)
{
uint blocksToRead = sectorsToTrim;
FixOffsetData(offsetBytes, sectorSize, sectorsForOffset, supportedSubchannel, ref blocksToRead, subSize,
ref cmdBuf, blockSize, false);
FixOffsetData(offsetBytes,
sectorSize,
sectorsForOffset,
supportedSubchannel,
ref blocksToRead,
subSize,
ref cmdBuf,
blockSize,
false);
}
if(supportedSubchannel != MmcSubchannel.None)
{
var data = new byte[sectorSize];
var sub = new byte[subSize];
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
Array.Copy(cmdBuf, 0, data, 0, sectorSize);
Array.Copy(cmdBuf, sectorSize, sub, 0, subSize);
if(supportsLongSectors)
outputOptical.WriteSectorLong(data, badSector);
@@ -262,23 +431,33 @@ partial class Dump
ulong trkStartBefore = track.StartSector;
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel, desiredSubchannel,
sub, badSector, 1, subLog, isrcs,
(byte)track.Sequence, ref mcn, tracks,
bool indexesChanged = Media.CompactDisc.WriteSubchannelToImage(supportedSubchannel,
desiredSubchannel,
sub,
badSector,
1,
subLog,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition, outputOptical,
_fixSubchannel, _fixSubchannelCrc,
_dumpLog, UpdateStatus,
smallestPregapLbaPerTrack, true, out _);
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
_dumpLog,
UpdateStatus,
smallestPregapLbaPerTrack,
true,
out _);
// Set tracks and go back
if(!indexesChanged)
continue;
if(!indexesChanged) continue;
outputOptical.SetTracks(tracks.ToList());
if(track.StartSector != trkStartBefore &&
!_resume.BadBlocks.Contains(track.StartSector))
if(track.StartSector != trkStartBefore && !_resume.BadBlocks.Contains(track.StartSector))
{
_resume.BadBlocks.Add(track.StartSector);
@@ -296,9 +475,13 @@ partial class Dump
outputOptical.WriteSector(Sector.GetUserData(cmdBuf), badSector);
}
_trimStopwatch.Stop();
EndProgress?.Invoke();
end = DateTime.UtcNow;
UpdateStatus?.Invoke($"Trimming finished in {(end - start).TotalSeconds} seconds.");
_dumpLog.WriteLine("Trimming finished in {0} seconds.", (end - start).TotalSeconds);
UpdateStatus?.Invoke(string.Format(Localization.Core.Trimming_finished_in_0,
_trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine(string.Format(Localization.Core.Trimming_finished_in_0,
_trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
}
}

View File

@@ -27,27 +27,29 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2020-2022 Rebecca Wallander
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2020-2024 Rebecca Wallander
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Text.Json;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
using Aaru.Core.Logging;
using Aaru.Database;
using Aaru.Devices;
using Aaru.Settings;
using Schemas;
using Humanizer;
using File = System.IO.File;
namespace Aaru.Core.Devices.Dumping;
/// <summary>Subchannel requested to dump</summary>
public enum DumpSubchannel
@@ -66,49 +68,61 @@ public enum DumpSubchannel
public partial class Dump
{
readonly bool _debug;
readonly Device _dev;
readonly string _devicePath;
readonly bool _doResume;
readonly DumpLog _dumpLog;
readonly bool _dumpRaw;
readonly Encoding _encoding;
readonly ErrorLog _errorLog;
readonly bool _fixSubchannel;
readonly bool _fixSubchannelCrc;
readonly bool _fixSubchannelPosition;
readonly bool _force;
readonly Dictionary<string, string> _formatOptions;
readonly bool _generateSubchannels;
readonly bool _metadata;
readonly string _outputPath;
readonly IBaseWritableImage _outputPlugin;
readonly string _outputPrefix;
readonly bool _persistent;
readonly CICMMetadataType _preSidecar;
readonly bool _private;
readonly ushort _retryPasses;
readonly bool _retrySubchannel;
readonly bool _stopOnError;
readonly bool _storeEncrypted;
readonly DumpSubchannel _subchannel;
readonly bool _titleKeys;
readonly bool _trim;
bool _aborted;
AaruContext _ctx; // Main database context
Database.Models.Device _dbDev; // Device database entry
bool _dumpFirstTrackPregap;
bool _fixOffset;
readonly uint _ignoreCdrRunOuts;
uint _maximumReadable; // Maximum number of sectors drive can read at once
Resume _resume;
Sidecar _sidecarClass;
uint _skip;
bool _skipCdireadyHole;
int _speed;
int _speedMultiplier;
bool _supportsPlextorD8;
bool _useBufferedReads;
const string PREGAP_MODULE_NAME = "Pregap calculator";
const string MODULE_NAME = "Media dumping";
static readonly TimeSpan _oneSecond = 1.Seconds();
readonly bool _createGraph;
readonly bool _debug;
readonly Device _dev;
readonly string _devicePath;
readonly uint _dimensions;
readonly bool _doResume;
readonly DumpLog _dumpLog;
readonly bool _dumpRaw;
readonly Stopwatch _dumpStopwatch;
readonly Encoding _encoding;
readonly ErrorLog _errorLog;
readonly bool _fixSubchannel;
readonly bool _fixSubchannelCrc;
readonly bool _fixSubchannelPosition;
readonly bool _force;
readonly Dictionary<string, string> _formatOptions;
readonly bool _generateSubchannels;
readonly uint _ignoreCdrRunOuts;
readonly Stopwatch _imageCloseStopwatch;
readonly bool _metadata;
readonly string _outputPath;
readonly IBaseWritableImage _outputPlugin;
readonly string _outputPrefix;
readonly bool _persistent;
readonly Metadata _preSidecar;
readonly bool _private;
readonly ushort _retryPasses;
readonly bool _retrySubchannel;
readonly Stopwatch _sidecarStopwatch;
readonly Stopwatch _speedStopwatch;
readonly bool _stopOnError;
readonly bool _storeEncrypted;
readonly DumpSubchannel _subchannel;
readonly bool _titleKeys;
readonly bool _trim;
readonly Stopwatch _trimStopwatch;
readonly Stopwatch _writeStopwatch;
bool _aborted;
AaruContext _ctx; // Main database context
Database.Models.Device _dbDev; // Device database entry
bool _dumpFirstTrackPregap;
bool _fixOffset;
uint _maximumReadable; // Maximum number of sectors drive can read at once
IMediaGraph _mediaGraph;
Resume _resume;
Sidecar _sidecarClass;
uint _skip;
bool _skipCdireadyHole;
int _speed;
int _speedMultiplier;
bool _supportsPlextorD8;
bool _useBufferedReads;
/// <summary>Initializes dumpers</summary>
/// <param name="doResume">Should resume?</param>
@@ -151,14 +165,16 @@ public partial class Dump
/// <param name="storeEncrypted">Store encrypted data as is</param>
/// <param name="titleKeys">Dump DVD CSS title keys</param>
/// <param name="ignoreCdrRunOuts">How many CD-R(W) run end sectors to ignore and regenerate</param>
public Dump(bool doResume, Device dev, string devicePath, IBaseWritableImage outputPlugin, ushort retryPasses,
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, DumpLog dumpLog,
/// <param name="createGraph">If set to <c>true</c> creates a graph of the dump.</param>
/// <param name="dimensions">Dimensions of graph in pixels for a square</param>
public Dump(bool doResume, Device dev, string devicePath, IBaseWritableImage outputPlugin, ushort retryPasses,
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, DumpLog dumpLog,
Encoding encoding, string outputPrefix, string outputPath, Dictionary<string, string> formatOptions,
CICMMetadataType preSidecar, uint skip, bool metadata, bool trim, bool dumpFirstTrackPregap,
bool fixOffset, bool debug, DumpSubchannel subchannel, int speed, bool @private,
bool fixSubchannelPosition, bool retrySubchannel, bool fixSubchannel, bool fixSubchannelCrc,
bool skipCdireadyHole, ErrorLog errorLog, bool generateSubchannels, uint maximumReadable,
bool useBufferedReads, bool storeEncrypted, bool titleKeys, uint ignoreCdrRunOuts)
Metadata preSidecar, uint skip, bool metadata, bool trim, bool dumpFirstTrackPregap, bool fixOffset,
bool debug, DumpSubchannel subchannel, int speed, bool @private, bool fixSubchannelPosition,
bool retrySubchannel, bool fixSubchannel, bool fixSubchannelCrc, bool skipCdireadyHole,
ErrorLog errorLog, bool generateSubchannels, uint maximumReadable, bool useBufferedReads,
bool storeEncrypted, bool titleKeys, uint ignoreCdrRunOuts, bool createGraph, uint dimensions)
{
_doResume = doResume;
_dev = dev;
@@ -199,32 +215,39 @@ public partial class Dump
_storeEncrypted = storeEncrypted;
_titleKeys = titleKeys;
_ignoreCdrRunOuts = ignoreCdrRunOuts;
_createGraph = createGraph;
_dimensions = dimensions;
_dumpStopwatch = new Stopwatch();
_sidecarStopwatch = new Stopwatch();
_speedStopwatch = new Stopwatch();
_trimStopwatch = new Stopwatch();
_writeStopwatch = new Stopwatch();
_imageCloseStopwatch = new Stopwatch();
}
/// <summary>Starts dumping with the established fields and autodetecting the device type</summary>
public void Start()
{
// Open main database
_ctx = AaruContext.Create(Settings.MainDbPath);
_ctx = AaruContext.Create(Settings.Settings.MainDbPath);
// Search for device in main database
_dbDev = _ctx.Devices.FirstOrDefault(d => d.Manufacturer == _dev.Manufacturer && d.Model == _dev.Model &&
_dbDev = _ctx.Devices.FirstOrDefault(d => d.Manufacturer == _dev.Manufacturer &&
d.Model == _dev.Model &&
d.Revision == _dev.FirmwareRevision);
if(_dbDev is null)
{
_dumpLog.WriteLine("Device not in database, please create a device report and attach it to a Github issue.");
_dumpLog.WriteLine(Localization.Core.Device_not_in_database);
UpdateStatus?.
Invoke("Device not in database, please create a device report and attach it to a Github issue.");
UpdateStatus?.Invoke(Localization.Core.Device_not_in_database);
}
else
{
_dumpLog.WriteLine($"Device in database since {_dbDev.LastSynchronized}.");
UpdateStatus?.Invoke($"Device in database since {_dbDev.LastSynchronized}.");
_dumpLog.WriteLine(string.Format(Localization.Core.Device_in_database_since_0, _dbDev.LastSynchronized));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_in_database_since_0, _dbDev.LastSynchronized));
if(_dbDev.OptimalMultipleSectorsRead > 0)
_maximumReadable = (uint)_dbDev.OptimalMultipleSectorsRead;
if(_dbDev.OptimalMultipleSectorsRead > 0) _maximumReadable = (uint)_dbDev.OptimalMultipleSectorsRead;
}
switch(_dev.IsUsb)
@@ -259,9 +282,9 @@ public partial class Dump
break;
default:
_dumpLog.WriteLine("Unknown device type.");
_dumpLog.WriteLine(Localization.Core.Unknown_device_type);
_dumpLog.Close();
StoppingErrorMessage?.Invoke("Unknown device type.");
StoppingErrorMessage?.Invoke(Localization.Core.Unknown_device_type);
return;
}
@@ -272,19 +295,31 @@ public partial class Dump
_errorLog.Close();
_dumpLog.Close();
if(_resume == null ||
!_doResume)
return;
if(_resume == null || !_doResume) return;
_resume.LastWriteDate = DateTime.UtcNow;
_resume.BadBlocks.Sort();
if(File.Exists(_outputPrefix + ".resume.xml"))
File.Delete(_outputPrefix + ".resume.xml");
if(_createGraph && _mediaGraph is not null)
{
_mediaGraph?.PaintSectorsBad(_resume.BadBlocks);
_mediaGraph?.WriteTo($"{_outputPrefix}.graph.png");
}
if(File.Exists(_outputPrefix + ".resume.xml")) File.Delete(_outputPrefix + ".resume.xml");
if(File.Exists(_outputPrefix + ".resume.json")) File.Delete(_outputPrefix + ".resume.json");
var fs = new FileStream(_outputPrefix + ".resume.json", FileMode.Create, FileAccess.ReadWrite);
JsonSerializer.Serialize(fs,
new ResumeJson
{
Resume = _resume
},
typeof(ResumeJson),
ResumeJsonContext.Default);
var fs = new FileStream(_outputPrefix + ".resume.xml", FileMode.Create, FileAccess.ReadWrite);
var xs = new XmlSerializer(_resume.GetType());
xs.Serialize(fs, _resume);
fs.Close();
}
@@ -297,22 +332,31 @@ public partial class Dump
/// <summary>Event raised when the progress bar is not longer needed</summary>
public event EndProgressHandler EndProgress;
/// <summary>Event raised when a progress bar is needed</summary>
public event InitProgressHandler InitProgress;
/// <summary>Event raised to report status updates</summary>
public event UpdateStatusHandler UpdateStatus;
/// <summary>Event raised to report a non-fatal error</summary>
public event ErrorMessageHandler ErrorMessage;
/// <summary>Event raised to report a fatal error that stops the dumping operation and should call user's attention</summary>
public event ErrorMessageHandler StoppingErrorMessage;
/// <summary>Event raised to update the values of a determinate progress bar</summary>
public event UpdateProgressHandler UpdateProgress;
/// <summary>Event raised to update the status of an undeterminate progress bar</summary>
public event PulseProgressHandler PulseProgress;
/// <summary>Event raised when the progress bar is not longer needed</summary>
public event EndProgressHandler2 EndProgress2;
/// <summary>Event raised when a progress bar is needed</summary>
public event InitProgressHandler2 InitProgress2;
/// <summary>Event raised to update the values of a determinate progress bar</summary>
public event UpdateProgressHandler2 UpdateProgress2;
}

View File

@@ -27,66 +27,53 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Linq;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Graphics;
using Humanizer;
using Humanizer.Bytes;
using Humanizer.Localisation;
using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Core.Devices.Dumping;
public partial class Dump
{
static readonly byte[] _sfcExtension =
{
0x53, 0x46, 0x43
};
static readonly byte[] _genesisExtension =
{
0x42, 0x49, 0x4E
};
static readonly byte[] _smsExtension =
{
0x53, 0x4D, 0x53
};
static readonly byte[] _z64Extension =
{
0x5A, 0x36, 0x34
};
static readonly byte[] _gbExtension =
{
0x47, 0x42, 0x20
};
static readonly byte[] _gbcExtension =
{
0x47, 0x42, 0x43
};
static readonly byte[] _gbaExtension =
{
0x47, 0x42, 0x41
};
static readonly byte[] _sfcExtension = "SFC"u8.ToArray();
static readonly byte[] _genesisExtension = "BIN"u8.ToArray();
static readonly byte[] _smsExtension = "SMS"u8.ToArray();
static readonly byte[] _z64Extension = "Z64"u8.ToArray();
static readonly byte[] _gbExtension = "GB "u8.ToArray();
static readonly byte[] _gbcExtension = "GBC"u8.ToArray();
static readonly byte[] _gbaExtension = "GBA"u8.ToArray();
/// <summary>Dumps a game cartridge using a Retrode adapter</summary>
void Retrode()
{
bool sense = _dev.Read10(out byte[] buffer, out _, 0, false, true, false, false, 0, 512, 0, 1, _dev.Timeout,
bool sense = _dev.Read10(out byte[] buffer,
out _,
0,
false,
true,
false,
false,
0,
512,
0,
1,
_dev.Timeout,
out _);
if(sense)
{
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine(Localization.Core.Could_not_read);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
return;
}
@@ -98,8 +85,8 @@ public partial class Dump
// UMDs are stored inside a FAT16 volume
if(!tmp.SequenceEqual(_fatSignature))
{
_dumpLog.WriteLine("Retrode partition not recognized, not dumping...");
StoppingErrorMessage?.Invoke("Retrode partition not recognized, not dumping...");
_dumpLog.WriteLine(Localization.Core.Retrode_partition_not_recognized_not_dumping);
StoppingErrorMessage?.Invoke(Localization.Core.Retrode_partition_not_recognized_not_dumping);
return;
}
@@ -110,15 +97,15 @@ public partial class Dump
var rootSize = (ushort)(((buffer[0x12] << 8) + buffer[0x11]) * 32 / 512);
byte sectorsPerCluster = buffer[0x0D];
UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}...");
_dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
UpdateStatus?.Invoke(string.Format(Localization.Core.Reading_root_directory_in_sector_0, rootStart));
_dumpLog.WriteLine(Localization.Core.Reading_root_directory_in_sector_0, rootStart);
sense = _dev.Read10(out buffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, _dev.Timeout, out _);
if(sense)
{
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
_dumpLog.WriteLine(Localization.Core.Could_not_read);
return;
}
@@ -179,21 +166,17 @@ public partial class Dump
break;
}
if(tmp.SequenceEqual(_gbaExtension))
{
gbaFound = true;
if(!tmp.SequenceEqual(_gbaExtension)) continue;
break;
}
gbaFound = true;
break;
}
if(!sfcFound &&
!genesisFound &&
!smsFound &&
!n64Found)
if(!sfcFound && !genesisFound && !smsFound && !n64Found && !gbaFound && !gbFound && !gbcFound)
{
StoppingErrorMessage?.Invoke("No cartridge found, not dumping...");
_dumpLog.WriteLine("No cartridge found, not dumping...");
StoppingErrorMessage?.Invoke(Localization.Core.No_cartridge_found_not_dumping);
_dumpLog.WriteLine(Localization.Core.No_cartridge_found_not_dumping);
return;
}
@@ -220,22 +203,34 @@ public partial class Dump
double minSpeed = double.MaxValue;
byte[] senseBuf;
if(_outputPlugin is not IByteAddressableImage outputBai ||
!outputBai.SupportedMediaTypes.Contains(mediaType))
if(_outputPlugin is not IByteAddressableImage outputBai || !outputBai.SupportedMediaTypes.Contains(mediaType))
{
_dumpLog.WriteLine("The specified format does not support the inserted cartridge...");
StoppingErrorMessage?.Invoke("The specified format does not support the inserted cartridge...");
_dumpLog.WriteLine(Localization.Core.The_specified_format_does_not_support_the_inserted_cartridge);
StoppingErrorMessage?.Invoke(Localization.Core
.The_specified_format_does_not_support_the_inserted_cartridge);
return;
}
sense = _dev.Read10(out byte[] readBuffer, out _, 0, false, true, false, false, 0, 512, 0, 1, _dev.Timeout,
sense = _dev.Read10(out byte[] readBuffer,
out _,
0,
false,
true,
false,
false,
0,
512,
0,
1,
_dev.Timeout,
out _);
if(sense)
{
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine(Localization.Core.Could_not_read);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
return;
}
@@ -244,183 +239,243 @@ public partial class Dump
uint romSectors = romSize / 512;
uint romRemaining = romSize % 512;
if(romSize > 1073741824)
UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1073741824d:F3} GiB)");
else if(romSize > 1048576)
UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1048576d:F3} MiB)");
else if(romSize > 1024)
UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1024d:F3} KiB)");
else
UpdateStatus?.Invoke($"Cartridge has {romSize} bytes");
switch(romSize)
{
case > 1073741824:
UpdateStatus?.Invoke(string.Format(Localization.Core.Cartridge_has_0_bytes_1_GiB,
romSize,
romSize / 1073741824d));
UpdateStatus?.Invoke($"Media identified as {mediaType}.");
_dumpLog.WriteLine("Media identified as {0}.", mediaType);
break;
case > 1048576:
UpdateStatus?.Invoke(string.Format(Localization.Core.Cartridge_has_0_bytes_1_MiB,
romSize,
romSize / 1048576d));
break;
case > 1024:
UpdateStatus?.Invoke(string.Format(Localization.Core.Cartridge_has_0_bytes_1_KiB,
romSize,
romSize / 1024d));
break;
default:
UpdateStatus?.Invoke(string.Format(Localization.Core.Cartridge_has_0_bytes, romSize));
break;
}
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_identified_as_0, mediaType));
_dumpLog.WriteLine(Localization.Core.Media_identified_as_0, mediaType);
ErrorNumber ret = outputBai.Create(_outputPath, mediaType, _formatOptions, romSize);
// Cannot create image
if(ret != ErrorNumber.NoError)
{
_dumpLog.WriteLine("Error {0} creating output image, not continuing.", ret);
_dumpLog.WriteLine(Localization.Core.Error_0_creating_output_image_not_continuing, ret);
_dumpLog.WriteLine(outputBai.ErrorMessage);
StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine +
StoppingErrorMessage?.Invoke(Localization.Core.Error_creating_output_image_not_continuing +
Environment.NewLine +
outputBai.ErrorMessage);
return;
}
DateTime start = DateTime.UtcNow;
double imageWriteDuration = 0;
if(_createGraph) _mediaGraph = new BlockMap((int)_dimensions, (int)_dimensions, romSectors);
DateTime timeSpeedStart = DateTime.UtcNow;
ulong sectorSpeedStart = 0;
_dumpStopwatch.Restart();
double imageWriteDuration = 0;
_speedStopwatch.Restart();
ulong sectorSpeedStart = 0;
InitProgress?.Invoke();
for(ulong i = 0; i < romSectors; i += blocksToRead)
{
if(_aborted)
{
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(romSectors - i < blocksToRead)
blocksToRead = (uint)(romSectors - i);
if(romSectors - i < blocksToRead) blocksToRead = (uint)(romSectors - i);
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading byte {i * 512} of {romSize} ({currentSpeed:F3} MiB/sec.)", (long)i * 512,
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_byte_0_of_1_2,
i * 512,
romSize,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i * 512,
romSize);
sense = _dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(startSector + i),
512, 0, (ushort)blocksToRead, _dev.Timeout, out double cmdDuration);
_speedStopwatch.Start();
sense = _dev.Read10(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)(startSector + i),
512,
0,
(ushort)blocksToRead,
_dev.Timeout,
out double cmdDuration);
_speedStopwatch.Stop();
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
_writeStopwatch.Restart();
if(!sense && !_dev.Error)
{
DateTime writeStart = DateTime.Now;
outputBai.WriteBytes(readBuffer, 0, readBuffer.Length, out _);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
_mediaGraph.PaintSectorsGood(i, blocksToRead);
}
else
{
_errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
_dumpLog.WriteLine("Skipping {0} bytes from errored byte {1}.", _skip * 512, i * 512);
_dumpLog.WriteLine(Localization.Core.Skipping_0_bytes_from_errored_byte_1, _skip * 512, i * 512);
i += _skip - blocksToRead;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * 512 < 524288) continue;
currentSpeed = sectorSpeedStart * 512 / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
if(romRemaining > 0 &&
!_aborted)
_speedStopwatch.Stop();
if(romRemaining > 0 && !_aborted)
{
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading byte {romSectors * 512} of {romSize} ({currentSpeed:F3} MiB/sec.)",
(long)romSectors * 512, romSize);
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_byte_0_of_1_2,
romSectors * 512,
romSize,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)romSectors * 512,
romSize);
sense = _dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, romSectors, 512, 0, 1,
_dev.Timeout, out double cmdDuration);
sense = _dev.Read10(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
romSectors,
512,
0,
1,
_dev.Timeout,
out double cmdDuration);
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputBai.WriteBytes(readBuffer, 0, (int)romRemaining, out _);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
_writeStopwatch.Stop();
}
else
{
_errorLog?.WriteLine(romSectors, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
_dumpLog.WriteLine("Skipping {0} bytes from errored byte {1}.", _skip * 512, romSectors * 512);
_dumpLog.WriteLine(Localization.Core.Skipping_0_bytes_from_errored_byte_1,
_skip * 512,
romSectors * 512);
}
}
DateTime end = DateTime.UtcNow;
EndProgress?.Invoke();
UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average dump speed {512 * (double)(romSectors + 1) / 1024 / (totalDuration / 1000):F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(512 * (romSectors + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
UpdateStatus?.
Invoke($"Average write speed {512 * (double)(romSectors + 1) / 1024 / imageWriteDuration:F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(512 * (romSectors + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
_dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
_dumpLog.WriteLine(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
512 * (double)(romSectors + 1) / 1024 / (totalDuration / 1000));
_dumpLog.WriteLine(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(512 * (romSectors + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
_dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
512 * (double)(romSectors + 1) / 1024 / imageWriteDuration);
_dumpLog.WriteLine(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(512 * (romSectors + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
var metadata = new ImageInfo
var metadata = new CommonTypes.Structs.ImageInfo
{
Application = "Aaru",
ApplicationVersion = Version.GetVersion()
};
if(!outputBai.SetMetadata(metadata))
ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine +
if(!outputBai.SetImageInfo(metadata))
{
ErrorMessage?.Invoke(Localization.Core.Error_0_setting_metadata +
Environment.NewLine +
outputBai.ErrorMessage);
}
// TODO: Set dump hardware
//outputBAI.SetDumpHardware();
if(_preSidecar != null)
outputBai.SetCicmMetadata(_preSidecar);
if(_preSidecar != null) outputBai.SetMetadata(_preSidecar);
_dumpLog.WriteLine("Closing output file.");
UpdateStatus?.Invoke("Closing output file.");
DateTime closeStart = DateTime.Now;
_dumpLog.WriteLine(Localization.Core.Closing_output_file);
UpdateStatus?.Invoke(Localization.Core.Closing_output_file);
_imageCloseStopwatch.Restart();
outputBai.Close();
DateTime closeEnd = DateTime.Now;
_dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);
_imageCloseStopwatch.Stop();
_dumpLog.WriteLine(Localization.Core.Closed_in_0,
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second));
if(_aborted)
{
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
return;
}
@@ -433,17 +488,30 @@ public partial class Dump
*/
UpdateStatus?.Invoke("");
UpdateStatus?.
Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing).");
UpdateStatus?.Invoke(string.Format(Localization.Core
.Took_a_total_of_0_1_processing_commands_2_checksumming_3_writing_4_closing,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second),
totalDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
totalChkDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
imageWriteDuration.Seconds().Humanize(minUnit: TimeUnit.Second),
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average speed: {512 * (double)(romSectors + 1) / 1048576 / (totalDuration / 1000):F3} MiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_speed_0,
ByteSize.FromBytes(512 * (romSectors + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
if(maxSpeed > 0)
UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec.");
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Fastest_speed_burst_0,
ByteSize.FromMegabytes(maxSpeed).Per(_oneSecond).Humanize()));
}
if(minSpeed is > 0 and < double.MaxValue)
UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec.");
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Slowest_speed_burst_0,
ByteSize.FromMegabytes(minSpeed).Per(_oneSecond).Humanize()));
}
UpdateStatus?.Invoke("");

File diff suppressed because it is too large Load Diff

View File

@@ -27,22 +27,22 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using System.Text.Json;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
using Schemas;
using MediaType = Aaru.CommonTypes.MediaType;
using Humanizer;
using Humanizer.Bytes;
using Humanizer.Localisation;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -55,18 +55,17 @@ partial class Dump
/// <param name="sessions">Disc sessions</param>
/// <param name="totalChkDuration">Total time spent doing checksums</param>
/// <param name="discOffset">Disc write offset</param>
void WriteOpticalSidecar(uint blockSize, ulong blocks, MediaType mediaType, LayersType layers,
void WriteOpticalSidecar(uint blockSize, ulong blocks, MediaType mediaType, Layers layers,
Dictionary<MediaTagType, byte[]> mediaTags, int sessions, out double totalChkDuration,
int? discOffset)
{
_dumpLog.WriteLine("Creating sidecar.");
var filters = new FiltersList();
IFilter filter = filters.GetFilter(_outputPath);
_dumpLog.WriteLine(Localization.Core.Creating_sidecar);
IFilter filter = PluginRegister.Singleton.GetFilter(_outputPath);
totalChkDuration = 0;
if(ImageFormat.Detect(filter) is not IMediaImage inputPlugin)
{
StoppingErrorMessage?.Invoke("Could not detect image format.");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_detect_image_format);
return;
}
@@ -75,12 +74,12 @@ partial class Dump
if(opened != ErrorNumber.NoError)
{
StoppingErrorMessage?.Invoke($"Error {opened} opening created image.");
StoppingErrorMessage?.Invoke(string.Format(Localization.Core.Error_0_opening_created_image, opened));
return;
}
DateTime chkStart = DateTime.UtcNow;
_sidecarStopwatch.Restart();
// ReSharper disable once UseObjectOrCollectionInitializer
_sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding);
@@ -91,66 +90,79 @@ partial class Dump
_sidecarClass.UpdateProgressEvent2 += UpdateProgress2;
_sidecarClass.EndProgressEvent2 += EndProgress2;
_sidecarClass.UpdateStatusEvent += UpdateStatus;
CICMMetadataType sidecar = _sidecarClass.Create();
DateTime end = DateTime.UtcNow;
Metadata sidecar = _sidecarClass.Create();
_sidecarStopwatch.Stop();
if(_aborted)
return;
if(_aborted) return;
totalChkDuration = (end - chkStart).TotalMilliseconds;
_dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);
totalChkDuration = _sidecarStopwatch.Elapsed.TotalMilliseconds;
_dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));
_dumpLog.WriteLine(Localization.Core.Sidecar_created_in_0,
_sidecarStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second));
_dumpLog.WriteLine(Localization.Core.Average_checksum_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalChkDuration.Milliseconds())
.Humanize());
if(_preSidecar != null)
{
_preSidecar.OpticalDisc = sidecar.OpticalDisc;
sidecar = _preSidecar;
_preSidecar.OpticalDiscs = sidecar.OpticalDiscs;
sidecar = _preSidecar;
}
List<(ulong start, string type)> filesystems = new();
List<(ulong start, string type)> filesystems = [];
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
if(sidecar.OpticalDiscs[0].Track != null)
{
filesystems.AddRange(from xmlTrack in sidecar.OpticalDiscs[0].Track
where xmlTrack.FileSystemInformation != null
from partition in xmlTrack.FileSystemInformation
where partition.FileSystems != null
from fileSystem in partition.FileSystems
select (partition.StartSector, fileSystem.Type));
if(filesystems.Count > 0)
foreach(var filesystem in filesystems.Select(o => new
{
o.start,
o.type
}).Distinct())
_dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(mediaType);
(string type, string subType) discType = CommonTypes.Metadata.MediaType.MediaTypeToString(mediaType);
sidecar.OpticalDisc[0].DiscType = discType.type;
sidecar.OpticalDisc[0].DiscSubType = discType.subType;
sidecar.OpticalDisc[0].DumpHardwareArray = _resume.Tries.ToArray();
sidecar.OpticalDisc[0].Sessions = (uint)sessions;
sidecar.OpticalDisc[0].Layers = layers;
if(discOffset.HasValue)
{
sidecar.OpticalDisc[0].Offset = (int)(discOffset / 4);
sidecar.OpticalDisc[0].OffsetSpecified = true;
}
if(filesystems.Count > 0)
{
foreach(var filesystem in filesystems.Select(o => new
{
o.start,
o.type
})
.Distinct())
_dumpLog.WriteLine(Localization.Core.Found_filesystem_0_at_sector_1, filesystem.type, filesystem.start);
}
sidecar.OpticalDiscs[0].Dimensions = Dimensions.FromMediaType(mediaType);
(string type, string subType) discType = CommonTypes.Metadata.MediaType.MediaTypeToString(mediaType);
sidecar.OpticalDiscs[0].DiscType = discType.type;
sidecar.OpticalDiscs[0].DiscSubType = discType.subType;
sidecar.OpticalDiscs[0].DumpHardware = _resume.Tries;
sidecar.OpticalDiscs[0].Sessions = (uint)sessions;
sidecar.OpticalDiscs[0].Layers = layers;
if(discOffset.HasValue) sidecar.OpticalDiscs[0].Offset = (int)(discOffset / 4);
if(mediaTags != null)
foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags.Where(tag => _outputPlugin.SupportedMediaTags.
Contains(tag.Key)))
{
foreach(KeyValuePair<MediaTagType, byte[]> tag in
mediaTags.Where(tag => _outputPlugin.SupportedMediaTags.Contains(tag.Key)))
AddMediaTagToSidecar(_outputPath, tag.Key, tag.Value, ref sidecar);
}
UpdateStatus?.Invoke("Writing metadata sidecar");
UpdateStatus?.Invoke(Localization.Core.Writing_metadata_sidecar);
var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create);
var jsonFs = new FileStream(_outputPrefix + ".metadata.json", FileMode.Create);
var xmlSer = new XmlSerializer(typeof(CICMMetadataType));
xmlSer.Serialize(xmlFs, sidecar);
xmlFs.Close();
JsonSerializer.Serialize(jsonFs,
new MetadataJson
{
AaruMetadata = sidecar
},
typeof(MetadataJson),
MetadataJsonContext.Default);
jsonFs.Close();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable InconsistentNaming
@@ -37,5 +37,5 @@ namespace Aaru.Core.Devices.Dumping;
public partial class Dump
{
/// <summary>Dumps an NVMe device</summary>
public void NVMe() => StoppingErrorMessage?.Invoke("NVMe devices not yet supported.");
public void NVMe() => StoppingErrorMessage?.Invoke(Localization.Core.NVMe_devices_not_yet_supported);
}

View File

@@ -28,31 +28,32 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using System.Text.Json;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
using Aaru.CommonTypes.Structs;
using Aaru.Console;
using Aaru.Core.Graphics;
using Aaru.Core.Logging;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
using Schemas;
using MediaType = Aaru.CommonTypes.MediaType;
using Humanizer;
using Humanizer.Bytes;
using Humanizer.Localisation;
using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Core.Devices.Dumping;
public partial class Dump
{
[SuppressMessage("ReSharper", "JoinDeclarationAndInitializer")]
@@ -65,15 +66,13 @@ public partial class Dump
double maxSpeed = double.MinValue;
double minSpeed = double.MaxValue;
uint blocksToRead = 64;
DateTime start;
DateTime end;
MediaType dskType;
bool sense;
byte[] senseBuf;
if(_outputPlugin is not IWritableImage outputFormat)
{
StoppingErrorMessage?.Invoke("Image is not writable, aborting...");
StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting);
return;
}
@@ -82,8 +81,8 @@ public partial class Dump
if(sense)
{
_dumpLog.WriteLine("Could not detect capacity...");
StoppingErrorMessage?.Invoke("Could not detect capacity...");
_dumpLog.WriteLine(Localization.Core.Could_not_detect_capacity);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_detect_capacity);
return;
}
@@ -92,93 +91,117 @@ public partial class Dump
blocks++;
ulong totalSize = blocks * (ulong)blockSize;
if(totalSize > 1099511627776)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1099511627776d:F3} TiB)");
else if(totalSize > 1073741824)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1073741824d:F3} GiB)");
else if(totalSize > 1048576)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1048576d:F3} MiB)");
else if(totalSize > 1024)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1024d:F3} KiB)");
else
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize} bytes)");
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_has_0_blocks_of_1_bytes_each_for_a_total_of_2,
blocks,
blockSize,
ByteSize.FromBytes(blocks * blockSize).ToString("0.000")));
if(blocks == 0)
{
_dumpLog.WriteLine("ERROR: Unable to read medium or empty medium present...");
StoppingErrorMessage?.Invoke("Unable to read medium or empty medium present...");
_dumpLog.WriteLine(Localization.Core.ERROR_Unable_to_read_medium_or_empty_medium_present);
StoppingErrorMessage?.Invoke(Localization.Core.Unable_to_read_medium_or_empty_medium_present);
return;
}
UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * blockSize} bytes).");
UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time.");
UpdateStatus?.Invoke($"Device reports {blockSize} bytes per logical block.");
UpdateStatus?.Invoke($"SCSI device type: {_dev.ScsiType}.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_blocks_1_bytes,
blocks,
blocks * blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_can_read_0_blocks_at_a_time, blocksToRead));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_bytes_per_logical_block, blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.SCSI_device_type_0, _dev.ScsiType));
if(blocks > 262144)
{
dskType = MediaType.MemoryStickProDuo;
_dumpLog.WriteLine("Media detected as MemoryStick Pro Duo...");
UpdateStatus?.Invoke("Media detected as MemoryStick Pro Duo...");
_dumpLog.WriteLine(Localization.Core.Media_detected_as_MemoryStick_Pro_Duo);
UpdateStatus?.Invoke(Localization.Core.Media_detected_as_MemoryStick_Pro_Duo);
}
else
{
dskType = MediaType.MemoryStickDuo;
_dumpLog.WriteLine("Media detected as MemoryStick Duo...");
UpdateStatus?.Invoke("Media detected as MemoryStick Duo...");
_dumpLog.WriteLine(Localization.Core.Media_detected_as_MemoryStick_Duo);
UpdateStatus?.Invoke(Localization.Core.Media_detected_as_MemoryStick_Duo);
}
bool ret;
var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private);
var ibgLog = new IbgLog(_outputPrefix + ".ibg", sbcProfile);
var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin",
_dev,
blocks,
blockSize,
blocksToRead,
_private,
_dimensions);
var ibgLog = new IbgLog(_outputPrefix + ".ibg", sbcProfile);
ret = outputFormat.Create(_outputPath, dskType, _formatOptions, blocks, blockSize);
// Cannot create image
if(!ret)
{
_dumpLog.WriteLine("Error creating output image, not continuing.");
_dumpLog.WriteLine(Localization.Core.Error_creating_output_image_not_continuing);
_dumpLog.WriteLine(outputFormat.ErrorMessage);
StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine +
StoppingErrorMessage?.Invoke(Localization.Core.Error_creating_output_image_not_continuing +
Environment.NewLine +
outputFormat.ErrorMessage);
return;
}
start = DateTime.UtcNow;
_dumpStopwatch.Restart();
double imageWriteDuration = 0;
DumpHardwareType currentTry = null;
ExtentsULong extents = null;
DumpHardware currentTry = null;
ExtentsULong extents = null;
ResumeSupport.Process(true, _dev.IsRemovable, blocks, _dev.Manufacturer, _dev.Model, _dev.Serial,
_dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision,
_private, _force);
ResumeSupport.Process(true,
_dev.IsRemovable,
blocks,
_dev.Manufacturer,
_dev.Model,
_dev.Serial,
_dev.PlatformId,
ref _resume,
ref currentTry,
ref extents,
_dev.FirmwareRevision,
_private,
_force);
if(currentTry == null ||
extents == null)
if(currentTry == null || extents == null)
{
StoppingErrorMessage?.Invoke("Could not process resume file, not continuing...");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_process_resume_file_not_continuing);
return;
}
if(_resume.NextBlock > 0)
_dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock);
if(_resume.NextBlock > 0) _dumpLog.WriteLine(Localization.Core.Resuming_from_block_0, _resume.NextBlock);
if(_createGraph)
{
Spiral.DiscParameters discSpiralParameters = Spiral.DiscParametersFromMediaType(dskType);
if(discSpiralParameters is not null)
_mediaGraph = new Spiral((int)_dimensions, (int)_dimensions, discSpiralParameters, blocks);
else
_mediaGraph = new BlockMap((int)_dimensions, (int)_dimensions, blocks);
if(_mediaGraph is not null)
{
foreach(Tuple<ulong, ulong> e in extents.ToArray())
_mediaGraph?.PaintSectorsGood(e.Item1, (uint)(e.Item2 - e.Item1 + 2));
}
_mediaGraph?.PaintSectorsBad(_resume.BadBlocks);
}
var newTrim = false;
DateTime timeSpeedStart = DateTime.UtcNow;
ulong sectorSpeedStart = 0;
_speedStopwatch.Restart();
ulong sectorSpeedStart = 0;
InitProgress?.Invoke();
for(ulong i = _resume.NextBlock; i < blocks; i += blocksToRead)
@@ -186,114 +209,141 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(blocks - i < blocksToRead)
blocksToRead = (uint)(blocks - i);
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i, blocks);
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
blocks);
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)i, blockSize, 0,
blocksToRead, false, _dev.Timeout, out double cmdDuration);
_speedStopwatch.Start();
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)i,
blockSize,
0,
blocksToRead,
false,
_dev.Timeout,
out double cmdDuration);
_speedStopwatch.Stop();
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration);
mhddLog.Write(i, cmdDuration, blocksToRead);
ibgLog.Write(i, currentSpeed * 1024);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(readBuffer, i, blocksToRead);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(i, blocksToRead, true);
_mediaGraph?.PaintSectorsGood(i, blocksToRead);
}
else
{
_errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
if(i + _skip > blocks)
_skip = (uint)(blocks - i);
if(i + _skip > blocks) _skip = (uint)(blocks - i);
// Write empty data
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(new byte[blockSize * _skip], i, _skip);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
for(ulong b = i; b < i + _skip; b++)
_resume.BadBlocks.Add(b);
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
_dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", _skip, i);
_dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
i += _skip - blocksToRead;
newTrim = true;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
_speedStopwatch.Stop();
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
end = DateTime.UtcNow;
_dumpStopwatch.Stop();
EndProgress?.Invoke();
mhddLog.Close();
ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000), _devicePath);
ibgLog.Close(_dev,
blocks,
blockSize,
_dumpStopwatch.Elapsed.TotalSeconds,
currentSpeed * 1024,
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
_devicePath);
UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average dump speed {blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000):F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
UpdateStatus?.
Invoke($"Average write speed {blockSize * (double)(blocks + 1) / 1024 / imageWriteDuration:F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
_dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
_dumpLog.WriteLine(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
_dumpLog.WriteLine(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
_dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / imageWriteDuration);
_dumpLog.WriteLine(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
#region Trimming
if(_resume.BadBlocks.Count > 0 &&
!_aborted &&
_trim &&
newTrim)
#region Trimming
if(_resume.BadBlocks.Count > 0 && !_aborted && _trim && newTrim)
{
start = DateTime.UtcNow;
UpdateStatus?.Invoke("Trimming skipped sectors");
_dumpLog.WriteLine("Trimming skipped sectors");
_trimStopwatch.Restart();
UpdateStatus?.Invoke(Localization.Core.Trimming_skipped_sectors);
_dumpLog.WriteLine(Localization.Core.Trimming_skipped_sectors);
ulong[] tmpArray = _resume.BadBlocks.ToArray();
InitProgress?.Invoke();
@@ -303,16 +353,28 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke($"Trimming sector {badSector}");
PulseProgress?.Invoke(string.Format(Localization.Core.Trimming_sector_0, badSector));
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)badSector,
blockSize, 0, 1, false, _dev.Timeout, out double _);
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)badSector,
blockSize,
0,
1,
false,
_dev.Timeout,
out double _);
if(sense || _dev.Error)
{
@@ -324,18 +386,21 @@ public partial class Dump
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputFormat.WriteSector(readBuffer, badSector);
_mediaGraph?.PaintSectorGood(badSector);
}
EndProgress?.Invoke();
end = DateTime.UtcNow;
_dumpLog.WriteLine("Trimming finished in {0} seconds.", (end - start).TotalSeconds);
}
#endregion Trimming
_trimStopwatch.Stop();
#region Error handling
if(_resume.BadBlocks.Count > 0 &&
!_aborted &&
_retryPasses > 0)
_dumpLog.WriteLine(string.Format(Localization.Core.Trimming_finished_in_0,
_trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
}
#endregion Trimming
#region Error handling
if(_resume.BadBlocks.Count > 0 && !_aborted && _retryPasses > 0)
{
var pass = 1;
var forward = true;
@@ -348,32 +413,47 @@ public partial class Dump
{
Modes.ModePage_01 pg;
sense = _dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
_dev.Timeout, out _);
sense = _dev.ModeSense6(out readBuffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(sense)
Modes.DecodedMode? dcMode6 = null;
if(!sense) dcMode6 = Modes.DecodeMode6(readBuffer, _dev.ScsiType);
if(sense || dcMode6 is null)
{
sense = _dev.ModeSense10(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
_dev.Timeout, out _);
sense = _dev.ModeSense10(out readBuffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(!sense)
{
Modes.DecodedMode? dcMode10 = Modes.DecodeMode10(readBuffer, _dev.ScsiType);
if(dcMode10.HasValue)
{
foreach(Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage =>
modePage.Page == 0x01 && modePage.Subpage == 0x00))
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
}
}
}
else
{
Modes.DecodedMode? dcMode6 = Modes.DecodeMode6(readBuffer, _dev.ScsiType);
if(dcMode6.HasValue)
{
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage =>
modePage.Page == 0x01 && modePage.Subpage == 0x00))
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
}
}
if(currentModePage == null)
@@ -417,31 +497,32 @@ public partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01(pg)
}
}
]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
_dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);
if(sense)
{
UpdateStatus?.
Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
UpdateStatus?.Invoke(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
AaruConsole.DebugWriteLine(Localization.Core.Error_0, Sense.PrettifySense(senseBuf));
_dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
_dumpLog.WriteLine(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
}
else
runningPersistent = true;
@@ -456,46 +537,76 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
forward ? "forward" : "reverse",
runningPersistent ? "recovering partial data, " : ""));
if(forward)
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_forward,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_forward,
badSector,
pass));
}
else
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_reverse,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_reverse,
badSector,
pass));
}
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)badSector,
blockSize, 0, 1, false, _dev.Timeout, out double cmdDuration);
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)badSector,
blockSize,
0,
1,
false,
_dev.Timeout,
out double cmdDuration);
totalDuration += cmdDuration;
if(sense || _dev.Error)
_errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf);
if(sense || _dev.Error) _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf);
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputFormat.WriteSector(readBuffer, badSector);
UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}.");
_dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
_mediaGraph?.PaintSectorGood(badSector);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_block_0_in_pass_1,
badSector,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_block_0_in_pass_1, badSector, pass);
}
else if(runningPersistent)
outputFormat.WriteSector(readBuffer, badSector);
else if(runningPersistent) outputFormat.WriteSector(readBuffer, badSector);
}
if(pass < _retryPasses &&
!_aborted &&
_resume.BadBlocks.Count > 0)
if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0)
{
pass++;
forward = !forward;
_resume.BadBlocks.Sort();
if(!forward)
_resume.BadBlocks.Reverse();
if(!forward) _resume.BadBlocks.Reverse();
goto repeatRetry;
}
@@ -505,57 +616,60 @@ public partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
currentModePage.Value
}
Pages = [currentModePage.Value]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
UpdateStatus?.Invoke("Sending MODE SELECT to drive (return device to previous status).");
_dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
_dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _);
}
EndProgress?.Invoke();
}
#endregion Error handling
#endregion Error handling
_resume.BadBlocks.Sort();
foreach(ulong bad in _resume.BadBlocks)
_dumpLog.WriteLine("Sector {0} could not be read.", bad);
foreach(ulong bad in _resume.BadBlocks) _dumpLog.WriteLine(Localization.Core.Sector_0_could_not_be_read, bad);
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
var metadata = new ImageInfo
var metadata = new CommonTypes.Structs.ImageInfo
{
Application = "Aaru",
ApplicationVersion = Version.GetVersion()
};
if(!outputFormat.SetMetadata(metadata))
ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine +
if(!outputFormat.SetImageInfo(metadata))
{
ErrorMessage?.Invoke(Localization.Core.Error_0_setting_metadata +
Environment.NewLine +
outputFormat.ErrorMessage);
}
outputFormat.SetDumpHardware(_resume.Tries);
if(_preSidecar != null)
outputFormat.SetCicmMetadata(_preSidecar);
if(_preSidecar != null) outputFormat.SetMetadata(_preSidecar);
_dumpLog.WriteLine("Closing output file.");
UpdateStatus?.Invoke("Closing output file.");
DateTime closeStart = DateTime.Now;
_dumpLog.WriteLine(Localization.Core.Closing_output_file);
UpdateStatus?.Invoke(Localization.Core.Closing_output_file);
_imageCloseStopwatch.Restart();
outputFormat.Close();
DateTime closeEnd = DateTime.Now;
UpdateStatus?.Invoke($"Closed in {(closeEnd - closeStart).TotalSeconds} seconds.");
_dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);
_imageCloseStopwatch.Stop();
UpdateStatus?.Invoke(string.Format(Localization.Core.Closed_in_0,
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine(Localization.Core.Closed_in_0,
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second));
if(_aborted)
{
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
return;
}
@@ -564,21 +678,20 @@ public partial class Dump
if(_metadata)
{
UpdateStatus?.Invoke("Creating sidecar.");
_dumpLog.WriteLine("Creating sidecar.");
var filters = new FiltersList();
IFilter filter = filters.GetFilter(_outputPath);
UpdateStatus?.Invoke(Localization.Core.Creating_sidecar);
_dumpLog.WriteLine(Localization.Core.Creating_sidecar);
IFilter filter = PluginRegister.Singleton.GetFilter(_outputPath);
var inputPlugin = ImageFormat.Detect(filter) as IMediaImage;
ErrorNumber opened = inputPlugin.Open(filter);
if(opened != ErrorNumber.NoError)
{
StoppingErrorMessage?.Invoke($"Error {opened} opening created image.");
StoppingErrorMessage?.Invoke(string.Format(Localization.Core.Error_0_opening_created_image, opened));
return;
}
DateTime chkStart = DateTime.UtcNow;
_sidecarStopwatch.Restart();
_sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding);
_sidecarClass.InitProgressEvent += InitProgress;
_sidecarClass.UpdateProgressEvent += UpdateProgress;
@@ -587,91 +700,125 @@ public partial class Dump
_sidecarClass.UpdateProgressEvent2 += UpdateProgress2;
_sidecarClass.EndProgressEvent2 += EndProgress2;
_sidecarClass.UpdateStatusEvent += UpdateStatus;
CICMMetadataType sidecar = _sidecarClass.Create();
end = DateTime.UtcNow;
Metadata sidecar = _sidecarClass.Create();
_sidecarStopwatch.Stop();
if(!_aborted)
{
totalChkDuration = (end - chkStart).TotalMilliseconds;
UpdateStatus?.Invoke($"Sidecar created in {(end - chkStart).TotalSeconds} seconds.");
totalChkDuration = _sidecarStopwatch.Elapsed.TotalMilliseconds;
UpdateStatus?.
Invoke($"Average checksum speed {blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000):F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Sidecar_created_in_0,
_sidecarStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_checksum_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalChkDuration.Milliseconds())
.Humanize()));
_dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));
_dumpLog.WriteLine(Localization.Core.Sidecar_created_in_0,
_sidecarStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second));
_dumpLog.WriteLine(Localization.Core.Average_checksum_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalChkDuration.Milliseconds())
.Humanize());
if(_preSidecar != null)
{
_preSidecar.BlockMedia = sidecar.BlockMedia;
sidecar = _preSidecar;
_preSidecar.BlockMedias = sidecar.BlockMedias;
sidecar = _preSidecar;
}
List<(ulong start, string type)> filesystems = new();
List<(ulong start, string type)> filesystems = [];
if(sidecar.BlockMedia[0].FileSystemInformation != null)
filesystems.AddRange(from partition in sidecar.BlockMedia[0].FileSystemInformation
where partition.FileSystems != null from fileSystem in partition.FileSystems
if(sidecar.BlockMedias[0].FileSystemInformation != null)
{
filesystems.AddRange(from partition in sidecar.BlockMedias[0].FileSystemInformation
where partition.FileSystems != null
from fileSystem in partition.FileSystems
select (partition.StartSector, fileSystem.Type));
}
if(filesystems.Count > 0)
{
foreach(var filesystem in filesystems.Select(o => new
{
o.start,
o.type
}).Distinct())
{
o.start,
o.type
})
.Distinct())
{
UpdateStatus?.Invoke($"Found filesystem {filesystem.type} at sector {filesystem.start}");
_dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_filesystem_0_at_sector_1,
filesystem.type,
filesystem.start));
_dumpLog.WriteLine(Localization.Core.Found_filesystem_0_at_sector_1,
filesystem.type,
filesystem.start);
}
}
sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
sidecar.BlockMedias[0].Dimensions = Dimensions.FromMediaType(dskType);
(string type, string subType) xmlType = CommonTypes.Metadata.MediaType.MediaTypeToString(dskType);
sidecar.BlockMedia[0].DiskType = xmlType.type;
sidecar.BlockMedia[0].DiskSubType = xmlType.subType;
sidecar.BlockMedia[0].Interface = "USB";
sidecar.BlockMedia[0].LogicalBlocks = blocks;
sidecar.BlockMedia[0].PhysicalBlockSize = (int)blockSize;
sidecar.BlockMedia[0].LogicalBlockSize = (int)blockSize;
sidecar.BlockMedia[0].Manufacturer = _dev.Manufacturer;
sidecar.BlockMedia[0].Model = _dev.Model;
sidecar.BlockMedias[0].MediaType = xmlType.type;
sidecar.BlockMedias[0].MediaSubType = xmlType.subType;
sidecar.BlockMedias[0].Interface = "USB";
sidecar.BlockMedias[0].LogicalBlocks = blocks;
sidecar.BlockMedias[0].PhysicalBlockSize = (int)blockSize;
sidecar.BlockMedias[0].LogicalBlockSize = (int)blockSize;
sidecar.BlockMedias[0].Manufacturer = _dev.Manufacturer;
sidecar.BlockMedias[0].Model = _dev.Model;
if(!_private)
sidecar.BlockMedia[0].Serial = _dev.Serial;
if(!_private) sidecar.BlockMedias[0].Serial = _dev.Serial;
sidecar.BlockMedia[0].Size = blocks * blockSize;
sidecar.BlockMedias[0].Size = blocks * blockSize;
if(_dev.IsRemovable)
sidecar.BlockMedia[0].DumpHardwareArray = _resume.Tries.ToArray();
if(_dev.IsRemovable) sidecar.BlockMedias[0].DumpHardware = _resume.Tries;
UpdateStatus?.Invoke("Writing metadata sidecar");
UpdateStatus?.Invoke(Localization.Core.Writing_metadata_sidecar);
var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create);
var jsonFs = new FileStream(_outputPrefix + ".metadata.json", FileMode.Create);
var xmlSer = new XmlSerializer(typeof(CICMMetadataType));
xmlSer.Serialize(xmlFs, sidecar);
xmlFs.Close();
JsonSerializer.Serialize(jsonFs,
new MetadataJson
{
AaruMetadata = sidecar
},
typeof(MetadataJson),
MetadataJsonContext.Default);
jsonFs.Close();
}
}
UpdateStatus?.Invoke("");
UpdateStatus?.
Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing).");
UpdateStatus?.Invoke(string.Format(Localization.Core
.Took_a_total_of_0_1_processing_commands_2_checksumming_3_writing_4_closing,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second),
totalDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
totalChkDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
imageWriteDuration.Seconds().Humanize(minUnit: TimeUnit.Second),
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average speed: {blockSize * (double)(blocks + 1) / 1048576 / (totalDuration / 1000):F3} MiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
if(maxSpeed > 0)
UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec.");
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Fastest_speed_burst_0,
ByteSize.FromMegabytes(maxSpeed).Per(_oneSecond).Humanize()));
}
if(minSpeed > 0 &&
minSpeed < double.MaxValue)
UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec.");
if(minSpeed is > 0 and < double.MaxValue)
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Slowest_speed_burst_0,
ByteSize.FromMegabytes(minSpeed).Per(_oneSecond).Humanize()));
}
UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read.");
UpdateStatus?.Invoke(string.Format(Localization.Core._0_sectors_could_not_be_read, _resume.BadBlocks.Count));
UpdateStatus?.Invoke("");
Statistics.AddMedia(dskType, true);

View File

@@ -27,11 +27,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Linq;
using Aaru.CommonTypes;
@@ -40,16 +38,12 @@ using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
namespace Aaru.Core.Devices.Dumping;
public partial class Dump
{
static readonly byte[] _fatSignature =
{
0x46, 0x41, 0x54, 0x31, 0x36, 0x20, 0x20, 0x20
};
static readonly byte[] _isoExtension =
{
0x49, 0x53, 0x4F
};
static readonly byte[] _fatSignature = "FAT16 "u8.ToArray();
static readonly byte[] _isoExtension = "ISO"u8.ToArray();
/// <summary>Dumps a CFW PlayStation Portable UMD</summary>
void PlayStationPortable()
@@ -58,24 +52,30 @@ public partial class Dump
!_outputPlugin.SupportedMediaTypes.Contains(MediaType.MemoryStickProDuo) &&
!_outputPlugin.SupportedMediaTypes.Contains(MediaType.UMD))
{
_dumpLog.WriteLine("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump...");
_dumpLog.WriteLine(Localization.Core
.Selected_output_format_does_not_support_MemoryStick_Duo_or_UMD_cannot_dump);
StoppingErrorMessage?.
Invoke("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump...");
StoppingErrorMessage?.Invoke(Localization.Core
.Selected_output_format_does_not_support_MemoryStick_Duo_or_UMD_cannot_dump);
return;
}
UpdateStatus?.Invoke("Checking if media is UMD or MemoryStick...");
_dumpLog.WriteLine("Checking if media is UMD or MemoryStick...");
UpdateStatus?.Invoke(Localization.Core.Checking_if_media_is_UMD_or_MemoryStick);
_dumpLog.WriteLine(Localization.Core.Checking_if_media_is_UMD_or_MemoryStick);
bool sense = _dev.ModeSense6(out byte[] buffer, out _, false, ScsiModeSensePageControl.Current, 0, _dev.Timeout,
bool sense = _dev.ModeSense6(out byte[] buffer,
out _,
false,
ScsiModeSensePageControl.Current,
0,
_dev.Timeout,
out _);
if(sense)
{
_dumpLog.WriteLine("Could not get MODE SENSE...");
StoppingErrorMessage?.Invoke("Could not get MODE SENSE...");
_dumpLog.WriteLine(Localization.Core.Could_not_get_MODE_SENSE);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_get_MODE_SENSE);
return;
}
@@ -84,8 +84,8 @@ public partial class Dump
if(!decoded.HasValue)
{
_dumpLog.WriteLine("Could not decode MODE SENSE...");
StoppingErrorMessage?.Invoke("Could not decode MODE SENSE...");
_dumpLog.WriteLine(Localization.Core.Could_not_decode_MODE_SENSE);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_decode_MODE_SENSE);
return;
}
@@ -102,8 +102,8 @@ public partial class Dump
if(sense)
{
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine(Localization.Core.Could_not_read);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
return;
}
@@ -124,16 +124,28 @@ public partial class Dump
var sectorsPerFat = (ushort)((buffer[0x17] << 8) + buffer[0x16]);
var rootStart = (ushort)(sectorsPerFat * 2 + fatStart);
UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}...");
_dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
UpdateStatus?.Invoke(string.Format(Localization.Core.Reading_root_directory_in_sector_0, rootStart));
_dumpLog.WriteLine(Localization.Core.Reading_root_directory_in_sector_0, rootStart);
sense = _dev.Read12(out buffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false, _dev.Timeout,
sense = _dev.Read12(out buffer,
out _,
0,
false,
true,
false,
false,
rootStart,
512,
0,
1,
false,
_dev.Timeout,
out _);
if(sense)
{
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
_dumpLog.WriteLine(Localization.Core.Could_not_read);
return;
}
@@ -148,11 +160,14 @@ public partial class Dump
return;
}
UpdateStatus?.Invoke($"FAT starts at sector {fatStart} and runs for {sectorsPerFat} sectors...");
_dumpLog.WriteLine("FAT starts at sector {0} and runs for {1} sectors...", fatStart, sectorsPerFat);
UpdateStatus?.Invoke(string.Format(Localization.Core.FAT_starts_at_sector_0_and_runs_for_1_sectors,
fatStart,
sectorsPerFat));
UpdateStatus?.Invoke("Reading FAT...");
_dumpLog.WriteLine("Reading FAT...");
_dumpLog.WriteLine(Localization.Core.FAT_starts_at_sector_0_and_runs_for_1_sectors, fatStart, sectorsPerFat);
UpdateStatus?.Invoke(Localization.Core.Reading_FAT);
_dumpLog.WriteLine(Localization.Core.Reading_FAT);
var fat = new byte[sectorsPerFat * 512];
@@ -162,16 +177,27 @@ public partial class Dump
{
uint transfer = 64;
if(transfer + position > sectorsPerFat)
transfer = sectorsPerFat - position;
if(transfer + position > sectorsPerFat) transfer = sectorsPerFat - position;
sense = _dev.Read12(out buffer, out _, 0, false, true, false, false, position + fatStart, 512, 0, transfer,
false, _dev.Timeout, out _);
sense = _dev.Read12(out buffer,
out _,
0,
false,
true,
false,
false,
position + fatStart,
512,
0,
transfer,
false,
_dev.Timeout,
out _);
if(sense)
{
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
_dumpLog.WriteLine(Localization.Core.Could_not_read);
return;
}
@@ -181,8 +207,8 @@ public partial class Dump
position += transfer;
}
UpdateStatus?.Invoke("Traversing FAT...");
_dumpLog.WriteLine("Traversing FAT...");
UpdateStatus?.Invoke(Localization.Core.Traversing_FAT);
_dumpLog.WriteLine(Localization.Core.Traversing_FAT);
var previousCluster = BitConverter.ToUInt16(fat, 4);
@@ -197,8 +223,7 @@ public partial class Dump
continue;
}
if(nextCluster == 0xFFFF)
break;
if(nextCluster == 0xFFFF) break;
DumpMs();
@@ -208,6 +233,6 @@ public partial class Dump
if(_outputPlugin is IWritableOpticalImage)
DumpUmd();
else
StoppingErrorMessage?.Invoke("The specified plugin does not support storing optical disc images.");
StoppingErrorMessage?.Invoke(Localization.Core.The_specified_image_format_cannot_represent_optical_discs);
}
}

View File

@@ -27,29 +27,32 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Console;
using Aaru.Core.Graphics;
using Aaru.Core.Logging;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
using Schemas;
using Humanizer;
using Humanizer.Bytes;
using Humanizer.Localisation;
using Track = Aaru.CommonTypes.Structs.Track;
using TrackType = Aaru.CommonTypes.Enums.TrackType;
using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Core.Devices.Dumping;
public partial class Dump
{
[SuppressMessage("ReSharper", "JoinDeclarationAndInitializer")]
@@ -62,24 +65,34 @@ public partial class Dump
double currentSpeed = 0;
double maxSpeed = double.MinValue;
double minSpeed = double.MaxValue;
DateTime start;
DateTime end;
byte[] senseBuf;
if(_outputPlugin is not IWritableOpticalImage outputOptical)
{
StoppingErrorMessage?.Invoke("Image is not writable, aborting...");
StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting);
return;
}
bool sense = _dev.Read12(out byte[] readBuffer, out _, 0, false, true, false, false, 0, 512, 0, 1, false,
_dev.Timeout, out _);
bool sense = _dev.Read12(out byte[] readBuffer,
out _,
0,
false,
true,
false,
false,
0,
512,
0,
1,
false,
_dev.Timeout,
out _);
if(sense)
{
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine(Localization.Core.Could_not_read);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
return;
}
@@ -90,16 +103,28 @@ public partial class Dump
var rootSize = (ushort)(((readBuffer[0x12] << 8) + readBuffer[0x11]) * 32 / 512);
var umdStart = (ushort)(rootStart + rootSize);
UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}...");
_dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
UpdateStatus?.Invoke(string.Format(Localization.Core.Reading_root_directory_in_sector_0, rootStart));
_dumpLog.WriteLine(Localization.Core.Reading_root_directory_in_sector_0, rootStart);
sense = _dev.Read12(out readBuffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false,
_dev.Timeout, out _);
sense = _dev.Read12(out readBuffer,
out _,
0,
false,
true,
false,
false,
rootStart,
512,
0,
1,
false,
_dev.Timeout,
out _);
if(sense)
{
_dumpLog.WriteLine("Could not read...");
StoppingErrorMessage?.Invoke("Could not read...");
_dumpLog.WriteLine(Localization.Core.Could_not_read);
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_read);
return;
}
@@ -108,93 +133,119 @@ public partial class Dump
ulong blocks = umdSizeInBytes / blockSize;
string mediaPartNumber = Encoding.ASCII.GetString(readBuffer, 0, 11).Trim();
ulong totalSize = blocks * blockSize;
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_has_0_blocks_of_1_bytes_each_for_a_total_of_2,
blocks,
blockSize,
ByteSize.FromBytes(blocks * blockSize).ToString("0.000")));
if(totalSize > 1073741824)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1073741824d:F3} GiB)");
else if(totalSize > 1048576)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1048576d:F3} MiB)");
else if(totalSize > 1024)
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize / 1024d:F3} KiB)");
else
UpdateStatus?.
Invoke($"Media has {blocks} blocks of {blockSize} bytes/each. (for a total of {totalSize} bytes)");
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_blocks_1_bytes,
blocks,
blocks * blockSize));
UpdateStatus?.Invoke($"Device reports {blocks} blocks ({blocks * blockSize} bytes).");
UpdateStatus?.Invoke($"Device can read {blocksToRead} blocks at a time.");
UpdateStatus?.Invoke($"Device reports {blockSize} bytes per logical block.");
UpdateStatus?.Invoke($"Device reports {2048} bytes per physical block.");
UpdateStatus?.Invoke($"SCSI device type: {_dev.ScsiType}.");
UpdateStatus?.Invoke($"Media identified as {dskType}.");
UpdateStatus?.Invoke($"Media part number is {mediaPartNumber}.");
_dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
_dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead);
_dumpLog.WriteLine("Device reports {0} bytes per logical block.", blockSize);
_dumpLog.WriteLine("Device reports {0} bytes per physical block.", 2048);
_dumpLog.WriteLine("SCSI device type: {0}.", _dev.ScsiType);
_dumpLog.WriteLine("Media identified as {0}.", dskType);
_dumpLog.WriteLine("Media part number is {0}.", mediaPartNumber);
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_can_read_0_blocks_at_a_time, blocksToRead));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_bytes_per_logical_block, blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.Device_reports_0_bytes_per_physical_block, blockSize));
UpdateStatus?.Invoke(string.Format(Localization.Core.SCSI_device_type_0, _dev.ScsiType));
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_identified_as_0, dskType));
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_part_number_is_0, mediaPartNumber));
_dumpLog.WriteLine(Localization.Core.Device_reports_0_blocks_1_bytes, blocks, blocks * blockSize);
_dumpLog.WriteLine(Localization.Core.Device_can_read_0_blocks_at_a_time, blocksToRead);
_dumpLog.WriteLine(Localization.Core.Device_reports_0_bytes_per_logical_block, blockSize);
_dumpLog.WriteLine(Localization.Core.Device_reports_0_bytes_per_physical_block, blockSize);
_dumpLog.WriteLine(Localization.Core.SCSI_device_type_0, _dev.ScsiType);
_dumpLog.WriteLine(Localization.Core.Media_identified_as_0, dskType);
_dumpLog.WriteLine(Localization.Core.Media_part_number_is_0, mediaPartNumber);
bool ret;
var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", _dev, blocks, blockSize, blocksToRead, _private);
var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010);
var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin",
_dev,
blocks,
blockSize,
blocksToRead,
_private,
_dimensions);
var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010);
ret = outputOptical.Create(_outputPath, dskType, _formatOptions, blocks, blockSize);
// Cannot create image
if(!ret)
{
_dumpLog.WriteLine("Error creating output image, not continuing.");
_dumpLog.WriteLine(Localization.Core.Error_creating_output_image_not_continuing);
_dumpLog.WriteLine(outputOptical.ErrorMessage);
StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine +
StoppingErrorMessage?.Invoke(Localization.Core.Error_creating_output_image_not_continuing +
Environment.NewLine +
outputOptical.ErrorMessage);
return;
}
start = DateTime.UtcNow;
_dumpStopwatch.Restart();
double imageWriteDuration = 0;
outputOptical.SetTracks(new List<Track>
outputOptical.SetTracks([
new Track
{
BytesPerSector = (int)blockSize,
EndSector = blocks - 1,
Sequence = 1,
RawBytesPerSector = (int)blockSize,
SubchannelType = TrackSubchannelType.None,
Session = 1,
Type = TrackType.Data
}
]);
DumpHardware currentTry = null;
ExtentsULong extents = null;
ResumeSupport.Process(true,
_dev.IsRemovable,
blocks,
_dev.Manufacturer,
_dev.Model,
_dev.Serial,
_dev.PlatformId,
ref _resume,
ref currentTry,
ref extents,
_dev.FirmwareRevision,
_private,
_force);
if(currentTry == null || extents == null)
{
new()
{
BytesPerSector = (int)blockSize,
EndSector = blocks - 1,
Sequence = 1,
RawBytesPerSector = (int)blockSize,
SubchannelType = TrackSubchannelType.None,
Session = 1,
Type = TrackType.Data
}
});
DumpHardwareType currentTry = null;
ExtentsULong extents = null;
ResumeSupport.Process(true, _dev.IsRemovable, blocks, _dev.Manufacturer, _dev.Model, _dev.Serial,
_dev.PlatformId, ref _resume, ref currentTry, ref extents, _dev.FirmwareRevision,
_private, _force);
if(currentTry == null ||
extents == null)
{
StoppingErrorMessage?.Invoke("Could not process resume file, not continuing...");
StoppingErrorMessage?.Invoke(Localization.Core.Could_not_process_resume_file_not_continuing);
return;
}
if(_resume.NextBlock > 0)
_dumpLog.WriteLine("Resuming from block {0}.", _resume.NextBlock);
if(_resume.NextBlock > 0) _dumpLog.WriteLine(Localization.Core.Resuming_from_block_0, _resume.NextBlock);
if(_createGraph)
{
Spiral.DiscParameters discSpiralParameters = Spiral.DiscParametersFromMediaType(dskType);
if(discSpiralParameters is not null)
_mediaGraph = new Spiral((int)_dimensions, (int)_dimensions, discSpiralParameters, blocks);
else
_mediaGraph = new BlockMap((int)_dimensions, (int)_dimensions, blocks);
if(_mediaGraph is not null)
{
foreach(Tuple<ulong, ulong> e in extents.ToArray())
_mediaGraph?.PaintSectorsGood(e.Item1, (uint)(e.Item2 - e.Item1 + 2));
}
_mediaGraph?.PaintSectorsBad(_resume.BadBlocks);
}
var newTrim = false;
DateTime timeSpeedStart = DateTime.UtcNow;
ulong sectorSpeedStart = 0;
_speedStopwatch.Reset();
ulong sectorSpeedStart = 0;
InitProgress?.Invoke();
for(ulong i = _resume.NextBlock; i < blocks; i += blocksToRead)
@@ -202,114 +253,139 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(blocks - i < blocksToRead)
blocksToRead = (uint)(blocks - i);
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i,
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
(long)blocks);
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(umdStart + i * 4),
512, 0, blocksToRead * 4, false, _dev.Timeout, out double cmdDuration);
_speedStopwatch.Start();
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)(umdStart + i * 4),
512,
0,
blocksToRead * 4,
false,
_dev.Timeout,
out double cmdDuration);
_speedStopwatch.Stop();
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
_writeStopwatch.Restart();
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration);
mhddLog.Write(i, cmdDuration, blocksToRead);
ibgLog.Write(i, currentSpeed * 1024);
DateTime writeStart = DateTime.Now;
outputOptical.WriteSectors(readBuffer, i, blocksToRead);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(i, blocksToRead, true);
_mediaGraph?.PaintSectorsGood(i, blocksToRead);
}
else
{
_errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
if(i + _skip > blocks)
_skip = (uint)(blocks - i);
if(i + _skip > blocks) _skip = (uint)(blocks - i);
// Write empty data
DateTime writeStart = DateTime.Now;
outputOptical.WriteSectors(new byte[blockSize * _skip], i, _skip);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
for(ulong b = i; b < i + _skip; b++)
_resume.BadBlocks.Add(b);
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
_dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", _skip, i);
_dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
i += _skip - blocksToRead;
newTrim = true;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
end = DateTime.UtcNow;
_dumpStopwatch.Stop();
EndProgress?.Invoke();
mhddLog.Close();
ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000), _devicePath);
ibgLog.Close(_dev,
blocks,
blockSize,
_dumpStopwatch.Elapsed.TotalSeconds,
currentSpeed * 1024,
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
_devicePath);
UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average dump speed {blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000):F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
UpdateStatus?.
Invoke($"Average write speed {blockSize * (double)(blocks + 1) / 1024 / imageWriteDuration:F3} KiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
_dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
_dumpLog.WriteLine(string.Format(Localization.Core.Dump_finished_in_0,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
_dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
_dumpLog.WriteLine(string.Format(Localization.Core.Average_dump_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
_dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
blockSize * (double)(blocks + 1) / 1024 / imageWriteDuration);
_dumpLog.WriteLine(string.Format(Localization.Core.Average_write_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(imageWriteDuration.Seconds())
.Humanize()));
#region Trimming
if(_resume.BadBlocks.Count > 0 &&
!_aborted &&
_trim &&
newTrim)
#region Trimming
if(_resume.BadBlocks.Count > 0 && !_aborted && _trim && newTrim)
{
start = DateTime.UtcNow;
_dumpLog.WriteLine("Trimming skipped sectors");
_trimStopwatch.Restart();
_dumpLog.WriteLine(Localization.Core.Trimming_skipped_sectors);
ulong[] tmpArray = _resume.BadBlocks.ToArray();
InitProgress?.Invoke();
@@ -319,15 +395,27 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke($"Trimming sector {badSector}");
PulseProgress?.Invoke(string.Format(Localization.Core.Trimming_sector_0, badSector));
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false,
(uint)(umdStart + badSector * 4), 512, 0, 4, false, _dev.Timeout, out double _);
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)(umdStart + badSector * 4),
512,
0,
4,
false,
_dev.Timeout,
out double _);
if(sense || _dev.Error)
{
@@ -339,18 +427,21 @@ public partial class Dump
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputOptical.WriteSector(readBuffer, badSector);
_mediaGraph?.PaintSectorGood(badSector);
}
EndProgress?.Invoke();
end = DateTime.UtcNow;
_dumpLog.WriteLine("Trimming finished in {0} seconds.", (end - start).TotalSeconds);
}
#endregion Trimming
_trimStopwatch.Stop();
#region Error handling
if(_resume.BadBlocks.Count > 0 &&
!_aborted &&
_retryPasses > 0)
_dumpLog.WriteLine(string.Format(Localization.Core.Trimming_finished_in_0,
_trimStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
}
#endregion Trimming
#region Error handling
if(_resume.BadBlocks.Count > 0 && !_aborted && _retryPasses > 0)
{
var pass = 1;
var forward = true;
@@ -363,17 +454,24 @@ public partial class Dump
{
Modes.ModePage_01 pg;
sense = _dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
_dev.Timeout, out _);
sense = _dev.ModeSense6(out readBuffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(!sense)
{
Modes.DecodedMode? dcMode6 = Modes.DecodeMode6(readBuffer, _dev.ScsiType);
if(dcMode6.HasValue)
{
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage =>
modePage.Page == 0x01 && modePage.Subpage == 0x00))
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
}
}
if(currentModePage == null)
@@ -417,30 +515,31 @@ public partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01(pg)
}
}
]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
_dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
sense = _dev.ModeSelect(md6, out senseBuf, true, false, _dev.Timeout, out _);
if(sense)
{
UpdateStatus?.
Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
UpdateStatus?.Invoke(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
AaruConsole.DebugWriteLine(Localization.Core.Error_0, Sense.PrettifySense(senseBuf));
_dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
_dumpLog.WriteLine(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
}
else
runningPersistent = true;
@@ -455,48 +554,76 @@ public partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
_dumpLog.WriteLine("Aborted!");
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.
Invoke($"Retrying sector {badSector}, pass {pass}, {(runningPersistent ? "recovering partial data, " : "")}{(forward ? "forward" : "reverse")}");
if(forward)
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_forward,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_forward,
badSector,
pass));
}
else
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_reverse,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_reverse,
badSector,
pass));
}
sense = _dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false,
(uint)(umdStart + badSector * 4), 512, 0, 4, false, _dev.Timeout,
sense = _dev.Read12(out readBuffer,
out senseBuf,
0,
false,
true,
false,
false,
(uint)(umdStart + badSector * 4),
512,
0,
4,
false,
_dev.Timeout,
out double cmdDuration);
totalDuration += cmdDuration;
if(sense || _dev.Error)
_errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf);
if(sense || _dev.Error) _errorLog?.WriteLine(badSector, _dev.Error, _dev.LastError, senseBuf);
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputOptical.WriteSector(readBuffer, badSector);
_mediaGraph?.PaintSectorGood(badSector);
UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_block_0_in_pass_1,
badSector,
pass));
_dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
_dumpLog.WriteLine(Localization.Core.Correctly_retried_block_0_in_pass_1, badSector, pass);
}
else if(runningPersistent)
outputOptical.WriteSector(readBuffer, badSector);
else if(runningPersistent) outputOptical.WriteSector(readBuffer, badSector);
}
if(pass < _retryPasses &&
!_aborted &&
_resume.BadBlocks.Count > 0)
if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0)
{
pass++;
forward = !forward;
_resume.BadBlocks.Sort();
if(!forward)
_resume.BadBlocks.Reverse();
if(!forward) _resume.BadBlocks.Reverse();
goto repeatRetry;
}
@@ -506,82 +633,94 @@ public partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
currentModePage.Value
}
Pages = [currentModePage.Value]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
_dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
_dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _);
}
EndProgress?.Invoke();
AaruConsole.WriteLine();
}
#endregion Error handling
#endregion Error handling
_resume.BadBlocks.Sort();
foreach(ulong bad in _resume.BadBlocks)
_dumpLog.WriteLine("Sector {0} could not be read.", bad);
foreach(ulong bad in _resume.BadBlocks) _dumpLog.WriteLine(Localization.Core.Sector_0_could_not_be_read, bad);
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
var metadata = new ImageInfo
var metadata = new CommonTypes.Structs.ImageInfo
{
Application = "Aaru",
ApplicationVersion = Version.GetVersion(),
MediaPartNumber = mediaPartNumber
};
if(!outputOptical.SetMetadata(metadata))
ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine +
if(!outputOptical.SetImageInfo(metadata))
{
ErrorMessage?.Invoke(Localization.Core.Error_0_setting_metadata +
Environment.NewLine +
outputOptical.ErrorMessage);
}
outputOptical.SetDumpHardware(_resume.Tries);
if(_preSidecar != null)
outputOptical.SetCicmMetadata(_preSidecar);
if(_preSidecar != null) outputOptical.SetMetadata(_preSidecar);
_dumpLog.WriteLine("Closing output file.");
UpdateStatus?.Invoke("Closing output file.");
DateTime closeStart = DateTime.Now;
_dumpLog.WriteLine(Localization.Core.Closing_output_file);
UpdateStatus?.Invoke(Localization.Core.Closing_output_file);
_imageCloseStopwatch.Restart();
outputOptical.Close();
DateTime closeEnd = DateTime.Now;
_dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);
_imageCloseStopwatch.Stop();
_dumpLog.WriteLine(Localization.Core.Closed_in_0,
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second));
if(_aborted)
{
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
return;
}
double totalChkDuration = 0;
if(_metadata)
WriteOpticalSidecar(blockSize, blocks, dskType, null, null, 1, out totalChkDuration, null);
if(_metadata) WriteOpticalSidecar(blockSize, blocks, dskType, null, null, 1, out totalChkDuration, null);
UpdateStatus?.Invoke("");
UpdateStatus?.
Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing).");
UpdateStatus?.Invoke(string.Format(Localization.Core
.Took_a_total_of_0_1_processing_commands_2_checksumming_3_writing_4_closing,
_dumpStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second),
totalDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
totalChkDuration.Milliseconds().Humanize(minUnit: TimeUnit.Second),
imageWriteDuration.Seconds().Humanize(minUnit: TimeUnit.Second),
_imageCloseStopwatch.Elapsed.Humanize(minUnit: TimeUnit.Second)));
UpdateStatus?.
Invoke($"Average speed: {blockSize * (double)(blocks + 1) / 1048576 / (totalDuration / 1000):F3} MiB/sec.");
UpdateStatus?.Invoke(string.Format(Localization.Core.Average_speed_0,
ByteSize.FromBytes(blockSize * (blocks + 1))
.Per(totalDuration.Milliseconds())
.Humanize()));
if(maxSpeed > 0)
UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec.");
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Fastest_speed_burst_0,
ByteSize.FromMegabytes(maxSpeed).Per(_oneSecond).Humanize()));
}
if(minSpeed > 0 &&
minSpeed < double.MaxValue)
UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec.");
if(minSpeed is > 0 and < double.MaxValue)
{
UpdateStatus?.Invoke(string.Format(Localization.Core.Slowest_speed_burst_0,
ByteSize.FromMegabytes(minSpeed).Per(_oneSecond).Humanize()));
}
UpdateStatus?.Invoke($"{_resume.BadBlocks.Count} sectors could not be read.");
UpdateStatus?.Invoke(string.Format(Localization.Core._0_sectors_could_not_be_read, _resume.BadBlocks.Count));
UpdateStatus?.Invoke("");
Statistics.AddMedia(dskType, true);

View File

@@ -27,19 +27,18 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Collections.Generic;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Metadata;
using Schemas;
using PlatformID = Aaru.CommonTypes.Interop.PlatformID;
using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Core.Devices.Dumping;
/// <summary>Implements resume support</summary>
static class ResumeSupport
{
@@ -64,65 +63,95 @@ static class ResumeSupport
/// progress dump
/// </exception>
internal static void Process(bool isLba, bool removable, ulong blocks, string manufacturer, string model,
string serial, PlatformID platform, ref Resume resume, ref DumpHardwareType currentTry,
string serial, PlatformID platform, ref Resume resume, ref DumpHardware currentTry,
ref ExtentsULong extents, string firmware, bool @private, bool force,
bool isTape = false)
{
if(@private)
serial = null;
if(@private) serial = null;
if(resume != null)
{
if(!isLba)
throw new NotImplementedException("Resuming CHS devices is currently not supported.");
throw new NotImplementedException(Localization.Core.Resuming_CHS_devices_is_currently_not_supported);
if(resume.Tape != isTape)
throw new
InvalidOperationException($"Resume file specifies a {(resume.Tape ? "tape" : "not tape")} device but you're requesting to dump a {(isTape ? "tape" : "not tape")} device, not continuing...");
if(resume.Removable != removable &&
!force)
throw new
InvalidOperationException($"Resume file specifies a {(resume.Removable ? "removable" : "non removable")} device but you're requesting to dump a {(removable ? "removable" : "non removable")} device, not continuing...");
if(!isTape &&
resume.LastBlock != blocks - 1 &&
!force)
throw new
InvalidOperationException($"Resume file specifies a device with {resume.LastBlock + 1} blocks but you're requesting to dump one with {blocks} blocks, not continuing...");
foreach(DumpHardwareType oldTry in resume.Tries)
{
if(!removable &&
!force)
if(resume.Tape)
throw new InvalidOperationException(Localization.Core.Resume_specifies_tape_but_device_is_not_tape);
throw new InvalidOperationException(Localization.Core.Resume_specifies_not_tape_but_device_is_tape);
}
if(resume.Removable != removable && !force)
{
if(resume.Removable)
{
throw new InvalidOperationException(Localization.Core
.Resume_specifies_removable_but_device_is_non_removable);
}
throw new InvalidOperationException(Localization.Core
.Resume_specifies_non_removable_but_device_is_removable);
}
if(!isTape && resume.LastBlock != blocks - 1 && !force)
{
throw new InvalidOperationException(string.Format(Localization.Core
.Resume_file_different_number_of_blocks_not_continuing,
resume.LastBlock + 1,
blocks));
}
foreach(DumpHardware oldTry in resume.Tries)
{
if(!removable && !force)
{
if(oldTry.Manufacturer != manufacturer)
throw new
InvalidOperationException($"Resume file specifies a device manufactured by {oldTry.Manufacturer} but you're requesting to dump one by {manufacturer}, not continuing...");
{
throw new InvalidOperationException(string.Format(Localization.Core
.Resume_file_different_manufacturer_not_continuing,
oldTry.Manufacturer,
manufacturer));
}
if(oldTry.Model != model)
throw new
InvalidOperationException($"Resume file specifies a device model {oldTry.Model} but you're requesting to dump model {model}, not continuing...");
{
throw new InvalidOperationException(string.Format(Localization.Core
.Resume_file_different_model_not_continuing,
oldTry.Model,
model));
}
if(oldTry.Serial != serial)
throw new
InvalidOperationException($"Resume file specifies a device with serial {oldTry.Serial} but you're requesting to dump one with serial {serial}, not continuing...");
{
throw new InvalidOperationException(string.Format(Localization.Core
.Resume_file_different_serial_number_not_continuing,
oldTry.Serial,
serial));
}
if(oldTry.Firmware != firmware)
throw new
InvalidOperationException($"Resume file specifies a device with firmware version {oldTry.Firmware} but you're requesting to dump one with firmware version {firmware}, not continuing...");
{
throw new InvalidOperationException(string.Format(Localization.Core
.Resume_file_different_firmware_revision_not_continuing,
oldTry.Firmware,
firmware));
}
}
if(oldTry.Software == null)
throw new InvalidOperationException("Found corrupt resume file, cannot continue...");
throw new InvalidOperationException(Localization.Core.Found_corrupt_resume_file_cannot_continue);
if(oldTry.Software.Name != "Aaru" ||
oldTry.Software.OperatingSystem != platform.ToString() ||
oldTry.Software.Version != Version.GetVersion())
continue;
if(removable && (oldTry.Manufacturer != manufacturer || oldTry.Model != model ||
oldTry.Serial != serial || oldTry.Firmware != firmware))
if(removable &&
(oldTry.Manufacturer != manufacturer ||
oldTry.Model != model ||
oldTry.Serial != serial ||
oldTry.Firmware != firmware))
continue;
currentTry = oldTry;
@@ -131,12 +160,11 @@ static class ResumeSupport
break;
}
if(currentTry != null)
return;
if(currentTry != null) return;
currentTry = new DumpHardwareType
currentTry = new DumpHardware
{
Software = CommonTypes.Metadata.Version.GetSoftwareType(),
Software = CommonTypes.Metadata.Version.GetSoftware(),
Manufacturer = manufacturer,
Model = model,
Serial = serial,
@@ -150,16 +178,16 @@ static class ResumeSupport
{
resume = new Resume
{
Tries = new List<DumpHardwareType>(),
Tries = [],
CreationDate = DateTime.UtcNow,
BadBlocks = new List<ulong>(),
BadBlocks = [],
LastBlock = isTape ? 0 : blocks - 1,
Tape = isTape
};
currentTry = new DumpHardwareType
currentTry = new DumpHardware
{
Software = CommonTypes.Metadata.Version.GetSoftwareType(),
Software = CommonTypes.Metadata.Version.GetSoftware(),
Manufacturer = manufacturer,
Model = model,
Serial = serial,

View File

@@ -27,11 +27,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Threading;
using Aaru.CommonTypes;
@@ -39,6 +37,8 @@ using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Decoders.SCSI;
namespace Aaru.Core.Devices.Dumping;
/// <summary>Implements dumping SCSI and ATAPI devices</summary>
public partial class Dump
{
@@ -60,92 +60,107 @@ public partial class Dump
if(decSense.HasValue)
{
ErrorMessage?.
Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h");
ErrorMessage?.Invoke(string.Format(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ));
_dumpLog.WriteLine("Device not ready. Sense {0} ASC {1:X2}h ASCQ {2:X2}h", decSense.Value.SenseKey,
decSense.Value.ASC, decSense.Value.ASCQ);
_dumpLog.WriteLine(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ);
// Just retry, for 5 times
if(decSense.Value.ASC == 0x29)
{
resets++;
if(resets < 5)
goto deviceGotReset;
if(resets < 5) goto deviceGotReset;
}
if(decSense.Value.ASC == 0x3A)
switch(decSense.Value.ASC)
{
var leftRetries = 5;
while(leftRetries > 0)
case 0x3A:
{
PulseProgress?.Invoke("Waiting for drive to become ready");
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
var leftRetries = 5;
if(!sense)
break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
while(leftRetries > 0)
{
ErrorMessage?.
Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h");
PulseProgress?.Invoke(Localization.Core.Waiting_for_drive_to_become_ready);
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
_dumpLog.WriteLine("Device not ready. Sense {0} ASC {1:X2}h ASCQ {2:X2}h",
decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ);
if(!sense) break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
{
ErrorMessage?.Invoke(string.Format(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ));
_dumpLog.WriteLine(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ);
}
leftRetries--;
}
leftRetries--;
}
if(sense)
{
StoppingErrorMessage?.Invoke("Please insert media in drive");
return;
}
}
else if(decSense.Value.ASC == 0x04 &&
decSense.Value.ASCQ == 0x01)
{
var leftRetries = 50;
while(leftRetries > 0)
{
PulseProgress?.Invoke("Waiting for drive to become ready");
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
if(!sense)
break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
if(sense)
{
ErrorMessage?.
Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h");
StoppingErrorMessage?.Invoke(Localization.Core.Please_insert_media_in_drive);
_dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ);
return;
}
leftRetries--;
break;
}
if(sense)
case 0x04 when decSense.Value.ASCQ == 0x01:
{
StoppingErrorMessage?.
Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}");
var leftRetries = 50;
return;
while(leftRetries > 0)
{
PulseProgress?.Invoke(Localization.Core.Waiting_for_drive_to_become_ready);
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
if(!sense) break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
{
ErrorMessage?.Invoke(string.Format(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ));
_dumpLog.WriteLine(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ);
}
leftRetries--;
}
if(sense)
{
StoppingErrorMessage?.Invoke(string.Format(Localization.Core
.Error_testing_unit_was_ready_0,
Sense.PrettifySense(senseBuf)));
return;
}
break;
}
}
/*else if (decSense.Value.ASC == 0x29 && decSense.Value.ASCQ == 0x00)
/*else if (decSense.Value.ASC == 0x29 && decSense.Value.ASCQ == 0x00)
{
if (!deviceReset)
{
@@ -158,52 +173,58 @@ public partial class Dump
Decoders.SCSI.Sense.PrettifySense(senseBuf)));
return;
}*/
// These should be trapped by the OS but seems in some cases they're not
else if(decSense.Value.ASC == 0x28)
{
var leftRetries = 10;
while(leftRetries > 0)
// These should be trapped by the OS but seems in some cases they're not
case 0x28:
{
PulseProgress?.Invoke("Waiting for drive to become ready");
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
var leftRetries = 10;
if(!sense)
break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
while(leftRetries > 0)
{
ErrorMessage?.
Invoke($"Device not ready. Sense {decSense.Value.SenseKey} ASC {decSense.Value.ASC:X2}h ASCQ {decSense.Value.ASCQ:X2}h");
PulseProgress?.Invoke(Localization.Core.Waiting_for_drive_to_become_ready);
Thread.Sleep(2000);
sense = _dev.ScsiTestUnitReady(out senseBuf, _dev.Timeout, out _);
_dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
decSense.Value.SenseKey, decSense.Value.ASC, decSense.Value.ASCQ);
if(!sense) break;
decSense = Sense.Decode(senseBuf);
if(decSense.HasValue)
{
ErrorMessage?.Invoke(string.Format(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ));
_dumpLog.WriteLine(Localization.Core.Device_not_ready_Sense,
decSense.Value.SenseKey,
decSense.Value.ASC,
decSense.Value.ASCQ);
}
leftRetries--;
}
leftRetries--;
}
if(sense)
{
StoppingErrorMessage?.Invoke(string.Format(Localization.Core
.Error_testing_unit_was_ready_0,
Sense.PrettifySense(senseBuf)));
if(sense)
{
StoppingErrorMessage?.
Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}");
return;
}
break;
}
default:
StoppingErrorMessage?.Invoke(string.Format(Localization.Core.Error_testing_unit_was_ready_0,
Sense.PrettifySense(senseBuf)));
return;
}
}
else
{
StoppingErrorMessage?.Invoke($"Error testing unit was ready:\n{Sense.PrettifySense(senseBuf)}");
return;
}
}
else
{
StoppingErrorMessage?.Invoke("Unknown testing unit was ready.");
StoppingErrorMessage?.Invoke(Localization.Core.Unknown_sense_testing_unit_was_ready);
return;
}
@@ -217,7 +238,7 @@ public partial class Dump
case PeripheralDeviceTypes.SequentialAccess:
if(_dumpRaw)
{
StoppingErrorMessage?.Invoke("Tapes cannot be dumped raw.");
StoppingErrorMessage?.Invoke(Localization.Core.Tapes_cannot_be_dumped_raw);
return;
}
@@ -225,15 +246,20 @@ public partial class Dump
if(_outputPlugin is IWritableTapeImage)
Ssc();
else
StoppingErrorMessage?.
Invoke("The specified plugin does not support storing streaming tape images.");
{
StoppingErrorMessage?.Invoke(Localization.Core
.The_specified_image_format_cannot_represent_streaming_tapes);
}
return;
case PeripheralDeviceTypes.MultiMediaDevice:
if(_outputPlugin is IWritableOpticalImage)
Mmc();
else
StoppingErrorMessage?.Invoke("The specified plugin does not support storing optical disc images.");
{
StoppingErrorMessage?.Invoke(Localization.Core
.The_specified_image_format_cannot_represent_optical_discs);
}
return;
case PeripheralDeviceTypes.BridgingExpander

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Cache.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2020-2024 Rebecca Wallander
// ****************************************************************************/
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.Core.Logging;
using Aaru.Decryption.DVD;
using Humanizer;
using Humanizer.Bytes;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
/// <summary>
/// Dumps data when dumping from a SCSI Block Commands compliant device,
/// and reads the data from the device cache
/// </summary>
/// <param name="blocks">Media blocks</param>
/// <param name="maxBlocksToRead">Maximum number of blocks to read in a single command</param>
/// <param name="blockSize">Block size in bytes</param>
/// <param name="currentTry">Resume information</param>
/// <param name="extents">Correctly dump extents</param>
/// <param name="currentSpeed">Current speed</param>
/// <param name="minSpeed">Minimum speed</param>
/// <param name="maxSpeed">Maximum speed</param>
/// <param name="totalDuration">Total time spent in commands</param>
/// <param name="scsiReader">SCSI reader</param>
/// <param name="mhddLog">MHDD log</param>
/// <param name="ibgLog">ImgBurn log</param>
/// <param name="imageWriteDuration">Total time spent writing to image</param>
/// <param name="newTrim">Set if we need to start a trim</param>
/// <param name="discKey">The DVD disc key</param>
void ReadCacheData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize, DumpHardware currentTry,
ExtentsULong extents, ref double currentSpeed, ref double minSpeed, ref double maxSpeed,
ref double totalDuration, Reader scsiReader, MhddLog mhddLog, IbgLog ibgLog,
ref double imageWriteDuration, ref bool newTrim, byte[] discKey)
{
ulong sectorSpeedStart = 0;
bool sense;
byte[] buffer;
uint blocksToRead = maxBlocksToRead;
var outputFormat = _outputPlugin as IWritableImage;
InitProgress?.Invoke();
if(scsiReader.HldtstReadRaw && _resume.NextBlock > 0)
// The HL-DT-ST buffer is stored and read in 96-sector chunks. If we start to read at an LBA which is
// not modulo 96, the data will not be correctly fetched. Therefore, we begin every resume read with
// filling the buffer at a known offset.
// TODO: This is very ugly and there probably exist a more elegant way to solve this issue.
scsiReader.ReadBlock(out _, _resume.NextBlock - _resume.NextBlock % 96 + 1, out _, out _, out _);
for(ulong i = _resume.NextBlock; i < blocks; i += blocksToRead)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
(long)blocks);
sense = scsiReader.ReadBlocks(out buffer, i, blocksToRead, out double cmdDuration, out _, out _);
totalDuration += cmdDuration;
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration, blocksToRead);
ibgLog.Write(i, currentSpeed * 1024);
_writeStopwatch.Restart();
byte[] tmpBuf;
var cmi = new byte[blocksToRead];
for(uint j = 0; j < blocksToRead; j++)
{
byte[] key = buffer.Skip((int)(2064 * j + 7)).Take(5).ToArray();
if(key.All(static k => k == 0))
{
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], i + j, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys?.Remove(i + j);
continue;
}
CSS.DecryptTitleKey(discKey, key, out tmpBuf);
outputFormat.WriteSectorTag(tmpBuf, i + j, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys?.Remove(i + j);
if(_storeEncrypted) continue;
cmi[j] = buffer[2064 * j + 6];
}
// Todo: Flag in the outputFormat that a sector has been decrypted
if(!_storeEncrypted)
{
ErrorNumber errno =
outputFormat.ReadSectorsTag(i,
blocksToRead,
SectorTagType.DvdTitleKeyDecrypted,
out byte[] titleKey);
if(errno != ErrorNumber.NoError)
{
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_title_key_for_sector_0,
i));
}
else
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi, blocksToRead);
}
outputFormat.WriteSectorsLong(buffer, i, blocksToRead);
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(i, blocksToRead, true);
_mediaGraph?.PaintSectorsGood(i, blocksToRead);
}
else
{
// TODO: Reset device after X errors
if(_stopOnError) return; // TODO: Return more cleanly
if(i + _skip > blocks) _skip = (uint)(blocks - i);
// Write empty data
_writeStopwatch.Restart();
outputFormat.WriteSectors(new byte[blockSize * _skip], i, _skip);
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
_dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
i += _skip - blocksToRead;
newTrim = true;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
_speedStopwatch.Restart();
}
_speedStopwatch.Stop();
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
EndProgress?.Invoke();
}
}

View File

@@ -21,20 +21,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2020-2022 Rebecca Wallander
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2020-2024 Rebecca Wallander
// ****************************************************************************/
using DVDDecryption = Aaru.Decryption.DVD.Dump;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
@@ -42,8 +35,15 @@ using Aaru.Core.Logging;
using Aaru.Decoders.DVD;
using Aaru.Decryption;
using Aaru.Decryption.DVD;
using Aaru.Settings;
using Schemas;
using Humanizer;
using Humanizer.Bytes;
using DVDDecryption = Aaru.Decryption.DVD.Dump;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -64,59 +64,56 @@ partial class Dump
/// <param name="newTrim">Set if we need to start a trim</param>
/// <param name="dvdDecrypt">DVD CSS decryption module</param>
/// <param name="discKey">The DVD disc key</param>
void ReadSbcData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize, DumpHardwareType currentTry,
void ReadSbcData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize, DumpHardware currentTry,
ExtentsULong extents, ref double currentSpeed, ref double minSpeed, ref double maxSpeed,
ref double totalDuration, Reader scsiReader, MhddLog mhddLog, IbgLog ibgLog,
ref double imageWriteDuration, ref bool newTrim, ref DVDDecryption dvdDecrypt, byte[] discKey)
ref double totalDuration, Reader scsiReader, MhddLog mhddLog, IbgLog ibgLog,
ref double imageWriteDuration, ref bool newTrim, ref DVDDecryption dvdDecrypt, byte[] discKey)
{
ulong sectorSpeedStart = 0;
bool sense;
DateTime timeSpeedStart = DateTime.UtcNow;
byte[] buffer;
uint blocksToRead = maxBlocksToRead;
var outputFormat = _outputPlugin as IWritableImage;
ulong sectorSpeedStart = 0;
bool sense;
byte[] buffer;
uint blocksToRead = maxBlocksToRead;
var outputFormat = _outputPlugin as IWritableImage;
InitProgress?.Invoke();
_speedStopwatch.Reset();
for(ulong i = _resume.NextBlock; i < blocks; i += blocksToRead)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(blocks - i < blocksToRead)
blocksToRead = (uint)(blocks - i);
if(blocks - i < blocksToRead) blocksToRead = (uint)(blocks - i);
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i,
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
(long)blocks);
_speedStopwatch.Start();
sense = scsiReader.ReadBlocks(out buffer, i, blocksToRead, out double cmdDuration, out _, out _);
totalDuration += cmdDuration;
_speedStopwatch.Stop();
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
if(Settings.Current.EnableDecryption &&
discKey != null &&
_titleKeys)
if(Settings.Settings.Current.EnableDecryption && discKey != null && _titleKeys)
{
for(ulong j = 0; j < blocksToRead; j++)
{
if(_aborted)
break;
if(_aborted) break;
if(!_resume.MissingTitleKeys.Contains(i + j))
@@ -125,65 +122,40 @@ partial class Dump
byte[] tmpBuf;
bool tmpSense = dvdDecrypt.ReadTitleKey(out tmpBuf, out _, DvdCssKeyClass.DvdCssCppmOrCprm,
i + j, _dev.Timeout, out _);
bool tmpSense = dvdDecrypt.ReadTitleKey(out tmpBuf,
out _,
DvdCssKeyClass.DvdCssCppmOrCprm,
i + j,
_dev.Timeout,
out _);
if(!tmpSense)
if(tmpSense) continue;
CSS_CPRM.TitleKey? titleKey = CSS.DecodeTitleKey(tmpBuf, dvdDecrypt.BusKey);
if(titleKey.HasValue)
outputFormat.WriteSectorTag([titleKey.Value.CMI], i + j, SectorTagType.DvdSectorCmi);
else
continue;
// According to libdvdcss, if the key is all zeroes, the sector is actually
// not encrypted even if the CMI says it is.
if(titleKey.Value.Key.All(static k => k == 0))
{
CSS_CPRM.TitleKey? titleKey = CSS.DecodeTitleKey(tmpBuf, dvdDecrypt.BusKey);
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], i + j, SectorTagType.DvdSectorTitleKey);
if(titleKey.HasValue)
outputFormat.WriteSectorTag(new[]
{
titleKey.Value.CMI
}, i + j, SectorTagType.DvdCmi);
else
continue;
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], i + j, SectorTagType.DvdTitleKeyDecrypted);
// If the CMI bit is 1, the sector is using copy protection, else it is not
if((titleKey.Value.CMI & 0x80) >> 7 == 0)
{
// The CMI indicates this sector is not encrypted.
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, i + j, SectorTagType.DvdTitleKey);
_resume.MissingTitleKeys.Remove(i + j);
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, i + j, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys.Remove(i + j);
continue;
}
// According to libdvdcss, if the key is all zeroes, the sector is actually
// not encrypted even if the CMI says it is.
if(titleKey.Value.Key.All(k => k == 0))
{
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, i + j, SectorTagType.DvdTitleKey);
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, i + j, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys.Remove(i + j);
continue;
}
outputFormat.WriteSectorTag(titleKey.Value.Key, i + j, SectorTagType.DvdTitleKey);
_resume.MissingTitleKeys.Remove(i + j);
CSS.DecryptTitleKey(0, discKey, titleKey.Value.Key, out tmpBuf);
outputFormat.WriteSectorTag(tmpBuf, i + j, SectorTagType.DvdTitleKeyDecrypted);
continue;
}
outputFormat.WriteSectorTag(titleKey.Value.Key, i + j, SectorTagType.DvdSectorTitleKey);
_resume.MissingTitleKeys.Remove(i + j);
CSS.DecryptTitleKey(discKey, titleKey.Value.Key, out tmpBuf);
outputFormat.WriteSectorTag(tmpBuf, i + j, SectorTagType.DvdTitleKeyDecrypted);
}
if(!_storeEncrypted)
@@ -191,84 +163,89 @@ partial class Dump
// Todo: Flag in the outputFormat that a sector has been decrypted
{
ErrorNumber errno =
outputFormat.ReadSectorsTag(i, blocksToRead, SectorTagType.DvdCmi, out byte[] cmi);
outputFormat.ReadSectorsTag(i, blocksToRead, SectorTagType.DvdSectorCmi, out byte[] cmi);
if(errno != ErrorNumber.NoError)
ErrorMessage?.Invoke($"Error retrieving CMI for sector {i}");
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_CMI_for_sector_0, i));
else
{
errno = outputFormat.ReadSectorsTag(i, blocksToRead, SectorTagType.DvdTitleKeyDecrypted,
errno = outputFormat.ReadSectorsTag(i,
blocksToRead,
SectorTagType.DvdTitleKeyDecrypted,
out byte[] titleKey);
if(errno != ErrorNumber.NoError)
ErrorMessage?.Invoke($"Error retrieving title key for sector {i}");
{
ErrorMessage?.Invoke(string.Format(Localization.Core
.Error_retrieving_title_key_for_sector_0,
i));
}
else
buffer = CSS.DecryptSector(buffer, cmi, titleKey, blocksToRead, blockSize);
buffer = CSS.DecryptSector(buffer, titleKey, cmi, blocksToRead, blockSize);
}
}
}
mhddLog.Write(i, cmdDuration);
mhddLog.Write(i, cmdDuration, blocksToRead);
ibgLog.Write(i, currentSpeed * 1024);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(buffer, i, blocksToRead);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(i, blocksToRead, true);
_mediaGraph?.PaintSectorsGood(i, blocksToRead);
}
else
{
if(_dev.Manufacturer.ToLowerInvariant() == "insite")
if(_dev.Manufacturer.Equals("insite", StringComparison.InvariantCultureIgnoreCase))
{
_resume.BadBlocks.Add(i);
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
_resume.NextBlock++;
_aborted = true;
_dumpLog?.
WriteLine("INSITE floptical drives get crazy on the SCSI bus when an error is found, stopping so you can reboot the computer or reset the scsi bus appropriately.");
_dumpLog?.WriteLine(Localization.Core
.INSITE_floptical_drives_get_crazy_on_the_SCSI_bus_when_an_error_is_found);
UpdateStatus?.
Invoke("INSITE floptical drives get crazy on the SCSI bus when an error is found, stopping so you can reboot the computer or reset the scsi bus appropriately");
UpdateStatus?.Invoke(Localization.Core
.INSITE_floptical_drives_get_crazy_on_the_SCSI_bus_when_an_error_is_found);
continue;
}
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
if(i + _skip > blocks)
_skip = (uint)(blocks - i);
if(i + _skip > blocks) _skip = (uint)(blocks - i);
// Write empty data
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(new byte[blockSize * _skip], i, _skip);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
for(ulong b = i; b < i + _skip; b++)
_resume.BadBlocks.Add(b);
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
_dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", _skip, i);
_dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
i += _skip - blocksToRead;
newTrim = true;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0 || sectorSpeedStart * blockSize < 524288) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Reset();
}
_speedStopwatch.Stop();
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
EndProgress?.Invoke();

File diff suppressed because it is too large Load Diff

View File

@@ -21,19 +21,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2020-2022 Rebecca Wallander
// Copyright © 2011-2024 Natalia Portillo
// Copyright © 2020-2024 Rebecca Wallander
// ****************************************************************************/
using DVDDecryption = Aaru.Decryption.DVD.Dump;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
@@ -44,7 +37,13 @@ using Aaru.Decoders.SCSI;
using Aaru.Decryption;
using Aaru.Decryption.DVD;
using Aaru.Devices;
using Schemas;
using DVDDecryption = Aaru.Decryption.DVD.Dump;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -54,7 +53,7 @@ partial class Dump
/// <param name="totalDuration">Total time spent in commands</param>
/// <param name="scsiReader">SCSI reader</param>
/// <param name="blankExtents">Blank extents</param>
void RetrySbcData(Reader scsiReader, DumpHardwareType currentTry, ExtentsULong extents, ref double totalDuration,
void RetrySbcData(Reader scsiReader, DumpHardware currentTry, ExtentsULong extents, ref double totalDuration,
ExtentsULong blankExtents)
{
var pass = 1;
@@ -75,12 +74,25 @@ partial class Dump
Modes.ModePage_01_MMC pgMmc;
Modes.ModePage_01 pg;
sense = _dev.ModeSense6(out buffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
sense = _dev.ModeSense6(out buffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(sense)
Modes.DecodedMode? dcMode6 = null;
if(!sense) dcMode6 = Modes.DecodeMode6(buffer, _dev.ScsiType);
if(sense || dcMode6 is null)
{
sense = _dev.ModeSense10(out buffer, out _, false, ScsiModeSensePageControl.Current, 0x01, _dev.Timeout,
sense = _dev.ModeSense10(out buffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
out _);
if(!sense)
@@ -88,19 +100,23 @@ partial class Dump
Modes.DecodedMode? dcMode10 = Modes.DecodeMode10(buffer, _dev.ScsiType);
if(dcMode10?.Pages != null)
{
foreach(Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage =>
modePage.Page == 0x01 && modePage.Subpage == 0x00))
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
}
}
}
else
{
Modes.DecodedMode? dcMode6 = Modes.DecodeMode6(buffer, _dev.ScsiType);
if(dcMode6?.Pages != null)
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage.Page == 0x01 &&
modePage.Subpage == 0x00))
if(dcMode6.Value.Pages != null)
{
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage is
{
Page: 0x01, Subpage: 0x00
}))
currentModePage = modePage;
}
}
if(currentModePage == null)
@@ -158,15 +174,15 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
}
}
]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
@@ -191,36 +207,36 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
Pages =
[
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01(pg)
}
}
]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
}
UpdateStatus?.Invoke("Sending MODE SELECT to drive (return damaged blocks).");
_dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
sense = _dev.ModeSelect(md6, out byte[] senseBuf, true, false, _dev.Timeout, out _);
if(sense)
sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
if(sense) sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
if(sense)
{
UpdateStatus?.
Invoke("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
UpdateStatus?.Invoke(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
AaruConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
AaruConsole.DebugWriteLine(Localization.Core.Error_0, Sense.PrettifySense(senseBuf));
_dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
_dumpLog.WriteLine(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
}
else
runningPersistent = true;
@@ -235,17 +251,47 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke(string.Format("Retrying sector {0}, pass {1}, {3}{2}", badSector, pass,
forward ? "forward" : "reverse",
runningPersistent ? "recovering partial data, " : ""));
if(forward)
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_forward,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_forward,
badSector,
pass));
}
else
{
PulseProgress?.Invoke(runningPersistent
? string.Format(Localization.Core
.Retrying_sector_0_pass_1_recovering_partial_data_reverse,
badSector,
pass)
: string.Format(Localization.Core.Retrying_sector_0_pass_1_reverse,
badSector,
pass));
}
sense = scsiReader.ReadBlock(out buffer, badSector, out double cmdDuration, out recoveredError,
if(scsiReader.HldtstReadRaw)
// The HL-DT-ST buffer is stored and read in 96-sector chunks. If we start to read at an LBA which is
// not modulo 96, the data will not be correctly fetched. Therefore, we begin every recovery read with
// filling the buffer at a known offset.
// TODO: This is very ugly and there probably exist a more elegant way to solve this issue.
scsiReader.ReadBlock(out _, badSector - badSector % 96 + 1, out _, out _, out _);
sense = scsiReader.ReadBlock(out buffer,
badSector,
out double cmdDuration,
out recoveredError,
out blankCheck);
totalDuration += cmdDuration;
@@ -256,8 +302,8 @@ partial class Dump
blankExtents.Add(badSector, badSector);
newBlank = true;
UpdateStatus?.Invoke($"Found blank block {badSector} in pass {pass}.");
_dumpLog.WriteLine("Found blank block {0} in pass {1}.", badSector, pass);
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_blank_block_0_in_pass_1, badSector, pass));
_dumpLog.WriteLine(Localization.Core.Found_blank_block_0_in_pass_1, badSector, pass);
continue;
}
@@ -267,23 +313,24 @@ partial class Dump
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputFormat.WriteSector(buffer, badSector);
UpdateStatus?.Invoke($"Correctly retried block {badSector} in pass {pass}.");
_dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
_mediaGraph?.PaintSectorGood(badSector);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_block_0_in_pass_1,
badSector,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_block_0_in_pass_1, badSector, pass);
}
else if(runningPersistent)
outputFormat.WriteSector(buffer, badSector);
else if(runningPersistent) outputFormat.WriteSector(buffer, badSector);
}
if(pass < _retryPasses &&
!_aborted &&
_resume.BadBlocks.Count > 0)
if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0)
{
pass++;
forward = !forward;
_resume.BadBlocks.Sort();
if(!forward)
_resume.BadBlocks.Reverse();
if(!forward) _resume.BadBlocks.Reverse();
goto repeatRetry;
}
@@ -293,25 +340,20 @@ partial class Dump
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
Pages = new[]
{
currentModePage.Value
}
Pages = [currentModePage.Value]
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
UpdateStatus?.Invoke("Sending MODE SELECT to drive (return device to previous status).");
_dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
_dumpLog.WriteLine(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
sense = _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _);
if(sense)
_dev.ModeSelect10(md10, out _, true, false, _dev.Timeout, out _);
if(sense) _dev.ModeSelect10(md10, out _, true, false, _dev.Timeout, out _);
}
if(newBlank)
_resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents);
if(newBlank) _resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents).ToArray();
EndProgress?.Invoke();
}
@@ -322,7 +364,8 @@ partial class Dump
var forward = true;
bool sense;
byte[] buffer;
var outputFormat = _outputPlugin as IWritableImage;
if(_outputPlugin is not IWritableImage outputFormat) return;
InitProgress?.Invoke();
@@ -333,79 +376,79 @@ partial class Dump
{
if(_aborted)
{
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke(string.Format("Retrying title key {0}, pass {1}, {2}", missingKey, pass,
forward ? "forward" : "reverse"));
PulseProgress?.Invoke(forward
? string.Format(Localization.Core.Retrying_title_key_0_pass_1_forward,
missingKey,
pass)
: string.Format(Localization.Core.Retrying_title_key_0_pass_1_reverse,
missingKey,
pass));
sense = dvdDecrypt.ReadTitleKey(out buffer, out _, DvdCssKeyClass.DvdCssCppmOrCprm, missingKey,
_dev.Timeout, out double cmdDuration);
sense = dvdDecrypt.ReadTitleKey(out buffer,
out _,
DvdCssKeyClass.DvdCssCppmOrCprm,
missingKey,
_dev.Timeout,
out double cmdDuration);
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
if(sense || _dev.Error) continue;
CSS_CPRM.TitleKey? titleKey = CSS.DecodeTitleKey(buffer, dvdDecrypt.BusKey);
if(!titleKey.HasValue) continue;
outputFormat.WriteSectorTag([titleKey.Value.CMI], missingKey, SectorTagType.DvdSectorCmi);
// If the CMI bit is 1, the sector is using copy protection, else it is not
// If the decoded title key is zeroed, there should be no copy protection
if((titleKey.Value.CMI & 0x80) >> 7 == 0 || titleKey.Value.Key.All(k => k == 0))
{
CSS_CPRM.TitleKey? titleKey = CSS.DecodeTitleKey(buffer, dvdDecrypt.BusKey);
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], missingKey, SectorTagType.DvdSectorTitleKey);
if(titleKey.HasValue)
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], missingKey, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys.Remove(missingKey);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_title_key_0_in_pass_1,
missingKey,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_title_key_0_in_pass_1, missingKey, pass);
}
else
{
outputFormat.WriteSectorTag(titleKey.Value.Key, missingKey, SectorTagType.DvdSectorTitleKey);
_resume.MissingTitleKeys.Remove(missingKey);
if(discKey != null)
{
outputFormat.WriteSectorTag(new[]
{
titleKey.Value.CMI
}, missingKey, SectorTagType.DvdCmi);
// If the CMI bit is 1, the sector is using copy protection, else it is not
// If the decoded title key is zeroed, there should be no copy protection
if((titleKey.Value.CMI & 0x80) >> 7 == 0 ||
titleKey.Value.Key.All(k => k == 0))
{
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, missingKey, SectorTagType.DvdTitleKey);
outputFormat.WriteSectorTag(new byte[]
{
0, 0, 0, 0, 0
}, missingKey, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys.Remove(missingKey);
UpdateStatus?.Invoke($"Correctly retried title key {missingKey} in pass {pass}.");
_dumpLog.WriteLine("Correctly retried title key {0} in pass {1}.", missingKey, pass);
}
else
{
outputFormat.WriteSectorTag(titleKey.Value.Key, missingKey, SectorTagType.DvdTitleKey);
_resume.MissingTitleKeys.Remove(missingKey);
if(discKey != null)
{
CSS.DecryptTitleKey(0, discKey, titleKey.Value.Key, out buffer);
outputFormat.WriteSectorTag(buffer, missingKey, SectorTagType.DvdTitleKeyDecrypted);
}
UpdateStatus?.Invoke($"Correctly retried title key {missingKey} in pass {pass}.");
_dumpLog.WriteLine("Correctly retried title key {0} in pass {1}.", missingKey, pass);
}
CSS.DecryptTitleKey(discKey, titleKey.Value.Key, out buffer);
outputFormat.WriteSectorTag(buffer, missingKey, SectorTagType.DvdTitleKeyDecrypted);
}
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_title_key_0_in_pass_1,
missingKey,
pass));
_dumpLog.WriteLine(Localization.Core.Correctly_retried_title_key_0_in_pass_1, missingKey, pass);
}
}
if(pass < _retryPasses &&
!_aborted &&
_resume.MissingTitleKeys.Count > 0)
if(pass < _retryPasses && !_aborted && _resume.MissingTitleKeys.Count > 0)
{
pass++;
forward = !forward;
_resume.MissingTitleKeys.Sort();
if(!forward)
_resume.MissingTitleKeys.Reverse();
if(!forward) _resume.MissingTitleKeys.Reverse();
goto repeatRetry;
}

View File

@@ -1,20 +1,20 @@
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using System;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Aaru.Console;
using Aaru.Core.Logging;
using Aaru.Decoders.SCSI;
using Aaru.Helpers;
using Schemas;
using Humanizer;
using Humanizer.Bytes;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -37,10 +37,10 @@ partial class Dump
/// <param name="imageWriteDuration">Total time spent writing to image</param>
/// <param name="newTrim">Set if we need to start a trim</param>
/// <param name="blankExtents">Blank extents</param>
void ReadOpticalData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize, DumpHardwareType currentTry,
void ReadOpticalData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize, DumpHardware currentTry,
ExtentsULong extents, ref double currentSpeed, ref double minSpeed, ref double maxSpeed,
ref double totalDuration, Reader scsiReader, MhddLog mhddLog, IbgLog ibgLog,
ref double imageWriteDuration, ref bool newTrim, ref ExtentsULong blankExtents)
ref double totalDuration, Reader scsiReader, MhddLog mhddLog, IbgLog ibgLog,
ref double imageWriteDuration, ref bool newTrim, ref ExtentsULong blankExtents)
{
const uint maxBlocks = 256;
var writtenExtents = new ExtentsULong();
@@ -53,7 +53,6 @@ partial class Dump
bool sense;
byte[] buffer;
ulong sectorSpeedStart = 0;
DateTime timeSpeedStart = DateTime.UtcNow;
var canMediumScan = true;
var outputFormat = _outputPlugin as IWritableImage;
@@ -63,40 +62,46 @@ partial class Dump
{
blankExtents = new ExtentsULong();
written = _dev.MediumScan(out buffer, true, false, false, false, false, 0, 1, 1, out _, out _,
uint.MaxValue, out _);
written = _dev.MediumScan(out buffer,
true,
false,
false,
false,
false,
0,
1,
1,
out _,
out _,
uint.MaxValue,
out _);
DecodedSense? decodedSense = Sense.Decode(buffer);
if(_dev.LastError != 0 ||
decodedSense?.SenseKey == SenseKeys.IllegalRequest)
if(_dev.LastError != 0 || decodedSense?.SenseKey == SenseKeys.IllegalRequest)
{
UpdateStatus?.
Invoke("The current environment doesn't support the medium scan command, dump will take much longer than normal.");
UpdateStatus?.Invoke(Localization.Core.The_current_environment_doesn_t_support_the_medium_scan_command);
canMediumScan = false;
writtenExtents.Add(0, blocks - 1);
}
// TODO: Find a place where MEDIUM SCAN works properly
else if(buffer?.Length > 0 &&
!ArrayHelpers.ArrayIsNullOrEmpty(buffer))
AaruConsole.
WriteLine("Please open a bug report in github with the manufacturer and model of this device, as well as your operating system name and version and this message: This environment correctly supports MEDIUM SCAN command.");
else if(buffer?.Length > 0 && !ArrayHelpers.ArrayIsNullOrEmpty(buffer))
AaruConsole.WriteLine(Localization.Core.MEDIUM_SCAN_github_plead_message);
changingCounter = false;
changingWritten = false;
for(uint b = 0; b < blocks; b += c)
{
if(!canMediumScan)
break;
if(!canMediumScan) break;
if(_aborted)
{
_resume.BlankExtents = null;
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
@@ -114,15 +119,33 @@ partial class Dump
changingCounter = false;
}
if(b + c >= blocks)
c = (uint)(blocks - b);
if(b + c >= blocks) c = (uint)(blocks - b);
UpdateProgress?.
Invoke($"Scanning for {c} {(written ? "written" : "blank")} blocks starting in block {b}", b,
(long)blocks);
UpdateProgress?.Invoke(written
? string.Format(Localization.Core
.Scanning_for_0_written_blocks_starting_in_block_1,
c,
b)
: string.Format(Localization.Core
.Scanning_for_0_blank_blocks_starting_in_block_1,
c,
b),
b,
(long)blocks);
conditionMet = _dev.MediumScan(out _, written, false, false, false, false, b, c, c, out _, out _,
uint.MaxValue, out _);
conditionMet = _dev.MediumScan(out _,
written,
false,
false,
false,
false,
b,
c,
c,
out _,
out _,
uint.MaxValue,
out _);
if(conditionMet)
{
@@ -131,8 +154,7 @@ partial class Dump
else
blankExtents.Add(b, c, true);
if(c < maxBlocks)
changingWritten = true;
if(c < maxBlocks) changingWritten = true;
}
else
{
@@ -143,8 +165,7 @@ partial class Dump
changingCounter = true;
if(c != 0)
continue;
if(c != 0) continue;
written = !written;
c = maxBlocks;
@@ -152,7 +173,7 @@ partial class Dump
}
if(_resume != null && canMediumScan)
_resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents);
_resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents).ToArray();
EndProgress?.Invoke();
}
@@ -167,8 +188,8 @@ partial class Dump
if(writtenExtents.Count == 0)
{
UpdateStatus?.Invoke("Cannot dump empty media!");
_dumpLog.WriteLine("Cannot dump empty media!");
UpdateStatus?.Invoke(Localization.Core.Cannot_dump_empty_media);
_dumpLog.WriteLine(Localization.Core.Cannot_dump_empty_media);
return;
}
@@ -179,91 +200,87 @@ partial class Dump
foreach(Tuple<ulong, ulong> extent in extentsToDump)
{
if(extent.Item2 < _resume.NextBlock)
continue; // Skip this extent
if(extent.Item2 < _resume.NextBlock) continue; // Skip this extent
ulong nextBlock = extent.Item1;
if(extent.Item1 < _resume.NextBlock)
nextBlock = (uint)_resume.NextBlock;
if(extent.Item1 < _resume.NextBlock) nextBlock = (uint)_resume.NextBlock;
_speedStopwatch.Restart();
for(ulong i = nextBlock; i <= extent.Item2; i += blocksToRead)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
if(extent.Item2 + 1 - i < blocksToRead)
blocksToRead = (uint)(extent.Item2 + 1 - i);
if(extent.Item2 + 1 - i < blocksToRead) blocksToRead = (uint)(extent.Item2 + 1 - i);
if(currentSpeed > maxSpeed &&
currentSpeed > 0)
maxSpeed = currentSpeed;
if(currentSpeed > maxSpeed && currentSpeed > 0) maxSpeed = currentSpeed;
if(currentSpeed < minSpeed &&
currentSpeed > 0)
minSpeed = currentSpeed;
if(currentSpeed < minSpeed && currentSpeed > 0) minSpeed = currentSpeed;
UpdateProgress?.Invoke($"Reading sector {i} of {blocks} ({currentSpeed:F3} MiB/sec.)", (long)i,
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
i,
blocks,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)i,
(long)blocks);
sense = scsiReader.ReadBlocks(out buffer, i, blocksToRead, out double cmdDuration, out _, out _);
totalDuration += cmdDuration;
if(!sense &&
!_dev.Error)
if(!sense && !_dev.Error)
{
mhddLog.Write(i, cmdDuration);
mhddLog.Write(i, cmdDuration, blocksToRead);
ibgLog.Write(i, currentSpeed * 1024);
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(buffer, i, blocksToRead);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(i, blocksToRead, true);
}
else
{
// TODO: Reset device after X errors
if(_stopOnError)
return; // TODO: Return more cleanly
if(_stopOnError) return; // TODO: Return more cleanly
if(i + _skip > extent.Item2 + 1)
_skip = (uint)(extent.Item2 + 1 - i);
if(i + _skip > extent.Item2 + 1) _skip = (uint)(extent.Item2 + 1 - i);
// Write empty data
DateTime writeStart = DateTime.Now;
_writeStopwatch.Restart();
outputFormat.WriteSectors(new byte[blockSize * _skip], i, _skip);
imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
for(ulong b = i; b < i + _skip; b++)
_resume.BadBlocks.Add(b);
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
ibgLog.Write(i, 0);
_dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", _skip, i);
_dumpLog.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
i += _skip - blocksToRead;
newTrim = true;
}
_writeStopwatch.Stop();
sectorSpeedStart += blocksToRead;
_resume.NextBlock = i + blocksToRead;
double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
double elapsed = _speedStopwatch.Elapsed.TotalSeconds;
if(elapsed <= 0)
continue;
if(elapsed <= 0) continue;
currentSpeed = sectorSpeedStart * blockSize / (1048576 * elapsed);
sectorSpeedStart = 0;
timeSpeedStart = DateTime.UtcNow;
_speedStopwatch.Restart();
}
}
_speedStopwatch.Stop();
_resume.BadBlocks = _resume.BadBlocks.Distinct().ToList();
EndProgress?.Invoke();

View File

@@ -21,20 +21,18 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2022 Natalia Portillo
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Extents;
using Aaru.CommonTypes.Interfaces;
using Schemas;
namespace Aaru.Core.Devices.Dumping;
partial class Dump
{
@@ -43,18 +41,18 @@ partial class Dump
/// <param name="extents">Correctly dump extents</param>
/// <param name="currentTry">Resume information</param>
/// <param name="blankExtents">Blank extents</param>
void TrimSbcData(Reader scsiReader, ExtentsULong extents, DumpHardwareType currentTry, ExtentsULong blankExtents)
void TrimSbcData(Reader scsiReader, ExtentsULong extents, DumpHardware currentTry, ExtentsULong blankExtents)
{
ulong[] tmpArray = _resume.BadBlocks.ToArray();
bool sense;
bool recoveredError;
bool blankCheck;
byte[] buffer;
var newBlank = false;
var newBlank = false;
if(_outputPlugin is not IWritableImage outputFormat)
{
StoppingErrorMessage?.Invoke("Image is not writable, aborting...");
StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting);
return;
}
@@ -64,13 +62,13 @@ partial class Dump
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke("Aborted!");
_dumpLog.WriteLine("Aborted!");
UpdateStatus?.Invoke(Localization.Core.Aborted);
_dumpLog.WriteLine(Localization.Core.Aborted);
break;
}
PulseProgress?.Invoke($"Trimming sector {badSector}");
PulseProgress?.Invoke(string.Format(Localization.Core.Trimming_sector_0, badSector));
sense = scsiReader.ReadBlock(out buffer, badSector, out double _, out recoveredError, out blankCheck);
@@ -80,22 +78,20 @@ partial class Dump
newBlank = true;
_resume.BadBlocks.Remove(badSector);
UpdateStatus?.Invoke($"Found blank block {badSector}.");
_dumpLog.WriteLine("Found blank block {0}.", badSector);
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_blank_block_0, badSector));
_dumpLog.WriteLine(Localization.Core.Found_blank_block_0, badSector);
continue;
}
if((sense || _dev.Error) &&
!recoveredError)
continue;
if((sense || _dev.Error) && !recoveredError) continue;
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
outputFormat.WriteSector(buffer, badSector);
_mediaGraph?.PaintSectorGood(badSector);
}
if(newBlank)
_resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents);
if(newBlank) _resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents).ToArray();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff