Skip to content

Commit

Permalink
Add raw DVD reading with HL-DT-ST command
Browse files Browse the repository at this point in the history
  • Loading branch information
FakeShemp committed Oct 16, 2023
1 parent a2c6b89 commit 3cb7b34
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 26 deletions.
1 change: 1 addition & 0 deletions .idea/.idea.Aaru/.idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

213 changes: 213 additions & 0 deletions Aaru.Core/Devices/Dumping/Sbc/Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Cache.cs
// Author(s) : Natalia Portillo <[email protected]>
//
// --[ 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-2023 Natalia Portillo
// Copyright © 2020-2023 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;
using DVDDecryption = Aaru.Decryption.DVD.Dump;

// 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;
byte[] 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(new byte[]
{
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();
}
}
2 changes: 1 addition & 1 deletion Aaru.Core/Devices/Dumping/Sbc/Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void ReadSbcData(in ulong blocks, in uint maxBlocksToRead, in uint blockSize

// 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))
if(titleKey.Value.Key.All(static k => k == 0))
{
outputFormat.WriteSectorTag(new byte[]
{
Expand Down
17 changes: 13 additions & 4 deletions Aaru.Core/Devices/Dumping/Sbc/Dump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -776,10 +776,19 @@ void Sbc(Dictionary<MediaTagType, byte[]> mediaTags, MediaType dskType, bool opt
}
else
{
ReadSbcData(blocks, blocksToRead, blockSize, currentTry, extents, ref currentSpeed, ref minSpeed,
ref maxSpeed, ref totalDuration, scsiReader, mhddLog, ibgLog, ref imageWriteDuration,
ref newTrim, ref dvdDecrypt,
mediaTags.TryGetValue(MediaTagType.DVD_DiscKey_Decrypted, out byte[] tag) ? tag : null);
mediaTags.TryGetValue(MediaTagType.DVD_DiscKey_Decrypted, out byte[] discKey);
if(scsiReader.HldtstReadRaw)
{
ReadCacheData(blocks, blocksToRead, blockSize, currentTry, extents, ref currentSpeed, ref minSpeed,
ref maxSpeed, ref totalDuration, scsiReader, mhddLog, ibgLog, ref imageWriteDuration,
ref newTrim, discKey ?? null);
}
else
{
ReadSbcData(blocks, blocksToRead, blockSize, currentTry, extents, ref currentSpeed, ref minSpeed,
ref maxSpeed, ref totalDuration, scsiReader, mhddLog, ibgLog, ref imageWriteDuration,
ref newTrim, ref dvdDecrypt, discKey ?? null);
}
}

_dumpStopwatch.Stop();
Expand Down
7 changes: 7 additions & 0 deletions Aaru.Core/Devices/Dumping/Sbc/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ void RetrySbcData(Reader scsiReader, DumpHardware currentTry, ExtentsULong
pass));
}

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);

Expand Down
24 changes: 16 additions & 8 deletions Aaru.Core/Devices/ReaderSCSI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
// ****************************************************************************/

using System;
using System.Linq;
using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Console;
using Aaru.Decoders.SCSI;
Expand All @@ -40,7 +41,7 @@ namespace Aaru.Core.Devices;
sealed partial class Reader
{
// TODO: Raw reading
bool _hldtstReadRaw;
public bool HldtstReadRaw;
bool _plextorReadRaw;
bool _read10;
bool _read12;
Expand Down Expand Up @@ -478,7 +479,7 @@ bool ScsiFindReadCommand()
switch(_dev.Manufacturer)
{
case "HL-DT-ST":
_hldtstReadRaw = !_dev.HlDtStReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _);
HldtstReadRaw = !_dev.HlDtStReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _);

break;
case "PLEXTOR":
Expand All @@ -487,7 +488,7 @@ bool ScsiFindReadCommand()
break;
}

if(_hldtstReadRaw || _plextorReadRaw)
if(HldtstReadRaw || _plextorReadRaw)
{
CanReadRaw = true;
LongBlockSize = 2064;
Expand Down Expand Up @@ -518,7 +519,7 @@ bool ScsiFindReadCommand()
AaruConsole.WriteLine(Localization.Core.Using_SyQuest_READ_LONG_10_command);
else if(_syqReadLong6)
AaruConsole.WriteLine(Localization.Core.Using_SyQuest_READ_LONG_6_command);
else if(_hldtstReadRaw)
else if(HldtstReadRaw)
AaruConsole.WriteLine(Localization.Core.Using_HL_DT_ST_raw_DVD_reading);
else if(_plextorReadRaw)
AaruConsole.WriteLine(Localization.Core.Using_Plextor_raw_DVD_reading);
Expand Down Expand Up @@ -584,7 +585,11 @@ bool ScsiGetBlocksToRead(uint startWithBlocks)

while(true)
{
if(_read6)
if(HldtstReadRaw)
{
BlocksToRead = 1;
}
else if(_read6)
{
_dev.Read6(out _, out _, 0, LogicalBlockSize, (byte)BlocksToRead, _timeout, out _);

Expand Down Expand Up @@ -668,10 +673,13 @@ bool ScsiReadBlocks(out byte[] buffer, ulong block, uint count, out double durat
sense = _dev.SyQuestReadLong6(out buffer, out senseBuf, (uint)block, LongBlockSize, _timeout,
out duration);
}
else if(_hldtstReadRaw)
else if(HldtstReadRaw)
{
sense = _dev.HlDtStReadRawDvd(out buffer, out senseBuf, (uint)block, LongBlockSize, _timeout,
out duration);
// We need to fill the buffer before reading it with the HL-DT-ST command. We don't care about sense,
// because the data can be wrong anyway, so we need to check the buffer data instead.
_dev.Read12(out buffer, out senseBuf, 0, false, false, false, false, (uint)(block), LogicalBlockSize, 0,
16, false, _timeout, out duration);
sense = _dev.HlDtStReadRawDvd(out buffer, out senseBuf, (uint)block, count, _timeout, out duration);
}
else if(_plextorReadRaw)
{
Expand Down
2 changes: 1 addition & 1 deletion Aaru.Decoders
Submodule Aaru.Decoders updated 1 files
+25 −12 DVD/Sector.cs
2 changes: 1 addition & 1 deletion Aaru.Decryption
Submodule Aaru.Decryption updated 1 files
+40 −0 DVD/CSS.cs
Loading

0 comments on commit 3cb7b34

Please sign in to comment.