diff --git a/.idea/.idea.Aaru/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.Aaru/.idea/codeStyles/codeStyleConfig.xml index 79ee123c2..6e6eec114 100644 --- a/.idea/.idea.Aaru/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/.idea.Aaru/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/Aaru.Core/Devices/Dumping/Sbc/Cache.cs b/Aaru.Core/Devices/Dumping/Sbc/Cache.cs new file mode 100644 index 000000000..c6e727ed4 --- /dev/null +++ b/Aaru.Core/Devices/Dumping/Sbc/Cache.cs @@ -0,0 +1,213 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Cache.cs +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-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 +{ + /// + /// Dumps data when dumping from a SCSI Block Commands compliant device, + /// and reads the data from the device cache + /// + /// Media blocks + /// Maximum number of blocks to read in a single command + /// Block size in bytes + /// Resume information + /// Correctly dump extents + /// Current speed + /// Minimum speed + /// Maximum speed + /// Total time spent in commands + /// SCSI reader + /// MHDD log + /// ImgBurn log + /// Total time spent writing to image + /// Set if we need to start a trim + /// The DVD disc key + 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(); + } +} \ No newline at end of file diff --git a/Aaru.Core/Devices/Dumping/Sbc/Data.cs b/Aaru.Core/Devices/Dumping/Sbc/Data.cs index 1e6da47f4..67f94ec0c 100644 --- a/Aaru.Core/Devices/Dumping/Sbc/Data.cs +++ b/Aaru.Core/Devices/Dumping/Sbc/Data.cs @@ -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[] { diff --git a/Aaru.Core/Devices/Dumping/Sbc/Dump.cs b/Aaru.Core/Devices/Dumping/Sbc/Dump.cs index f51dcb145..d577bb234 100644 --- a/Aaru.Core/Devices/Dumping/Sbc/Dump.cs +++ b/Aaru.Core/Devices/Dumping/Sbc/Dump.cs @@ -776,10 +776,19 @@ void Sbc(Dictionary 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(); diff --git a/Aaru.Core/Devices/Dumping/Sbc/Error.cs b/Aaru.Core/Devices/Dumping/Sbc/Error.cs index 61aeee883..4c8203bdd 100644 --- a/Aaru.Core/Devices/Dumping/Sbc/Error.cs +++ b/Aaru.Core/Devices/Dumping/Sbc/Error.cs @@ -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); diff --git a/Aaru.Core/Devices/ReaderSCSI.cs b/Aaru.Core/Devices/ReaderSCSI.cs index a43fa5543..43df1f1b7 100644 --- a/Aaru.Core/Devices/ReaderSCSI.cs +++ b/Aaru.Core/Devices/ReaderSCSI.cs @@ -31,6 +31,7 @@ // ****************************************************************************/ using System; +using System.Linq; using Aaru.CommonTypes.Structs.Devices.SCSI; using Aaru.Console; using Aaru.Decoders.SCSI; @@ -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; @@ -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": @@ -487,7 +488,7 @@ bool ScsiFindReadCommand() break; } - if(_hldtstReadRaw || _plextorReadRaw) + if(HldtstReadRaw || _plextorReadRaw) { CanReadRaw = true; LongBlockSize = 2064; @@ -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); @@ -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 _); @@ -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) { diff --git a/Aaru.Decoders b/Aaru.Decoders index 089ba5273..a0bb12a7c 160000 --- a/Aaru.Decoders +++ b/Aaru.Decoders @@ -1 +1 @@ -Subproject commit 089ba527329920d2106af7f7d190a8bcb3f37a13 +Subproject commit a0bb12a7c681239ab196a080e6891cd2a4811693 diff --git a/Aaru.Decryption b/Aaru.Decryption index 605e00ed8..3aa48889d 160000 --- a/Aaru.Decryption +++ b/Aaru.Decryption @@ -1 +1 @@ -Subproject commit 605e00ed85de6a875ff930b039565460ba43c7df +Subproject commit 3aa48889dd89ef22d346328bda8e23668dcd196a diff --git a/Aaru.Devices/Device/ScsiCommands/HL-DT-ST.cs b/Aaru.Devices/Device/ScsiCommands/HL-DT-ST.cs index b4f9d0cb3..2deeea2ca 100644 --- a/Aaru.Devices/Device/ScsiCommands/HL-DT-ST.cs +++ b/Aaru.Devices/Device/ScsiCommands/HL-DT-ST.cs @@ -30,12 +30,19 @@ // Copyright © 2011-2023 Natalia Portillo // ****************************************************************************/ +using System; +using System.Collections.Generic; +using Aaru.CommonTypes.Enums; using Aaru.Console; +using Aaru.Decoders.DVD; +using Aaru.Helpers; namespace Aaru.Devices; public partial class Device { + readonly Sector _decoding = new(); + /// Reads a "raw" sector from DVD on HL-DT-ST drives. /// true if the command failed and contains the sense buffer. /// Buffer where the HL-DT-ST READ DVD (RAW) response will be stored @@ -51,15 +58,17 @@ public bool HlDtStReadRawDvd(out byte[] buffer, out byte[] senseBuffer, uint lba var cdb = new byte[12]; buffer = new byte[2064 * transferLength]; + uint cacheDataOffset = 0x80000000 + (lba % 96 * 2064); + cdb[0] = (byte)ScsiCommands.HlDtStVendor; cdb[1] = 0x48; cdb[2] = 0x49; cdb[3] = 0x54; cdb[4] = 0x01; - cdb[6] = (byte)((lba & 0xFF000000) >> 24); - cdb[7] = (byte)((lba & 0xFF0000) >> 16); - cdb[8] = (byte)((lba & 0xFF00) >> 8); - cdb[9] = (byte)(lba & 0xFF); + cdb[6] = (byte)((cacheDataOffset & 0xFF000000) >> 24); + cdb[7] = (byte)((cacheDataOffset & 0xFF0000) >> 16); + cdb[8] = (byte)((cacheDataOffset & 0xFF00) >> 8); + cdb[9] = (byte)(cacheDataOffset & 0xFF); cdb[10] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[11] = (byte)(buffer.Length & 0xFF); @@ -70,6 +79,38 @@ public bool HlDtStReadRawDvd(out byte[] buffer, out byte[] senseBuffer, uint lba AaruConsole.DebugWriteLine(SCSI_MODULE_NAME, Localization.HL_DT_ST_READ_DVD_RAW_took_0_ms, duration); + if(!CheckSectorNumber(buffer, lba, transferLength)) + return true; + + if(_decoding.Scramble(buffer, transferLength, out byte[] scrambledBuffer) != ErrorNumber.NoError) + return true; + + buffer = scrambledBuffer; return sense; } + + /// + /// Makes sure the data's sector number is the one expected. + /// + /// Data buffer + /// First consecutive LBA of the buffer + /// How many blocks to in buffer + /// false if any sector is not matching expected value, else true + static bool CheckSectorNumber(IReadOnlyList buffer, uint firstLba, uint transferLength) + { + for(int i = 0; i < transferLength; i++) + { + byte[] sectorBuffer = + { + 0x0, buffer[1 + (2064 * i)], buffer[2 + (2064 * i)], buffer[3 + (2064 * i)] + }; + + uint sectorNumber = BigEndianBitConverter.ToUInt32(sectorBuffer, 0); + + if(sectorNumber != firstLba + i + 0x30000) + return false; + } + + return true; + } } \ No newline at end of file diff --git a/Aaru.Images/AaruFormat/Write.cs b/Aaru.Images/AaruFormat/Write.cs index 8a6dcc493..e693fe7e2 100644 --- a/Aaru.Images/AaruFormat/Write.cs +++ b/Aaru.Images/AaruFormat/Write.cs @@ -577,6 +577,39 @@ public bool Create(string path, MediaType mediaType, Dictionary AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, GC.GetTotalMemory(false)); + break; + case DataType.DvdSectorId: + _sectorId = data; + + if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorInformation)) + _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorInformation); + + if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorNumber)) + _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorNumber); + + AaruConsole.DebugWriteLine("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, + GC.GetTotalMemory(false)); + + break; + case DataType.DvdSectorIed: + _sectorIed = data; + + if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorIed)) + _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorIed); + + AaruConsole.DebugWriteLine("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, + GC.GetTotalMemory(false)); + + break; + case DataType.DvdSectorEdc: + _sectorEdc = data; + + if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.DvdSectorEdc)) + _imageInfo.ReadableSectorTags.Add(SectorTagType.DvdSectorEdc); + + AaruConsole.DebugWriteLine("Aaru Format plugin", Localization.Memory_snapshot_0_bytes, + GC.GetTotalMemory(false)); + break; default: MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); @@ -2025,7 +2058,9 @@ public bool WriteSectorLong(byte[] data, ulong sectorAddress) if(track.Sequence == 0 && track.StartSector == 0 && track.EndSector == 0) track.Type = TrackType.Data; - if(data.Length == 2064 && _imageInfo.MediaType == MediaType.DVDROM) + if(data.Length == 2064 && + (_imageInfo.MediaType == MediaType.DVDROM || + _imageInfo.MediaType == MediaType.PS2DVD)) { sector = new byte[2048]; _sectorId ??= new byte[_imageInfo.Sectors * 4]; @@ -2444,6 +2479,7 @@ public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) switch(_imageInfo.MediaType) { case MediaType.DVDROM: + case MediaType.PS2DVD: if(data.Length % 2064 != 0) { ErrorMessage = Localization.Incorrect_data_size; diff --git a/Aaru.Images/ZZZRawImage/Read.cs b/Aaru.Images/ZZZRawImage/Read.cs index ceaa2c75f..cbaa249d8 100644 --- a/Aaru.Images/ZZZRawImage/Read.cs +++ b/Aaru.Images/ZZZRawImage/Read.cs @@ -1341,9 +1341,13 @@ public ErrorNumber ReadSectors(ulong sectorAddress, uint length, out byte[] buff { if(_rawDvd) { - byte[] sector = br.ReadBytes((int)(sectorSize + sectorSkip + sectorOffset)); - sector = _decoding.Scramble(sector); - Array.Copy(sector, sectorOffset, buffer, i * sectorSize, sectorSize); + byte[] sector = br.ReadBytes((int)(sectorSize + sectorSkip + sectorOffset)); + ErrorNumber error = _decoding.Scramble(sector, out byte[] scrambled); + + if(error != ErrorNumber.NoError) + return error; + + Array.Copy(scrambled, sectorOffset, buffer, i * sectorSize, sectorSize); } else { @@ -1741,9 +1745,13 @@ public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, out byte[] { for(var i = 0; i < length; i++) { - byte[] sector = br.ReadBytes((int)sectorSize); - sector = _decoding.Scramble(sector); - Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize); + byte[] sector = br.ReadBytes((int)sectorSize); + ErrorNumber error = _decoding.Scramble(sector, out byte[] scrambled); + + if(error != ErrorNumber.NoError) + return error; + + Array.Copy(scrambled, 0, buffer, i * sectorSize, sectorSize); } } else if(sectorSkip == 0)