use windows_sys::Win32::Foundation::{
CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, GetDriveTypeW,
OPEN_EXISTING,
};
use windows_sys::Win32::Storage::IscsiDisc::{
IOCTL_SCSI_PASS_THROUGH_DIRECT, SCSI_IOCTL_DATA_IN, SCSI_PASS_THROUGH_DIRECT,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use crate::{CdReaderError, RetryConfig, ScsiError, ScsiOp, Toc, parse_toc, windows_read_track};
use std::mem;
use std::ptr;
#[repr(C)]
pub struct SptdWithSense {
pub sptd: SCSI_PASS_THROUGH_DIRECT,
pub sense: [u8; 32],
}
static mut DRIVE_HANDLE: Option<HANDLE> = None;
const DRIVE_CDROM: u32 = 5;
pub fn list_drive_paths() -> std::io::Result<Vec<String>> {
let mut paths = Vec::new();
for letter in b'A'..=b'Z' {
let drive = letter as char;
let root: Vec<u16> = format!("{drive}:\\")
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let drive_type = unsafe { GetDriveTypeW(root.as_ptr()) };
if drive_type == DRIVE_CDROM {
paths.push(format!(r"\\.\{drive}:"));
}
}
Ok(paths)
}
#[allow(static_mut_refs)]
pub fn open_drive(path: &str) -> std::io::Result<()> {
unsafe {
if DRIVE_HANDLE.is_some() {
return Err(std::io::Error::other("Drive is already open"));
}
}
let path_wide: Vec<u16> = path.encode_utf16().chain(std::iter::once(0)).collect();
let drive_handle = unsafe {
CreateFileW(
path_wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
ptr::null_mut(),
)
};
if drive_handle == INVALID_HANDLE_VALUE {
println!("Device NOT opened succesfully");
return Err(std::io::Error::last_os_error());
}
unsafe {
DRIVE_HANDLE = Some(drive_handle);
}
Ok(())
}
pub fn close_drive() {
unsafe {
if let Some(current_drive) = DRIVE_HANDLE {
CloseHandle(current_drive);
DRIVE_HANDLE = None;
}
}
}
pub fn read_toc() -> Result<Toc, CdReaderError> {
let handle = unsafe {
DRIVE_HANDLE
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "Drive not opened"))
.map_err(CdReaderError::Io)?
};
let alloc_len: usize = 2048;
let mut data = vec![0u8; alloc_len];
let mut wrapper: SptdWithSense = unsafe { mem::zeroed() };
let sptd = &mut wrapper.sptd;
sptd.Length = size_of::<SCSI_PASS_THROUGH_DIRECT>() as u16;
sptd.CdbLength = 10; sptd.DataIn = SCSI_IOCTL_DATA_IN as u8;
sptd.TimeOutValue = 10; sptd.DataTransferLength = alloc_len as u32;
sptd.DataBuffer = data.as_mut_ptr() as *mut _;
sptd.SenseInfoLength = wrapper.sense.len() as u8;
sptd.SenseInfoOffset = size_of::<SCSI_PASS_THROUGH_DIRECT>() as u32;
let cdb = &mut sptd.Cdb;
cdb[0] = 0x43; cdb[1] = 0x00; cdb[2] = 0x00; cdb[6] = 0x00; cdb[7] = ((alloc_len >> 8) & 0xFF) as u8;
cdb[8] = (alloc_len & 0xFF) as u8;
cdb[9] = 0x00;
let mut bytes = 0u32;
let ok = unsafe {
DeviceIoControl(
handle,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&mut wrapper as *mut _ as *mut _,
size_of::<SptdWithSense>() as u32,
&mut wrapper as *mut _ as *mut _,
size_of::<SptdWithSense>() as u32,
&mut bytes as *mut u32,
ptr::null_mut(),
)
};
if ok == 0 {
return Err(CdReaderError::Io(std::io::Error::last_os_error()));
} else if wrapper.sptd.ScsiStatus != 0 {
let (sense_key, asc, ascq) = parse_sense(&wrapper.sense, wrapper.sptd.SenseInfoLength);
return Err(CdReaderError::Scsi(ScsiError {
op: ScsiOp::ReadToc,
lba: None,
sectors: None,
scsi_status: wrapper.sptd.ScsiStatus,
sense_key,
asc,
ascq,
}));
}
parse_toc::parse_toc(data).map_err(|err| CdReaderError::Parse(err.to_string()))
}
pub fn read_track_with_retry(
toc: &Toc,
track_no: u8,
cfg: &RetryConfig,
) -> Result<Vec<u8>, CdReaderError> {
let (start_lba, sectors) =
crate::utils::get_track_bounds(toc, track_no).map_err(CdReaderError::Io)?;
read_sectors_with_retry(start_lba, sectors, cfg)
}
pub fn read_sectors_with_retry(
start_lba: u32,
sectors: u32,
cfg: &RetryConfig,
) -> Result<Vec<u8>, CdReaderError> {
let handle = unsafe {
DRIVE_HANDLE
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "Drive not opened"))
.map_err(CdReaderError::Io)?
};
windows_read_track::read_audio_range_with_retry(handle, start_lba, sectors, cfg)
}
fn parse_sense(sense: &[u8], sense_len: u8) -> (Option<u8>, Option<u8>, Option<u8>) {
if sense_len < 14 || sense.len() < 14 {
return (None, None, None);
}
(Some(sense[2] & 0x0F), Some(sense[12]), Some(sense[13]))
}