Add raw DVD reading with HL-DT-ST command (#823)

This commit is contained in:
Rebecca Wallander
2023-10-16 21:38:11 +02:00
committed by GitHub
parent a2c6b8961a
commit 1153a270a9
11 changed files with 377 additions and 26 deletions

View File

@@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<state> <state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" /> <option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state> </state>
</component> </component>

View File

@@ -0,0 +1,213 @@
// /***************************************************************************
// 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-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();
}
}

View File

@@ -159,7 +159,7 @@ partial class Dump
// According to libdvdcss, if the key is all zeroes, the sector is actually // According to libdvdcss, if the key is all zeroes, the sector is actually
// not encrypted even if the CMI says it is. // 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[] outputFormat.WriteSectorTag(new byte[]
{ {

View File

@@ -776,10 +776,19 @@ partial class Dump
} }
else else
{ {
ReadSbcData(blocks, blocksToRead, blockSize, currentTry, extents, ref currentSpeed, ref minSpeed, mediaTags.TryGetValue(MediaTagType.DVD_DiscKey_Decrypted, out byte[] discKey);
ref maxSpeed, ref totalDuration, scsiReader, mhddLog, ibgLog, ref imageWriteDuration, if(scsiReader.HldtstReadRaw)
ref newTrim, ref dvdDecrypt, {
mediaTags.TryGetValue(MediaTagType.DVD_DiscKey_Decrypted, out byte[] tag) ? tag : null); 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(); _dumpStopwatch.Stop();

View File

@@ -266,6 +266,13 @@ partial class Dump
pass)); 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, sense = scsiReader.ReadBlock(out buffer, badSector, out double cmdDuration, out recoveredError,
out blankCheck); out blankCheck);

View File

@@ -31,6 +31,7 @@
// ****************************************************************************/ // ****************************************************************************/
using System; using System;
using System.Linq;
using Aaru.CommonTypes.Structs.Devices.SCSI; using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Console; using Aaru.Console;
using Aaru.Decoders.SCSI; using Aaru.Decoders.SCSI;
@@ -40,7 +41,7 @@ namespace Aaru.Core.Devices;
sealed partial class Reader sealed partial class Reader
{ {
// TODO: Raw reading // TODO: Raw reading
bool _hldtstReadRaw; public bool HldtstReadRaw;
bool _plextorReadRaw; bool _plextorReadRaw;
bool _read10; bool _read10;
bool _read12; bool _read12;
@@ -478,7 +479,7 @@ sealed partial class Reader
switch(_dev.Manufacturer) switch(_dev.Manufacturer)
{ {
case "HL-DT-ST": 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; break;
case "PLEXTOR": case "PLEXTOR":
@@ -487,7 +488,7 @@ sealed partial class Reader
break; break;
} }
if(_hldtstReadRaw || _plextorReadRaw) if(HldtstReadRaw || _plextorReadRaw)
{ {
CanReadRaw = true; CanReadRaw = true;
LongBlockSize = 2064; LongBlockSize = 2064;
@@ -518,7 +519,7 @@ sealed partial class Reader
AaruConsole.WriteLine(Localization.Core.Using_SyQuest_READ_LONG_10_command); AaruConsole.WriteLine(Localization.Core.Using_SyQuest_READ_LONG_10_command);
else if(_syqReadLong6) else if(_syqReadLong6)
AaruConsole.WriteLine(Localization.Core.Using_SyQuest_READ_LONG_6_command); 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); AaruConsole.WriteLine(Localization.Core.Using_HL_DT_ST_raw_DVD_reading);
else if(_plextorReadRaw) else if(_plextorReadRaw)
AaruConsole.WriteLine(Localization.Core.Using_Plextor_raw_DVD_reading); AaruConsole.WriteLine(Localization.Core.Using_Plextor_raw_DVD_reading);
@@ -584,7 +585,11 @@ sealed partial class Reader
while(true) while(true)
{ {
if(_read6) if(HldtstReadRaw)
{
BlocksToRead = 1;
}
else if(_read6)
{ {
_dev.Read6(out _, out _, 0, LogicalBlockSize, (byte)BlocksToRead, _timeout, out _); _dev.Read6(out _, out _, 0, LogicalBlockSize, (byte)BlocksToRead, _timeout, out _);
@@ -668,10 +673,13 @@ sealed partial class Reader
sense = _dev.SyQuestReadLong6(out buffer, out senseBuf, (uint)block, LongBlockSize, _timeout, sense = _dev.SyQuestReadLong6(out buffer, out senseBuf, (uint)block, LongBlockSize, _timeout,
out duration); out duration);
} }
else if(_hldtstReadRaw) else if(HldtstReadRaw)
{ {
sense = _dev.HlDtStReadRawDvd(out buffer, out senseBuf, (uint)block, LongBlockSize, _timeout, // We need to fill the buffer before reading it with the HL-DT-ST command. We don't care about sense,
out duration); // 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) else if(_plextorReadRaw)
{ {

View File

@@ -30,12 +30,19 @@
// Copyright © 2011-2023 Natalia Portillo // Copyright © 2011-2023 Natalia Portillo
// ****************************************************************************/ // ****************************************************************************/
using System;
using System.Collections.Generic;
using Aaru.CommonTypes.Enums;
using Aaru.Console; using Aaru.Console;
using Aaru.Decoders.DVD;
using Aaru.Helpers;
namespace Aaru.Devices; namespace Aaru.Devices;
public partial class Device public partial class Device
{ {
readonly Sector _decoding = new();
/// <summary>Reads a "raw" sector from DVD on HL-DT-ST drives.</summary> /// <summary>Reads a "raw" sector from DVD on HL-DT-ST drives.</summary>
/// <returns><c>true</c> if the command failed and <paramref name="senseBuffer" /> contains the sense buffer.</returns> /// <returns><c>true</c> if the command failed and <paramref name="senseBuffer" /> contains the sense buffer.</returns>
/// <param name="buffer">Buffer where the HL-DT-ST READ DVD (RAW) response will be stored</param> /// <param name="buffer">Buffer where the HL-DT-ST READ DVD (RAW) response will be stored</param>
@@ -51,15 +58,17 @@ public partial class Device
var cdb = new byte[12]; var cdb = new byte[12];
buffer = new byte[2064 * transferLength]; buffer = new byte[2064 * transferLength];
uint cacheDataOffset = 0x80000000 + (lba % 96 * 2064);
cdb[0] = (byte)ScsiCommands.HlDtStVendor; cdb[0] = (byte)ScsiCommands.HlDtStVendor;
cdb[1] = 0x48; cdb[1] = 0x48;
cdb[2] = 0x49; cdb[2] = 0x49;
cdb[3] = 0x54; cdb[3] = 0x54;
cdb[4] = 0x01; cdb[4] = 0x01;
cdb[6] = (byte)((lba & 0xFF000000) >> 24); cdb[6] = (byte)((cacheDataOffset & 0xFF000000) >> 24);
cdb[7] = (byte)((lba & 0xFF0000) >> 16); cdb[7] = (byte)((cacheDataOffset & 0xFF0000) >> 16);
cdb[8] = (byte)((lba & 0xFF00) >> 8); cdb[8] = (byte)((cacheDataOffset & 0xFF00) >> 8);
cdb[9] = (byte)(lba & 0xFF); cdb[9] = (byte)(cacheDataOffset & 0xFF);
cdb[10] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[10] = (byte)((buffer.Length & 0xFF00) >> 8);
cdb[11] = (byte)(buffer.Length & 0xFF); cdb[11] = (byte)(buffer.Length & 0xFF);
@@ -70,6 +79,38 @@ public partial class Device
AaruConsole.DebugWriteLine(SCSI_MODULE_NAME, Localization.HL_DT_ST_READ_DVD_RAW_took_0_ms, duration); 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; return sense;
} }
/// <summary>
/// Makes sure the data's sector number is the one expected.
/// </summary>
/// <param name="buffer">Data buffer</param>
/// <param name="firstLba">First consecutive LBA of the buffer</param>
/// <param name="transferLength">How many blocks to in buffer</param>
/// <returns><c>false</c> if any sector is not matching expected value, else <c>true</c></returns>
static bool CheckSectorNumber(IReadOnlyList<byte> 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;
}
} }

View File

@@ -577,6 +577,39 @@ public sealed partial class AaruFormat
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes, AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Memory_snapshot_0_bytes,
GC.GetTotalMemory(false)); 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; break;
default: default:
MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type); MediaTagType mediaTagType = GetMediaTagTypeForDataType(blockHeader.type);
@@ -2025,7 +2058,23 @@ public sealed partial class AaruFormat
if(track.Sequence == 0 && track.StartSector == 0 && track.EndSector == 0) if(track.Sequence == 0 && track.StartSector == 0 && track.EndSector == 0)
track.Type = TrackType.Data; track.Type = TrackType.Data;
if(data.Length == 2064 && _imageInfo.MediaType == MediaType.DVDROM) if(data.Length == 2064 &&
(_imageInfo.MediaType == MediaType.DVDROM ||
_imageInfo.MediaType == MediaType.PS2DVD ||
_imageInfo.MediaType == MediaType.SACD ||
_imageInfo.MediaType == MediaType.PS3DVD ||
_imageInfo.MediaType == MediaType.DVDR ||
_imageInfo.MediaType == MediaType.DVDRW ||
_imageInfo.MediaType == MediaType.DVDPR ||
_imageInfo.MediaType == MediaType.DVDPRW ||
_imageInfo.MediaType == MediaType.DVDPRWDL ||
_imageInfo.MediaType == MediaType.DVDRDL ||
_imageInfo.MediaType == MediaType.DVDPRDL ||
_imageInfo.MediaType == MediaType.DVDRAM ||
_imageInfo.MediaType == MediaType.DVDRWDL ||
_imageInfo.MediaType == MediaType.DVDDownload ||
_imageInfo.MediaType == MediaType.Nuon
))
{ {
sector = new byte[2048]; sector = new byte[2048];
_sectorId ??= new byte[_imageInfo.Sectors * 4]; _sectorId ??= new byte[_imageInfo.Sectors * 4];
@@ -2444,6 +2493,21 @@ public sealed partial class AaruFormat
switch(_imageInfo.MediaType) switch(_imageInfo.MediaType)
{ {
case MediaType.DVDROM: case MediaType.DVDROM:
case MediaType.PS2DVD:
case MediaType.SACD:
case MediaType.DVDROM:
case MediaType.DVDR:
case MediaType.DVDRW:
case MediaType.DVDPR:
case MediaType.DVDPRW:
case MediaType.DVDPRWDL:
case MediaType.DVDRDL:
case MediaType.DVDPRDL:
case MediaType.DVDRAM:
case MediaType.DVDRWDL:
case MediaType.DVDDownload:
case MediaType.PS3DVD:
case MediaType.Nuon:
if(data.Length % 2064 != 0) if(data.Length % 2064 != 0)
{ {
ErrorMessage = Localization.Incorrect_data_size; ErrorMessage = Localization.Incorrect_data_size;

View File

@@ -1341,9 +1341,13 @@ public sealed partial class ZZZRawImage
{ {
if(_rawDvd) if(_rawDvd)
{ {
byte[] sector = br.ReadBytes((int)(sectorSize + sectorSkip + sectorOffset)); byte[] sector = br.ReadBytes((int)(sectorSize + sectorSkip + sectorOffset));
sector = _decoding.Scramble(sector); ErrorNumber error = _decoding.Scramble(sector, out byte[] scrambled);
Array.Copy(sector, sectorOffset, buffer, i * sectorSize, sectorSize);
if(error != ErrorNumber.NoError)
return error;
Array.Copy(scrambled, sectorOffset, buffer, i * sectorSize, sectorSize);
} }
else else
{ {
@@ -1741,9 +1745,13 @@ public sealed partial class ZZZRawImage
{ {
for(var i = 0; i < length; i++) for(var i = 0; i < length; i++)
{ {
byte[] sector = br.ReadBytes((int)sectorSize); byte[] sector = br.ReadBytes((int)sectorSize);
sector = _decoding.Scramble(sector); ErrorNumber error = _decoding.Scramble(sector, out byte[] scrambled);
Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize);
if(error != ErrorNumber.NoError)
return error;
Array.Copy(scrambled, 0, buffer, i * sectorSize, sectorSize);
} }
} }
else if(sectorSkip == 0) else if(sectorSkip == 0)