// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : MMC.cs // Author(s) : Natalia Portillo // // Component : SCSI MultiMedia Commands. // // --[ Description ] ---------------------------------------------------------- // // Contains SCSI commands defined in MMC standards. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ using System; using System.Diagnostics.CodeAnalysis; using System.Text; using Aaru.Logging; // ReSharper disable UnusedMember.Global namespace Aaru.Devices; [SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")] public partial class Device { /// Sends the MMC GET CONFIGURATION command for all Features /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI GET CONFIGURATION response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool GetConfiguration(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => GetConfiguration(out buffer, out senseBuffer, 0x0000, MmcGetConfigurationRt.All, timeout, out duration); /// Sends the MMC GET CONFIGURATION command for all Features starting with specified one /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI GET CONFIGURATION response will be stored /// Sense buffer. /// Feature number where the feature list should start from /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool GetConfiguration(out byte[] buffer, out ReadOnlySpan senseBuffer, ushort startingFeatureNumber, uint timeout, out double duration) => GetConfiguration(out buffer, out senseBuffer, startingFeatureNumber, MmcGetConfigurationRt.All, timeout, out duration); /// Sends the MMC GET CONFIGURATION command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI GET CONFIGURATION response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// Starting Feature number. /// Return type, . public bool GetConfiguration(out byte[] buffer, out ReadOnlySpan senseBuffer, ushort startingFeatureNumber, MmcGetConfigurationRt rt, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); buffer = new byte[8]; cdb[0] = (byte)ScsiCommands.GetConfiguration; cdb[1] = (byte)((byte)rt & 0x03); cdb[2] = (byte)((startingFeatureNumber & 0xFF00) >> 8); cdb[3] = (byte)(startingFeatureNumber & 0xFF); cdb[7] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[8] = (byte)(buffer.Length & 0xFF); cdb[9] = 0; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; if(sense) return true; var confLength = (ushort)((buffer[2] << 8) + buffer[3] + 4); buffer = new byte[confLength]; cdb[7] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[8] = (byte)(buffer.Length & 0xFF); senseBuffer = SenseBuffer; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .GET_CONFIGURATION_Starting_Feature_Number_1_Return_Type_2_Sense_3_Last_Error_4_took_0_ms, duration, startingFeatureNumber, rt, sense, LastError); return sense; } /// Sends the MMC READ DISC STRUCTURE command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ DISC STRUCTURE response will be stored /// Sense buffer. /// Medium type for requested disc structure /// Medium address for requested disc structure /// Medium layer for requested disc structure /// Timeout in seconds. /// Which disc structure are we requesting /// AGID used in medium copy protection /// Duration in milliseconds it took for the device to execute the command. public bool ReadDiscStructure(out byte[] buffer, out ReadOnlySpan senseBuffer, MmcDiscStructureMediaType mediaType, uint address, byte layerNumber, MmcDiscStructureFormat format, byte agid, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..12]; cdb.Clear(); buffer = new byte[8]; cdb[0] = (byte)ScsiCommands.ReadDiscStructure; cdb[1] = (byte)((byte)mediaType & 0x0F); cdb[2] = (byte)((address & 0xFF000000) >> 24); cdb[3] = (byte)((address & 0xFF0000) >> 16); cdb[4] = (byte)((address & 0xFF00) >> 8); cdb[5] = (byte)(address & 0xFF); cdb[6] = layerNumber; cdb[7] = (byte)format; cdb[8] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[9] = (byte)(buffer.Length & 0xFF); cdb[10] = (byte)((agid & 0x03) << 6); LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; if(sense) return true; var strctLength = (ushort)((buffer[0] << 8) + buffer[1] + 2); // WORKAROUND: Some drives return incorrect length information. As these structures are fixed length just apply known length. if(mediaType == MmcDiscStructureMediaType.Bd) { buffer = format switch { MmcDiscStructureFormat.DiscInformation => new byte[4100], MmcDiscStructureFormat.BdBurstCuttingArea => new byte[68], MmcDiscStructureFormat.BdDds => new byte[strctLength < 100 ? 100 : strctLength], MmcDiscStructureFormat.CartridgeStatus => new byte[8], MmcDiscStructureFormat.BdSpareAreaInformation => new byte[16], _ => new byte[strctLength] }; } else buffer = new byte[strctLength]; cdb[8] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[9] = (byte)(buffer.Length & 0xFF); senseBuffer = SenseBuffer; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .READ_DISC_STRUCTURE_Media_Type_1_Address_2_Layer_Number_3_Format_4_AGID_5_Sense_6_Last_Error_7_took_0_ms, duration, mediaType, address, layerNumber, format, agid, sense, LastError); return sense; } /// Sends the MMC READ TOC/PMA/ATIP command to get formatted TOC from disc, in MM:SS:FF format /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Start TOC from this track /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadToc(out byte[] buffer, out ReadOnlySpan senseBuffer, byte track, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 0, track, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get formatted TOC from disc /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// If true, request data in MM:SS:FF units, otherwise, in blocks /// Start TOC from this track /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadToc(out byte[] buffer, out ReadOnlySpan senseBuffer, bool msf, byte track, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, msf, 0, track, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get multi-session information, in MM:SS:FF format /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadSessionInfo(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 1, 0, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get multi-session information /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// If true, request data in MM:SS:FF units, otherwise, in blocks /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadSessionInfo(out byte[] buffer, out ReadOnlySpan senseBuffer, bool msf, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, msf, 1, 0, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get raw TOC subchannels /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Session which TOC to get /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadRawToc(out byte[] buffer, out ReadOnlySpan senseBuffer, byte sessionNumber, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 2, sessionNumber, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get PMA /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadPma(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 3, 0, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get ATIP /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadAtip(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 4, 0, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command to get Lead-In CD-TEXT /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadCdText(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => ReadTocPmaAtip(out buffer, out senseBuffer, true, 5, 0, timeout, out duration); /// Sends the MMC READ TOC/PMA/ATIP command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ TOC/PMA/ATIP response will be stored /// Sense buffer. /// If true, request data in MM:SS:FF units, otherwise, in blocks /// What structure is requested /// Track/Session number /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadTocPmaAtip(out byte[] buffer, out ReadOnlySpan senseBuffer, bool msf, byte format, byte trackSessionNumber, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); byte[] tmpBuffer = (format & 0x0F) == 5 ? new byte[32768] : new byte[1536]; cdb[0] = (byte)ScsiCommands.ReadTocPmaAtip; if(msf) cdb[1] = 0x02; cdb[2] = (byte)(format & 0x0F); cdb[6] = trackSessionNumber; cdb[7] = (byte)((tmpBuffer.Length & 0xFF00) >> 8); cdb[8] = (byte)(tmpBuffer.Length & 0xFF); LastError = SendScsiCommand(cdb, ref tmpBuffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; var strctLength = (uint)((tmpBuffer[0] << 8) + tmpBuffer[1] + 2); buffer = new byte[strctLength]; if(buffer.Length <= tmpBuffer.Length) { Array.Copy(tmpBuffer, 0, buffer, 0, buffer.Length); AaruLogging.Debug(SCSI_MODULE_NAME, Localization .READ_TOC_PMA_ATIP_took_MSF_1_Format_2_Track_Session_Number_3_Sense_4_LastError_5_0_ms, duration, msf, format, trackSessionNumber, sense, LastError); return sense; } double tmpDuration = duration; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out sense); Error = LastError != 0; duration += tmpDuration; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .READ_TOC_PMA_ATIP_took_MSF_1_Format_2_Track_Session_Number_3_Sense_4_LastError_5_0_ms, duration, msf, format, trackSessionNumber, sense, LastError); return sense; } /// Sends the MMC READ DISC INFORMATION command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ DISC INFORMATION response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadDiscInformation(out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) => ReadDiscInformation(out buffer, out senseBuffer, MmcDiscInformationDataTypes.DiscInformation, timeout, out duration); /// Sends the MMC READ DISC INFORMATION command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ DISC INFORMATION response will be stored /// Sense buffer. /// Which disc information to read /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. public bool ReadDiscInformation(out byte[] buffer, out ReadOnlySpan senseBuffer, MmcDiscInformationDataTypes dataType, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); var tmpBuffer = new byte[804]; cdb[0] = (byte)ScsiCommands.ReadDiscInformation; cdb[1] = (byte)dataType; cdb[7] = (byte)((tmpBuffer.Length & 0xFF00) >> 8); cdb[8] = (byte)(tmpBuffer.Length & 0xFF); LastError = SendScsiCommand(cdb, ref tmpBuffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; var strctLength = (uint)((tmpBuffer[0] << 8) + tmpBuffer[1] + 2); if(strctLength > tmpBuffer.Length) strctLength = (uint)tmpBuffer.Length; buffer = new byte[strctLength]; Array.Copy(tmpBuffer, 0, buffer, 0, buffer.Length); AaruLogging.Debug(SCSI_MODULE_NAME, Localization.READ_DISC_INFORMATION_Data_Type_1_Sense_2_Last_Error_3_took_0_ms, duration, dataType, sense, LastError); return sense; } /// Sends the MMC READ CD command /// true if the command failed and contains the sense buffer. /// Buffer where the MMC READ CD response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// Start block address. /// How many blocks to read. /// Block size. /// Expected sector type. /// If set to true CD-DA should be modified by mute and interpolation /// If set to true address is relative to current position. /// If set to true we request the sync bytes for data sectors. /// Header codes. /// If set to true we request the user data. /// If set to true we request the EDC/ECC fields for data sectors. /// C2 error options. /// Subchannel selection. public bool ReadCd(out byte[] buffer, out ReadOnlySpan senseBuffer, uint lba, uint blockSize, uint transferLength, MmcSectorTypes expectedSectorType, bool dap, bool relAddr, bool sync, MmcHeaderCodes headerCodes, bool userData, bool edcEcc, MmcErrorField c2Error, MmcSubchannel subchannel, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..12]; cdb.Clear(); cdb[0] = (byte)ScsiCommands.ReadCd; cdb[1] = (byte)((byte)expectedSectorType << 2); if(dap) cdb[1] += 0x02; if(relAddr) cdb[1] += 0x01; cdb[2] = (byte)((lba & 0xFF000000) >> 24); cdb[3] = (byte)((lba & 0xFF0000) >> 16); cdb[4] = (byte)((lba & 0xFF00) >> 8); cdb[5] = (byte)(lba & 0xFF); cdb[6] = (byte)((transferLength & 0xFF0000) >> 16); cdb[7] = (byte)((transferLength & 0xFF00) >> 8); cdb[8] = (byte)(transferLength & 0xFF); cdb[9] = (byte)((byte)c2Error << 1); cdb[9] += (byte)((byte)headerCodes << 5); if(sync) cdb[9] += 0x80; if(userData) cdb[9] += 0x10; if(edcEcc) cdb[9] += 0x08; cdb[10] = (byte)subchannel; buffer = new byte[blockSize * transferLength]; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .READ_CD_LBA_1_Block_Size_2_Transfer_Length_3_Expected_Sector_Type_4_DAP_5_Relative_Address_6_Sync_7_Headers_8_User_Data_9_ECC_EDC_10_C2_11_Subchannel_12_Sense_13_Last_Error_14_took_0_ms, duration, lba, blockSize, transferLength, expectedSectorType, dap, relAddr, sync, headerCodes, userData, edcEcc, c2Error, subchannel, sense, LastError); return sense; } /// Sends the MMC READ CD MSF command /// true if the command failed and contains the sense buffer. /// Buffer where the MMC READ CD MSF response will be stored /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// Start MM:SS:FF of read encoded as 0x00MMSSFF. /// End MM:SS:FF of read encoded as 0x00MMSSFF. /// Block size. /// Expected sector type. /// If set to true CD-DA should be modified by mute and interpolation /// If set to true we request the sync bytes for data sectors. /// Header codes. /// If set to true we request the user data. /// If set to true we request the EDC/ECC fields for data sectors. /// C2 error options. /// Subchannel selection. public bool ReadCdMsf(out byte[] buffer, out ReadOnlySpan senseBuffer, uint startMsf, uint endMsf, uint blockSize, MmcSectorTypes expectedSectorType, bool dap, bool sync, MmcHeaderCodes headerCodes, bool userData, bool edcEcc, MmcErrorField c2Error, MmcSubchannel subchannel, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..12]; cdb.Clear(); cdb[0] = (byte)ScsiCommands.ReadCdMsf; cdb[1] = (byte)((byte)expectedSectorType << 2); if(dap) cdb[1] += 0x02; cdb[3] = (byte)((startMsf & 0xFF0000) >> 16); cdb[4] = (byte)((startMsf & 0xFF00) >> 8); cdb[5] = (byte)(startMsf & 0xFF); cdb[6] = (byte)((endMsf & 0xFF0000) >> 16); cdb[7] = (byte)((endMsf & 0xFF00) >> 8); cdb[8] = (byte)(endMsf & 0xFF); cdb[9] = (byte)((byte)c2Error << 1); cdb[9] += (byte)((byte)headerCodes << 5); if(sync) cdb[9] += 0x80; if(userData) cdb[9] += 0x10; if(edcEcc) cdb[9] += 0x08; cdb[10] = (byte)subchannel; var transferLength = (uint)((cdb[6] - cdb[3]) * 60 * 75 + (cdb[7] - cdb[4]) * 75 + (cdb[8] - cdb[5])); buffer = new byte[blockSize * transferLength]; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .READ_CD_MSF_Start_MSF_1_End_MSF_2_Block_Size_3_Expected_Sector_Type_4_DAP_5_Sync_6_Headers_7_User_Data_8_ECC_EDC_9_C2_10_Subchannel_11_Sense_12_LastError_13_took_0_ms, duration, startMsf, endMsf, blockSize, expectedSectorType, dap, sync, headerCodes, userData, edcEcc, c2Error, subchannel, sense, LastError); return sense; } /// Prevents ejection of the media inserted in the drive /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool PreventMediumRemoval(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => PreventAllowMediumRemoval(out senseBuffer, false, true, timeout, out duration); /// Allows ejection of the media inserted in the drive /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool AllowMediumRemoval(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => PreventAllowMediumRemoval(out senseBuffer, false, false, timeout, out duration); /// Prevents or allows ejection of the media inserted in the drive /// Sense buffer. /// Persistent. /// Prevent. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool PreventAllowMediumRemoval(out ReadOnlySpan senseBuffer, bool persistent, bool prevent, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..6]; cdb.Clear(); byte[] buffer = []; cdb[0] = (byte)ScsiCommands.PreventAllowMediumRemoval; if(prevent) cdb[4] += 0x01; if(persistent) cdb[4] += 0x02; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.None, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .PREVENT_ALLOW_MEDIUM_REMOVAL_Persistent_1_Prevent_2_Sense_3_LastError_4_took_0_ms, duration, persistent, prevent, sense, LastError); return sense; } /// Loads the media tray /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool LoadTray(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => StartStopUnit(out senseBuffer, false, 0, 0, false, true, true, timeout, out duration); /// Ejects the media or its tray /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool EjectTray(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => StartStopUnit(out senseBuffer, false, 0, 0, false, true, false, timeout, out duration); /// Starts the drive's reading mechanism /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool StartUnit(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => StartStopUnit(out senseBuffer, false, 0, 0, false, false, true, timeout, out duration); /// Stops the drive's reading mechanism /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool StopUnit(out ReadOnlySpan senseBuffer, uint timeout, out double duration) => StartStopUnit(out senseBuffer, false, 0, 0, false, false, false, timeout, out duration); /// Starts or stops the drive's reading mechanism or tray /// Sense buffer. /// Return from execution immediately /// Choose density layer for hybrid discs /// Power condition /// Change format layer /// Loads or ejects the media /// Starts the mechanism /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool StartStopUnit(out ReadOnlySpan senseBuffer, bool immediate, byte formatLayer, byte powerConditions, bool changeFormatLayer, bool loadEject, bool start, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..6]; cdb.Clear(); byte[] buffer = []; cdb[0] = (byte)ScsiCommands.StartStopUnit; if(immediate) cdb[1] += 0x01; if(changeFormatLayer) { cdb[3] = (byte)(formatLayer & 0x03); cdb[4] += 0x04; } else { if(loadEject) cdb[4] += 0x02; if(start) cdb[4] += 0x01; } cdb[4] += (byte)((powerConditions & 0x0F) << 4); LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.None, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .START_STOP_UNIT_Immediate_1_FormatLayer_2_Power_Conditions_3_Change_Format_Layer_4_Load_Eject_5_Start_6_Sense_7_Last_Error_8_took_0_ms, duration, immediate, formatLayer, powerConditions, changeFormatLayer, loadEject, start, sense, LastError); return sense; } /// Reads the MCN from a disc /// Decoded MCN. /// Buffer containing raw drive response. /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. [SuppressMessage("ReSharper", "ShiftExpressionZeroLeftOperand")] public bool ReadMcn(out string mcn, out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); mcn = null; cdb[0] = (byte)ScsiCommands.ReadSubChannel; cdb[1] = 0; cdb[2] = 0x40; cdb[3] = 0x02; cdb[7] = (23 & 0xFF00) >> 8; cdb[8] = 23 & 0xFF; buffer = new byte[23]; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization.READ_READ_SUB_CHANNEL_MCN_Sense_1_Last_Error_2_took_0_ms, duration, sense, LastError); if(!sense && (buffer[8] & 0x80) == 0x80) mcn = Encoding.ASCII.GetString(buffer, 9, 13); return sense; } /// Reads the ISRC from a track /// Track number. /// Decoded ISRC. /// Buffer containing raw drive response. /// Sense buffer. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. [SuppressMessage("ReSharper", "ShiftExpressionZeroLeftOperand")] public bool ReadIsrc(byte trackNumber, out string isrc, out byte[] buffer, out ReadOnlySpan senseBuffer, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); isrc = null; cdb[0] = (byte)ScsiCommands.ReadSubChannel; cdb[1] = 0; cdb[2] = 0x40; cdb[3] = 0x03; cdb[6] = trackNumber; cdb[7] = (23 & 0xFF00) >> 8; cdb[8] = 23 & 0xFF; buffer = new byte[23]; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization.READ_READ_SUB_CHANNEL_ISRC_Track_Number_1_Sense_2_Last_Error_3_took_0_ms, duration, trackNumber, sense, LastError); if(!sense && (buffer[8] & 0x80) == 0x80) isrc = Encoding.ASCII.GetString(buffer, 9, 12); return sense; } /// Sets the reading speed /// Sense buffer. /// Rotational control. /// Read speed. /// Write speed. /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// true if the command failed and contains the sense buffer. public bool SetCdSpeed(out ReadOnlySpan senseBuffer, RotationalControl rotationalControl, ushort readSpeed, ushort writeSpeed, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..12]; cdb.Clear(); byte[] buffer = []; cdb[0] = (byte)ScsiCommands.SetCdRomSpeed; cdb[1] = (byte)((byte)rotationalControl & 0x03); cdb[2] = (byte)((readSpeed & 0xFF00) >> 8); cdb[3] = (byte)(readSpeed & 0xFF); cdb[4] = (byte)((writeSpeed & 0xFF00) >> 8); cdb[5] = (byte)(writeSpeed & 0xFF); LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.None, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization .SET_CD_SPEED_Rotational_Control_1_Read_Speed_2_Write_Speed_3_Sense_4_Last_Error_5_took_0_ms, duration, rotationalControl, readSpeed, writeSpeed, sense, LastError); return sense; } /// Sends the MMC READ TRACK INFORMATION command /// true if the command failed and contains the sense buffer. /// Buffer where the SCSI READ DISC INFORMATION response will be stored /// Sense buffer. /// Track/session/sector address /// Timeout in seconds. /// Duration in milliseconds it took for the device to execute the command. /// Report information of non-closed tracks /// Type of information to retrieve public bool ReadTrackInformation(out byte[] buffer, out ReadOnlySpan senseBuffer, bool open, TrackInformationType type, uint address, uint timeout, out double duration) { senseBuffer = SenseBuffer; Span cdb = CdbBuffer[..10]; cdb.Clear(); buffer = new byte[48]; cdb[0] = (byte)ScsiCommands.ReadTrackInformation; cdb[1] = (byte)((byte)type & 0x3); cdb[2] = (byte)((address & 0xFF000000) >> 24); cdb[3] = (byte)((address & 0xFF0000) >> 16); cdb[4] = (byte)((address & 0xFF00) >> 8); cdb[5] = (byte)(address & 0xFF); cdb[7] = (byte)((buffer.Length & 0xFF00) >> 8); cdb[8] = (byte)(buffer.Length & 0xFF); if(open) cdb[1] += 4; LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense); Error = LastError != 0; AaruLogging.Debug(SCSI_MODULE_NAME, Localization.READ_TRACK_INFORMATION_Data_Type_1_Sense_2_Last_Error_3_took_0_ms, duration, type, sense, LastError); return sense; } }