Files
Aaru/Aaru.Core/Devices/Dumping/Sbc/Error.cs

490 lines
19 KiB
C#
Raw Normal View History

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Error.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/>.
//
// ----------------------------------------------------------------------------
2024-12-19 10:45:18 +00:00
// Copyright © 2011-2025 Natalia Portillo
// Copyright © 2020-2025 Rebecca Wallander
// ****************************************************************************/
2025-08-22 19:57:09 +01:00
using System;
using System.Linq;
using Aaru.CommonTypes.AaruMetadata;
2021-01-15 12:36:00 +01:00
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Extents;
2021-12-28 00:14:33 +00:00
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs.Devices.SCSI;
2021-01-15 12:36:00 +01:00
using Aaru.Decoders.DVD;
using Aaru.Decoders.SCSI;
2021-01-15 12:36:00 +01:00
using Aaru.Decryption;
using Aaru.Decryption.DVD;
using Aaru.Devices;
using Aaru.Logging;
using DVDDecryption = Aaru.Decryption.DVD.Dump;
// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope
namespace Aaru.Core.Devices.Dumping;
2022-03-06 13:29:38 +00:00
partial class Dump
{
2022-03-06 13:29:38 +00:00
/// <summary>Retries errored data when dumping from a SCSI Block Commands compliant device</summary>
/// <param name="currentTry">Resume information</param>
/// <param name="extents">Correctly dump extents</param>
/// <param name="totalDuration">Total time spent in commands</param>
/// <param name="scsiReader">SCSI reader</param>
/// <param name="blankExtents">Blank extents</param>
2023-10-03 22:57:50 +01:00
void RetrySbcData(Reader scsiReader, DumpHardware currentTry, ExtentsULong extents, ref double totalDuration,
2025-03-15 21:53:45 +01:00
ExtentsULong blankExtents, byte[] discKey)
{
int pass = 1;
bool forward = true;
bool runningPersistent = false;
2022-03-06 13:29:38 +00:00
bool sense;
byte[] buffer;
bool recoveredError;
Modes.ModePage? currentModePage = null;
byte[] md6;
byte[] md10;
bool blankCheck;
bool newBlank = false;
2022-03-06 13:29:38 +00:00
var outputFormat = _outputPlugin as IWritableImage;
if(_persistent)
{
2022-03-06 13:29:38 +00:00
Modes.ModePage_01_MMC pgMmc;
Modes.ModePage_01 pg;
2024-05-01 04:05:22 +01:00
sense = _dev.ModeSense6(out buffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
2022-03-06 13:29:38 +00:00
out _);
Modes.DecodedMode? dcMode6 = null;
2024-05-01 04:05:22 +01:00
if(!sense) dcMode6 = Modes.DecodeMode6(buffer, _dev.ScsiType);
if(sense || dcMode6 is null)
2022-03-06 13:29:38 +00:00
{
2024-05-01 04:05:22 +01:00
sense = _dev.ModeSense10(out buffer,
out _,
false,
ScsiModeSensePageControl.Current,
0x01,
_dev.Timeout,
2022-03-07 07:36:44 +00:00
out _);
2022-03-06 13:29:38 +00:00
if(!sense)
{
2022-03-06 13:29:38 +00:00
Modes.DecodedMode? dcMode10 = Modes.DecodeMode10(buffer, _dev.ScsiType);
2022-03-06 13:29:38 +00:00
if(dcMode10?.Pages != null)
2023-10-03 22:57:50 +01:00
{
2022-03-06 13:29:38 +00:00
foreach(Modes.ModePage modePage in dcMode10.Value.Pages.Where(modePage =>
2024-05-01 04:05:22 +01:00
modePage is { Page: 0x01, Subpage: 0x00 }))
currentModePage = modePage;
2023-10-03 22:57:50 +01:00
}
}
2022-03-06 13:29:38 +00:00
}
else
{
2024-05-01 23:52:03 +01:00
if(dcMode6.Value.Pages != null)
2023-10-03 22:57:50 +01:00
{
foreach(Modes.ModePage modePage in dcMode6.Value.Pages.Where(modePage => modePage is
2024-05-01 04:05:22 +01:00
{
Page: 0x01, Subpage: 0x00
}))
2022-03-06 13:29:38 +00:00
currentModePage = modePage;
2023-10-03 22:57:50 +01:00
}
2022-03-06 13:29:38 +00:00
}
2022-03-06 13:29:38 +00:00
if(currentModePage == null)
{
if(_dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice)
{
pgMmc = new Modes.ModePage_01_MMC
{
PS = false,
2022-03-06 13:29:38 +00:00
ReadRetryCount = 32,
Parameter = 0x00
};
2022-03-06 13:29:38 +00:00
currentModePage = new Modes.ModePage
{
2022-03-06 13:29:38 +00:00
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
};
}
else
{
pg = new Modes.ModePage_01
{
PS = false,
2022-03-06 13:29:38 +00:00
AWRE = true,
ARRE = true,
TB = false,
RC = false,
EER = true,
PER = false,
2022-03-06 13:29:38 +00:00
DTE = true,
DCR = false,
2022-03-06 13:29:38 +00:00
ReadRetryCount = 32
};
2022-03-06 13:29:38 +00:00
currentModePage = new Modes.ModePage
{
2022-03-06 13:29:38 +00:00
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01(pg)
};
}
2022-03-06 13:29:38 +00:00
}
2022-03-06 13:29:38 +00:00
if(_dev.ScsiType == PeripheralDeviceTypes.MultiMediaDevice)
{
pgMmc = new Modes.ModePage_01_MMC
{
PS = false,
ReadRetryCount = 255,
Parameter = 0x20
};
2022-03-06 13:29:38 +00:00
var md = new Modes.DecodedMode
{
2022-03-06 13:29:38 +00:00
Header = new Modes.ModeHeader(),
2024-05-01 04:39:38 +01:00
Pages =
[
2022-03-06 13:29:38 +00:00
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
}
2024-05-01 04:39:38 +01:00
]
2022-03-06 13:29:38 +00:00
};
2022-03-06 13:29:38 +00:00
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
}
else
{
pg = new Modes.ModePage_01
{
PS = false,
AWRE = false,
ARRE = false,
TB = true,
RC = false,
EER = true,
PER = false,
DTE = false,
DCR = false,
ReadRetryCount = 255
};
2022-03-06 13:29:38 +00:00
var md = new Modes.DecodedMode
{
2022-03-06 13:29:38 +00:00
Header = new Modes.ModeHeader(),
2024-05-01 04:39:38 +01:00
Pages =
[
2022-03-06 13:29:38 +00:00
new Modes.ModePage
{
Page = 0x01,
Subpage = 0x00,
PageResponse = Modes.EncodeModePage_01(pg)
}
2024-05-01 04:39:38 +01:00
]
2022-03-06 13:29:38 +00:00
};
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
}
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_damaged_blocks);
2025-08-22 19:57:09 +01:00
sense = _dev.ModeSelect(md6, out ReadOnlySpan<byte> senseBuf, true, false, _dev.Timeout, out _);
2024-05-01 04:05:22 +01:00
if(sense) sense = _dev.ModeSelect10(md10, out senseBuf, true, false, _dev.Timeout, out _);
2022-03-06 13:29:38 +00:00
if(sense)
{
2024-05-01 04:05:22 +01:00
UpdateStatus?.Invoke(Localization.Core
.Drive_did_not_accept_MODE_SELECT_command_for_persistent_error_reading);
2025-08-17 06:11:22 +01:00
AaruLogging.Debug(Localization.Core.Error_0, Sense.PrettifySense(senseBuf));
2022-03-06 13:29:38 +00:00
}
else
runningPersistent = true;
}
2022-03-06 13:29:38 +00:00
InitProgress?.Invoke();
2023-10-03 22:57:50 +01:00
repeatRetry:
2022-03-06 13:29:38 +00:00
ulong[] tmpArray = _resume.BadBlocks.ToArray();
2022-03-06 13:29:38 +00:00
foreach(ulong badSector in tmpArray)
{
if(_aborted)
{
currentTry.Extents = ExtentsConverter.ToMetadata(extents);
UpdateStatus?.Invoke(Localization.Core.Aborted);
2022-03-06 13:29:38 +00:00
break;
}
if(forward)
2023-10-03 22:57:50 +01:00
{
PulseProgress?.Invoke(runningPersistent
2024-05-01 04:05:22 +01:00
? 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));
2023-10-03 22:57:50 +01:00
}
else
2023-10-03 22:57:50 +01:00
{
PulseProgress?.Invoke(runningPersistent
2024-05-01 04:05:22 +01:00
? 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));
2023-10-03 22:57:50 +01:00
}
if(scsiReader.HldtstReadRaw)
2024-05-01 04:05:22 +01:00
// 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.
2024-05-01 04:05:22 +01:00
scsiReader.ReadBlock(out _, badSector - badSector % 96 + 1, out _, out _, out _);
2024-05-01 04:05:22 +01:00
sense = scsiReader.ReadBlock(out buffer,
badSector,
out double cmdDuration,
out recoveredError,
2022-03-06 13:29:38 +00:00
out blankCheck);
2022-03-06 13:29:38 +00:00
totalDuration += cmdDuration;
2022-03-06 13:29:38 +00:00
if(blankCheck)
{
2022-03-06 13:29:38 +00:00
_resume.BadBlocks.Remove(badSector);
blankExtents.Add(badSector, badSector);
newBlank = true;
UpdateStatus?.Invoke(string.Format(Localization.Core.Found_blank_block_0_in_pass_1, badSector, pass));
2022-03-06 13:29:38 +00:00
continue;
}
2023-10-03 22:57:50 +01:00
if(!sense && !_dev.Error || recoveredError)
{
2022-03-06 13:29:38 +00:00
_resume.BadBlocks.Remove(badSector);
extents.Add(badSector);
2025-03-15 21:53:45 +01:00
if(scsiReader.LiteOnReadRaw || scsiReader.HldtstReadRaw)
{
byte[] cmi = new byte[1];
byte[] key = buffer.Skip(7).Take(5).ToArray();
if(key.All(static k => k == 0))
{
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], badSector, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys?.Remove(badSector);
}
else
{
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
outputFormat.WriteSectorTag(tmpBuf, badSector, SectorTagType.DvdTitleKeyDecrypted);
_resume.MissingTitleKeys?.Remove(badSector);
cmi[0] = buffer[6];
}
if(!_storeEncrypted)
{
ErrorNumber errno =
outputFormat.ReadSectorsTag(badSector,
1,
SectorTagType.DvdTitleKeyDecrypted,
out byte[] titleKey);
if(errno != ErrorNumber.NoError)
{
ErrorMessage?.Invoke(string.Format(Localization.Core
.Error_retrieving_title_key_for_sector_0,
2025-03-15 21:53:45 +01:00
badSector));
}
else
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi);
}
_resume.BadBlocks.Remove(badSector);
outputFormat.WriteSectorLong(buffer, badSector);
}
else
outputFormat.WriteSector(buffer, badSector);
2022-12-05 20:11:59 +00:00
_mediaGraph?.PaintSectorGood(badSector);
2024-05-01 04:05:22 +01:00
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_block_0_in_pass_1,
badSector,
pass));
2022-03-06 13:29:38 +00:00
}
2024-05-01 04:05:22 +01:00
else if(runningPersistent) outputFormat.WriteSector(buffer, badSector);
2022-03-06 13:29:38 +00:00
}
if(pass < _retryPasses && !_aborted && _resume.BadBlocks.Count > 0)
2022-03-06 13:29:38 +00:00
{
pass++;
forward = !forward;
_resume.BadBlocks.Sort();
2024-05-01 04:05:22 +01:00
if(!forward) _resume.BadBlocks.Reverse();
2022-03-06 13:29:38 +00:00
goto repeatRetry;
}
if(runningPersistent && currentModePage.HasValue)
{
var md = new Modes.DecodedMode
{
Header = new Modes.ModeHeader(),
2024-05-01 04:39:38 +01:00
Pages = [currentModePage.Value]
2022-03-06 13:29:38 +00:00
};
2022-03-06 13:29:38 +00:00
md6 = Modes.EncodeMode6(md, _dev.ScsiType);
md10 = Modes.EncodeMode10(md, _dev.ScsiType);
UpdateStatus?.Invoke(Localization.Core.Sending_MODE_SELECT_to_drive_return_device_to_previous_status);
2022-03-06 13:29:38 +00:00
sense = _dev.ModeSelect(md6, out _, true, false, _dev.Timeout, out _);
2024-05-01 04:05:22 +01:00
if(sense) _dev.ModeSelect10(md10, out _, true, false, _dev.Timeout, out _);
}
2021-01-15 12:36:00 +01:00
2024-05-01 04:05:22 +01:00
if(newBlank) _resume.BlankExtents = ExtentsConverter.ToMetadata(blankExtents).ToArray();
2021-01-15 12:36:00 +01:00
2022-03-06 13:29:38 +00:00
EndProgress?.Invoke();
}
void RetryTitleKeys(DVDDecryption dvdDecrypt, byte[] discKey, ref double totalDuration)
{
int pass = 1;
bool forward = true;
2022-03-06 13:29:38 +00:00
bool sense;
byte[] buffer;
2022-11-15 01:35:06 +00:00
2024-05-01 04:05:22 +01:00
if(_outputPlugin is not IWritableImage outputFormat) return;
2021-01-15 12:36:00 +01:00
2022-03-06 13:29:38 +00:00
InitProgress?.Invoke();
2021-01-15 12:36:00 +01:00
2023-10-03 22:57:50 +01:00
repeatRetry:
2022-03-06 13:29:38 +00:00
ulong[] tmpArray = _resume.MissingTitleKeys.ToArray();
foreach(ulong missingKey in tmpArray)
{
if(_aborted)
2021-01-15 12:36:00 +01:00
{
UpdateStatus?.Invoke(Localization.Core.Aborted);
2021-01-15 12:36:00 +01:00
2022-03-06 13:29:38 +00:00
break;
}
PulseProgress?.Invoke(forward
2024-05-01 04:05:22 +01:00
? string.Format(Localization.Core.Retrying_title_key_0_pass_1_forward,
missingKey,
pass)
2024-05-01 04:05:22 +01:00
: string.Format(Localization.Core.Retrying_title_key_0_pass_1_reverse,
missingKey,
pass));
2021-01-15 12:36:00 +01:00
2024-05-01 04:05:22 +01:00
sense = dvdDecrypt.ReadTitleKey(out buffer,
out _,
DvdCssKeyClass.DvdCssCppmOrCprm,
missingKey,
_dev.Timeout,
out double cmdDuration);
2021-01-15 12:36:00 +01:00
2022-03-06 13:29:38 +00:00
totalDuration += cmdDuration;
2021-01-15 12:36:00 +01:00
2024-05-01 04:05:22 +01:00
if(sense || _dev.Error) continue;
2021-01-15 12:36:00 +01:00
CSS_CPRM.TitleKey? titleKey = CSS.DecodeTitleKey(buffer, dvdDecrypt.BusKey);
2021-01-15 12:36:00 +01:00
2024-05-01 04:05:22 +01:00
if(!titleKey.HasValue) continue;
2021-01-15 12:36:00 +01:00
2024-05-01 04:39:38 +01:00
outputFormat.WriteSectorTag([titleKey.Value.CMI], missingKey, SectorTagType.DvdSectorCmi);
2021-01-15 12:36:00 +01:00
// 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))
{
2024-05-01 04:39:38 +01:00
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], missingKey, SectorTagType.DvdSectorTitleKey);
2021-01-15 12:36:00 +01:00
2024-05-01 04:39:38 +01:00
outputFormat.WriteSectorTag([0, 0, 0, 0, 0], missingKey, SectorTagType.DvdTitleKeyDecrypted);
2022-03-06 13:29:38 +00:00
_resume.MissingTitleKeys.Remove(missingKey);
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_title_key_0_in_pass_1,
2024-05-01 04:05:22 +01:00
missingKey,
pass));
}
else
{
outputFormat.WriteSectorTag(titleKey.Value.Key, missingKey, SectorTagType.DvdSectorTitleKey);
_resume.MissingTitleKeys.Remove(missingKey);
if(discKey != null)
{
2023-07-29 23:50:37 +02:00
CSS.DecryptTitleKey(discKey, titleKey.Value.Key, out buffer);
outputFormat.WriteSectorTag(buffer, missingKey, SectorTagType.DvdTitleKeyDecrypted);
2021-01-15 12:36:00 +01:00
}
UpdateStatus?.Invoke(string.Format(Localization.Core.Correctly_retried_title_key_0_in_pass_1,
2024-05-01 04:05:22 +01:00
missingKey,
pass));
2021-01-15 12:36:00 +01:00
}
2022-03-06 13:29:38 +00:00
}
2021-01-15 12:36:00 +01:00
if(pass < _retryPasses && !_aborted && _resume.MissingTitleKeys.Count > 0)
2022-03-06 13:29:38 +00:00
{
pass++;
forward = !forward;
_resume.MissingTitleKeys.Sort();
2021-01-15 12:36:00 +01:00
2024-05-01 04:05:22 +01:00
if(!forward) _resume.MissingTitleKeys.Reverse();
2021-01-15 12:36:00 +01:00
2022-03-06 13:29:38 +00:00
goto repeatRetry;
2021-01-15 12:36:00 +01:00
}
2022-03-06 13:29:38 +00:00
EndProgress?.Invoke();
}
}