From 3fd37e6afea39e1f53e46a480f96a773a47db694 Mon Sep 17 00:00:00 2001 From: chudov Date: Sun, 7 Dec 2008 00:33:20 +0000 Subject: [PATCH] More robustness to ripper --- Bwg.Scsi/Device.cs | 47 +++++-- CUETools.Ripper.Console/Program.cs | 10 ++ CUETools.Ripper.SCSI/SCSIDrive.cs | 218 ++++++++++++++++++++--------- 3 files changed, 198 insertions(+), 77 deletions(-) diff --git a/Bwg.Scsi/Device.cs b/Bwg.Scsi/Device.cs index f2a119b..1571e50 100644 --- a/Bwg.Scsi/Device.cs +++ b/Bwg.Scsi/Device.cs @@ -2131,11 +2131,31 @@ namespace Bwg.Scsi return CommandStatus.Success; } + public enum SubChannelMode + { + None, + QOnly, /// + 16 bytes + RWMode /// + 96 bytes + }; + + public enum C2ErrorMode + { + None, + Mode294, /// +294 bytes + Mode296, /// +296 bytes + }; + + public enum MainChannelSelection + { + UserData, + F8h + }; + /// /// /// - /// subchannel mode (+16 bytes if equals 2) - /// report C2 errors (+296 bytes if true) + /// subchannel mode + /// C2 errors report mode /// expected sector type /// /// @@ -2143,28 +2163,33 @@ namespace Bwg.Scsi /// the memory area /// the size of the memory area given by the data parameter /// - public CommandStatus ReadCDAndSubChannel(byte mode, bool c2, byte exp, bool dap, uint start, uint length, IntPtr data, int size) + public CommandStatus ReadCDAndSubChannel(MainChannelSelection mainmode, SubChannelMode submode, C2ErrorMode c2mode, byte exp, bool dap, uint start, uint length, IntPtr data, int timeout) { if (m_logger != null) { - string args = exp.ToString() + ", " + dap.ToString() + ", " + start.ToString() + ", " + length.ToString() + ", data, " + size.ToString(); + string args = exp.ToString() + ", " + dap.ToString() + ", " + start.ToString() + ", " + length.ToString() + ", data"; m_logger.LogMessage(new UserMessage(UserMessage.Category.Debug, 8, "Bwg.Scsi.Device.ReadCD(" + args + ")")); } - if (mode != 1 && mode != 2 && mode != 4) - throw new Exception("invalid read mode for ReadSubchannel() call"); + int size = (4 * 588 + + (submode == SubChannelMode.QOnly ? 16 : submode == SubChannelMode.RWMode ? 96 : 0) + + (c2mode == C2ErrorMode.Mode294 ? 294 : c2mode == C2ErrorMode.Mode296 ? 296 : 0)) * (int) length; + + byte mode = (byte) (submode == SubChannelMode.QOnly ? 2 : submode == SubChannelMode.RWMode ? 4 : 0); if (exp != 1 && exp != 2 && exp != 3 && exp != 4 && exp != 5) return CommandStatus.NotSupported; - using (Command cmd = new Command(ScsiCommandCode.ReadCd, 12, data, size, Command.CmdDirection.In, 5 * 60)) + using (Command cmd = new Command(ScsiCommandCode.ReadCd, 12, data, size, Command.CmdDirection.In, timeout)) { byte b = (byte)((exp & 0x07) << 2); if (dap) b |= 0x02; - byte byte9 = 0x10; - if (c2) + byte byte9 = (byte) (mainmode == MainChannelSelection.UserData ? 0x10 : 0xF8); + if (c2mode == C2ErrorMode.Mode294) byte9 |= 0x02; + else if (c2mode == C2ErrorMode.Mode296) + byte9 |= 0x04; cmd.SetCDB8(1, b); cmd.SetCDB32(2, start); cmd.SetCDB24(6, length); @@ -2250,7 +2275,7 @@ namespace Bwg.Scsi /// /// the subchannel mode /// - public CommandStatus ReadSubChannel(byte mode, uint sector, uint length, ref byte[] data) + public CommandStatus ReadSubChannel(byte mode, uint sector, uint length, ref byte[] data, int timeout) { byte bytes_per_sector; @@ -2271,7 +2296,7 @@ namespace Bwg.Scsi throw new Exception("data buffer is not large enough to hold the data requested"); - using (Command cmd = new Command(ScsiCommandCode.ReadCd, 12, (int)(length * bytes_per_sector), Command.CmdDirection.In, 10 * 60)) + using (Command cmd = new Command(ScsiCommandCode.ReadCd, 12, (int)(length * bytes_per_sector), Command.CmdDirection.In, timeout)) { cmd.SetCDB32(2, sector); // The sector number to start with cmd.SetCDB24(6, length); // The length in sectors diff --git a/CUETools.Ripper.Console/Program.cs b/CUETools.Ripper.Console/Program.cs index 26edf2e..08d0a8c 100644 --- a/CUETools.Ripper.Console/Program.cs +++ b/CUETools.Ripper.Console/Program.cs @@ -79,6 +79,7 @@ namespace CUERipper Console.WriteLine("-P, --paranoid maximum level of error correction;"); Console.WriteLine("-D, --drive use a specific CD drive, e.g. {0};", drives); Console.WriteLine("-O, --offset use specific drive read offset;"); + Console.WriteLine("-T, --test detect read command;"); } static void Main(string[] args) @@ -92,11 +93,14 @@ namespace CUERipper int correctionQuality = 1; string driveLetter = null; int driveOffset = 0; + bool test = false; for (int arg = 0; arg < args.Length; arg++) { bool ok = true; if (args[arg] == "-P" || args[arg] == "--paranoid") correctionQuality = 4; + if (args[arg] == "-T" || args[arg] == "--test") + test = true; //else if (args[arg] == "-B" || args[arg] == "--burst") // correctionQuality = 1; else if ((args[arg] == "-D" || args[arg] == "--drive") && ++arg < args.Length) @@ -145,8 +149,14 @@ namespace CUERipper if (!AccurateRipVerify.FindDriveReadOffset(audioSource.ARName, out driveOffset)) Console.WriteLine("Unknown read offset for drive {0}!!!", audioSource.Path); //throw new Exception("Failed to find drive read offset for drive" + audioSource.ARName); + if (test) + { + Console.Write(audioSource.TestReadCommand()); + return; + } audioSource.DriveOffset = driveOffset; audioSource.CorrectionQuality = correctionQuality; + audioSource.DebugMessages = true; AccurateRipVerify arVerify = new AccurateRipVerify(audioSource.TOC); int[,] buff = new int[audioSource.BestBlockSize, audioSource.ChannelCount]; diff --git a/CUETools.Ripper.SCSI/SCSIDrive.cs b/CUETools.Ripper.SCSI/SCSIDrive.cs index 4676b11..390af4c 100644 --- a/CUETools.Ripper.SCSI/SCSIDrive.cs +++ b/CUETools.Ripper.SCSI/SCSIDrive.cs @@ -30,6 +30,7 @@ using Bwg.Scsi; using Bwg.Logging; using CUETools.CDImage; using CUETools.Codecs; +using System.Threading; namespace CUETools.Ripper.SCSI { @@ -44,24 +45,26 @@ namespace CUETools.Ripper.SCSI int _driveOffset = 0; int _correctionQuality = 1; int _currentStart = -1, _currentEnd = -1, _currentErrorsCount = 0; - const bool DoC2 = true; - const int CB_AUDIO = 588 * 4 + 16 + (DoC2 ? 294 : 0); - //const int REDUNDANCY = 8; - const int NSECTORS = 64; //255 - REDUNDANCY; + const int CB_AUDIO = 588 * 4 + 2 + 294 + 16; + const int NSECTORS = 32; const int MSECTORS = 10000000 / CB_AUDIO; int _currentTrack = -1, _currentIndex = -1, _currentTrackActualStart = -1; Logger m_logger; CDImageLayout _toc; DeviceInfo m_info; Crc16Ccitt _crc; - //RsEncode _rsEncoder; - //RsDecode _rsDecoder; List _scanResults; ScanResults _currentScan; BitArray _errors; int _errorsCount; byte[] _currentData = new byte[MSECTORS * 4 * 588]; int[] valueScore = new int[256]; + bool _debugMessages = false; + Device.MainChannelSelection _mainChannelMode = Device.MainChannelSelection.UserData; + Device.SubChannelMode _subChannelMode = Device.SubChannelMode.None; + Device.C2ErrorMode _c2ErrorMode = Device.C2ErrorMode.Mode296; + byte[] _readBuffer = new byte[NSECTORS * CB_AUDIO]; + byte[] _subchannelBuffer = new byte[NSECTORS * 16]; public event EventHandler ReadProgress; @@ -89,12 +92,22 @@ namespace CUETools.Ripper.SCSI } } + public bool DebugMessages + { + get + { + return _debugMessages; + } + set + { + _debugMessages = value; + } + } + public CDDriveReader() { m_logger = new Logger(); _crc = new Crc16Ccitt(InitialCrcValue.Zeros); - //_rsEncoder = new RsEncode(REDUNDANCY); - //_rsDecoder = new RsDecode(REDUNDANCY); } public bool Open(char Drive) @@ -186,8 +199,9 @@ namespace CUETools.Ripper.SCSI } } - private void ProcessSubchannel(int sector, int Sectors2Read, bool updateMap) + private int ProcessSubchannel(int sector, int Sectors2Read, bool updateMap) { + int posCount = 0; for (int iSector = 0; iSector < Sectors2Read; iSector++) { int q_pos = (sector - _currentStart + iSector + 1) * CB_AUDIO - 16; @@ -205,27 +219,32 @@ namespace CUETools.Ripper.SCSI int ff = fromBCD(_currentScan.Data[q_pos + 9]); int sec = ff + 75 * (ss + 60 * mm) - 150; // sector + iSector; //if (sec != sector + iSector) - // System.Console.WriteLine("\rLost sync: {0} vs {1} ({2:X} vs {3:X})", CDImageLayout.TimeToString((uint)(sector + iSector)), CDImageLayout.TimeToString((uint)sec), sector + iSector, sec); - //ushort crc = _crc.ComputeChecksum(_currentScan.Data, q_pos, 10); - //crc ^= 0xffff; - //if (_currentScan.Data[q_pos + 10] != 0 && _currentScan.Data[q_pos + 11] != 0 && - // ((crc & 0xff) != _currentScan.Data[q_pos + 11] || - // (crc >> 8) != _currentScan.Data[q_pos + 10]) - // ) - //{ - // System.Console.WriteLine("CRC error at {0}", CDImageLayout.TimeToString((uint)(sector + iSector))); - //} + // System.Console.WriteLine("\rLost sync: {0} vs {1} ({2:X} vs {3:X})", CDImageLayout.TimeToString((uint)(sector + iSector)), CDImageLayout.TimeToString((uint)sec), sector + iSector, sec); + ushort crc = _crc.ComputeChecksum(_currentScan.Data, q_pos, 10); + crc ^= 0xffff; + if (_currentScan.Data[q_pos + 10] != 0 && _currentScan.Data[q_pos + 11] != 0 && + ((crc & 0xff) != _currentScan.Data[q_pos + 11] || + (crc >> 8) != _currentScan.Data[q_pos + 10]) + ) + { + if (_debugMessages) + System.Console.WriteLine("\nCRC error at {0}", CDImageLayout.TimeToString((uint)(sector + iSector))); + continue; + } if (iTrack == 110) { if (sector + iSector + 75 < _toc.AudioLength) throw new Exception("lead out area encountred"); // make sure that data is zero? - return; + return posCount; } if (iTrack == 0) throw new Exception("lead in area encountred"); + posCount++; if (!updateMap) break; + if (iTrack > _toc.AudioTracks) + throw new Exception("strange track number encountred"); if (iTrack != _currentTrack) { _currentTrack = iTrack; @@ -265,13 +284,12 @@ namespace CUETools.Ripper.SCSI if (_toc[_currentTrack].ISRC == null) { StringBuilder isrc = new StringBuilder(); - char[] ISRC6 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '#', '#', '#', '#', '#', '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; - isrc.Append(ISRC6[_currentScan.Data[q_pos + 1] >> 2]); - isrc.Append(ISRC6[((_currentScan.Data[q_pos + 1] & 0x3) << 4) + (_currentScan.Data[q_pos + 2] >> 4)]); - isrc.Append(ISRC6[((_currentScan.Data[q_pos + 2] & 0xf) << 2) + (_currentScan.Data[q_pos + 3] >> 6)]); - isrc.Append(ISRC6[(_currentScan.Data[q_pos + 3] & 0x3f)]); - isrc.Append(ISRC6[_currentScan.Data[q_pos + 4] >> 2]); - isrc.Append(ISRC6[((_currentScan.Data[q_pos + 4] & 0x3) << 4) + (_currentScan.Data[q_pos + 5] >> 4)]); + isrc.Append(from6bit(_currentScan.Data[q_pos + 1] >> 2)); + isrc.Append(from6bit(((_currentScan.Data[q_pos + 1] & 0x3) << 4) + (0x0f & (_currentScan.Data[q_pos + 2] >> 4)))); + isrc.Append(from6bit(((_currentScan.Data[q_pos + 2] & 0xf) << 2) + (0x03 & (_currentScan.Data[q_pos + 3] >> 6)))); + isrc.Append(from6bit((_currentScan.Data[q_pos + 3] & 0x3f))); + isrc.Append(from6bit(_currentScan.Data[q_pos + 4] >> 2)); + isrc.Append(from6bit(((_currentScan.Data[q_pos + 4] & 0x3) << 4) + (0x0f & (_currentScan.Data[q_pos + 5] >> 4)))); isrc.AppendFormat("{0:x}", _currentScan.Data[q_pos + 5] & 0xf); isrc.AppendFormat("{0:x2}", _currentScan.Data[q_pos + 6]); isrc.AppendFormat("{0:x2}", _currentScan.Data[q_pos + 7]); @@ -281,11 +299,57 @@ namespace CUETools.Ripper.SCSI break; } } + return posCount; + } + + public unsafe string TestReadCommand() + { + byte[] test = new byte[CB_AUDIO]; + string s = ""; + fixed (byte* data = test) + { + for (int m = 0; m <= 1; m++) + for (int q = 0; q <= 2; q++) + for (int c = 0; c <= 2; c++) + { + Device.MainChannelSelection[] mainmode = { Device.MainChannelSelection.UserData, Device.MainChannelSelection.F8h }; + Device.SubChannelMode[] submode = { Device.SubChannelMode.None, Device.SubChannelMode.QOnly, Device.SubChannelMode.RWMode }; + Device.C2ErrorMode[] c2mode = { Device.C2ErrorMode.None, Device.C2ErrorMode.Mode294, Device.C2ErrorMode.Mode296 }; + Device.CommandStatus st = m_device.ReadCDAndSubChannel(mainmode[m], submode[q], c2mode[c], 1, false, (uint)0, (uint)1, (IntPtr)((void*)data), 3); + s += string.Format("M{0}Q{1}C{2}: {3}\n", m, q, c, (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString())); + } + } + return s; + } + + + private void ReorganiseSectors(int sector, int Sectors2Read) + { + if (_subChannelMode == Device.SubChannelMode.QOnly && _c2ErrorMode == Device.C2ErrorMode.Mode296) + { + Array.Copy(_readBuffer, 0, _currentScan.Data, (sector - _currentStart) * CB_AUDIO, Sectors2Read * CB_AUDIO); + return; + } + // fill Q subchannel + if (_subChannelMode == Device.SubChannelMode.None) + { + Device.CommandStatus st = m_device.ReadSubChannel(2, (uint)sector, (uint)Sectors2Read, ref _subchannelBuffer, 10); + if (st != Device.CommandStatus.Success) + throw new Exception("ReadSubChannel: " + (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString())); + for (int iSector = 0; iSector < Sectors2Read; iSector++) + Array.Copy(_subchannelBuffer, iSector * 16, _currentScan.Data, (sector - _currentStart + iSector + 1) * CB_AUDIO - 16, 16); + } + int c2Size = _c2ErrorMode == Device.C2ErrorMode.None ? 0 : _c2ErrorMode == Device.C2ErrorMode.Mode294 ? 294 : 296; + int oldSize = 4 * 588 + c2Size + (_subChannelMode == Device.SubChannelMode.None ? 0 : 16); + // fill Data & C2 + // TODO: handle other c2 modes here... + for (int iSector = 0; iSector < Sectors2Read; iSector++) + Array.Copy(_readBuffer, iSector * oldSize, _currentScan.Data, (sector - _currentStart + iSector) * CB_AUDIO, 4 * 588 + 296); } private unsafe void FetchSectors(int sector, int Sectors2Read) { - fixed (byte* data = _currentScan.Data) + fixed (byte* data = _readBuffer) { //Device.CommandStatus st; //using (Command cmd = new Command(ScsiCommandCode.Read12, 12, (IntPtr)((void*)data), Sectors2Read * 588 * 4, Command.CmdDirection.In, 5 * 60)) @@ -296,42 +360,46 @@ namespace CUETools.Ripper.SCSI // //cmd.SetCDB8(10, 0x80); // streaming // st= m_device.SendCommand(cmd); //} - Device.CommandStatus st = m_device.ReadCDAndSubChannel(2, DoC2, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)(data + (sector - _currentStart) * CB_AUDIO)), Sectors2Read * CB_AUDIO); - if (st != Device.CommandStatus.Success) + Device.CommandStatus st = m_device.ReadCDAndSubChannel(_mainChannelMode, _subChannelMode, _c2ErrorMode, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)data), 10); + if (st == Device.CommandStatus.Success) { - if (st == Device.CommandStatus.DeviceFailed && m_device.GetSenseAsc() == 0x64 && m_device.GetSenseAscq() == 0x00) - { - int iErrors = 0; - for (int iSector = 0; iSector < Sectors2Read; iSector++) - { - st = m_device.ReadCDAndSubChannel(2, DoC2, 1, false, (uint)(sector + iSector), 1U, (IntPtr)((void*)(data + (sector + iSector - _currentStart) * CB_AUDIO)), CB_AUDIO); - if (st != Device.CommandStatus.Success) - { - iErrors ++; - for (int iPos = 0; iPos < CB_AUDIO; iPos++) - data[(sector + iSector - _currentStart) * CB_AUDIO + iPos] = (DoC2 && iPos >= 4 * 588 && iPos < 4 * 588 + 294) ? (byte)255 : (byte)0; - } - } - if (iErrors < Sectors2Read) - return; - } - string status = (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString()); - st = m_device.ReadCDAndSubChannel(0, false, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)(data + (sector - _currentStart) * 4 * 588)), Sectors2Read * 4 * 588); - if (st != Device.CommandStatus.Success) - status += "; ReadWithoutSubchannel: " + (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString()); - else - status += "; ReadWithoutSubchannel: might work"; - throw new Exception("ReadCD: " + status); + ReorganiseSectors(sector, Sectors2Read); + return; } - - //int localC2 = 0; - //if (DoC2) - // for (int iSector = 0; iSector < Sectors2Read; iSector++) - // { - // _currentScan.C2Errors[sector + iSector] = data[(iSector + 1) * CB_AUDIO - 18] != 0; - // localC2 += (data[(iSector + 1) * CB_AUDIO - 18] != 0) ? 1 : 0; - // } - //_currentScan.C2Count += localC2; + if (sector == 0 && _subChannelMode != Device.SubChannelMode.None) + { + _subChannelMode = Device.SubChannelMode.None; + if (_debugMessages) + System.Console.WriteLine("\nFailed to read CD data with subchannel. Switching to ReadCD without subchannel + ReadSubchannel."); + st = m_device.ReadCDAndSubChannel(_mainChannelMode, _subChannelMode, _c2ErrorMode, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)data), 10); + if (st == Device.CommandStatus.Success) + { + ReorganiseSectors(sector, Sectors2Read); + return; + } + } + if (sector != 0 && st == Device.CommandStatus.DeviceFailed && m_device.GetSenseAsc() == 0x64 && m_device.GetSenseAscq() == 0x00) + { + int iErrors = 0; + for (int iSector = 0; iSector < Sectors2Read; iSector++) + { + st = m_device.ReadCDAndSubChannel(_mainChannelMode, _subChannelMode, _c2ErrorMode, 1, false, (uint)(sector + iSector), 1U, (IntPtr)((void*)data), 5); + if (st != Device.CommandStatus.Success) + { + iErrors ++; + for (int iPos = 0; iPos < CB_AUDIO; iPos++) + _currentScan.Data[(sector + iSector - _currentStart) * CB_AUDIO + iPos] = (iPos == 4 * 588 || (iPos >= 4 * 588 + 2 && iPos < 4 * 588 + 2 + 294)) ? (byte)255 : (byte)0; + if (_debugMessages) + System.Console.WriteLine("\nSector lost"); + } else + ReorganiseSectors(sector+iSector, 1); + } + if (iErrors < Sectors2Read) + return; + } + string status = (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString()); + string test = TestReadCommand(); + throw new Exception("ReadCD: " + status + "; Autodetect: " + test); } } @@ -343,7 +411,7 @@ namespace CUETools.Ripper.SCSI for (int iPar = 0; iPar < 4 * 588; iPar++) { int dataPos = (sector - _currentStart + iSector) * CB_AUDIO + iPar; - int c2Pos = (sector - _currentStart + iSector) * CB_AUDIO + 4 * 588 + iPar / 8; + int c2Pos = (sector - _currentStart + iSector) * CB_AUDIO + 2 + 4 * 588 + iPar / 8; int c2Bit = 0x80 >> (iPar % 8); Array.Clear(valueScore, 0, 256); @@ -391,7 +459,7 @@ namespace CUETools.Ripper.SCSI // for (int iPar = 0; iPar < 4 * 588; iPar++) // { // int dataPos = iSector * CB_AUDIO + iPar; - // int c2Pos = iSector * CB_AUDIO + 4 * 588 + iPar / 8; + // int c2Pos = iSector * CB_AUDIO + 2 + 4 * 588 + iPar / 8; // int c2Bit = 0x80 >> (iPar % 8); // Array.Clear(valueScore, 0, 256); @@ -453,16 +521,27 @@ namespace CUETools.Ripper.SCSI for (int pass = 0; pass <= nPasses + nExtraPasses; pass++) { - DateTime PassTime = DateTime.Now; + DateTime PassTime = DateTime.Now, LastFetch = DateTime.Now; _currentScan = new ScanResults(MSECTORS, CB_AUDIO); _currentErrorsCount = 0; int nSectors = Math.Min(NSECTORS, m_device.MaximumTransferLength / CB_AUDIO - 1); for (int sector = _currentStart; sector < _currentEnd; sector += nSectors) { int Sectors2Read = Math.Min(nSectors, _currentEnd - sector); + int speed = pass == 4 || pass == 5 ? 4 : pass == 8 || pass == 9 ? 2 : pass == 17 || pass == 18 ? 1 : 0; + if (speed != 0) + Thread.Sleep(Math.Max(1, 1000 * nSectors / (75 * speed) - (int)((DateTime.Now - LastFetch).TotalMilliseconds))); + LastFetch = DateTime.Now; FetchSectors(sector, Sectors2Read); + + if (ProcessSubchannel(sector, Sectors2Read, pass == 0) == 0 && _subChannelMode != Device.SubChannelMode.None && sector == 0) + { + if (_debugMessages) + System.Console.WriteLine("\nGot no subchannel using ReadCD. Switching to ReadSubchannel."); + _subChannelMode = Device.SubChannelMode.None; + FetchSectors(sector, Sectors2Read); + } CorrectSectors(sector, Sectors2Read, pass > nPasses, pass == nPasses + nExtraPasses); - ProcessSubchannel(sector, Sectors2Read, pass == 0); if (ReadProgress != null) ReadProgress(this, new ReadProgressArgs(sector + Sectors2Read, pass, _currentStart, _currentEnd, _currentErrorsCount, PassTime)); } @@ -699,6 +778,13 @@ namespace CUETools.Ripper.SCSI return (hex >> 4) * 10 + (hex & 15); } + private char from6bit(int bcd) + { + char[] ISRC6 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '#', '#', '#', '#', '#', '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + bcd &= 0x3f; + return bcd >= ISRC6.Length ? '#' : ISRC6[bcd]; + } + public static char[] DrivesAvailable() { List result = new List();