{ } unit wincd; {$mode objfpc} {$h+} interface uses Windows,SysUtils; Type TCDAccessMethod = (camNone,camASPI,camSPTI,camIOCTL); {$packrecords c} TTOCTrack = packed record rsvd, ADR, trackNumber, rsvd2 : Byte; addr : Array[0..3] of byte; end; TTOC = packed Record toclen : word; firsttrack, lastTrack : byte; toctrack: Array[0..99] of TTocTrack; end; Const AccessMethodNames : Array[TCDAccessMethod] of string = ('None','ASPI','SPTI','IOCTL'); Function GetCDAccessMethod : TCDAccessMethod; Procedure SetCDAccessMethod (Value : TCDAccessMethod); Function ReadTOC(Device : String; Var TOC : TTOc) : Integer; Function EnumCDDrives(Var Drives : Array of String) : Integer; Function GetNumDrives : Integer; implementation uses cdromioctl,wnaspi32,scsidefs; Var CurrentAccessMethod : TCDAccessMethod; CDOSVer : Integer; { --------------------------------------------------------------------- SPTI Defines. ---------------------------------------------------------------------} Type {$packrecords C} SCSI_PASS_THROUGH = record Length : USHORT; ScsiStatus : UCHAR; PathId : UCHAR; TargetId : UCHAR; Lun : UCHAR; CdbLength : UCHAR; SenseInfoLength : UCHAR; DataIn : UCHAR; DataTransferLength : ULONG; TimeOutValue : ULONG; DataBufferOffset : ULONG; SenseInfoOffset : ULONG; Cdb : array[0..15] of UCHAR; end; TSCSI_PASS_THROUGH = SCSI_PASS_THROUGH; PSCSI_PASS_THROUGH = ^TSCSI_PASS_THROUGH; SCSI_PASS_THROUGH_DIRECT = record Length : USHORT; ScsiStatus : UCHAR; PathId : UCHAR; TargetId : UCHAR; Lun : UCHAR; CdbLength : UCHAR; SenseInfoLength : UCHAR; DataIn : UCHAR; DataTransferLength : ULONG; TimeOutValue : ULONG; DataBuffer : PVOID; SenseInfoOffset : ULONG; Cdb : array[0..15] of UCHAR; end; TSCSI_PASS_THROUGH_DIRECT = SCSI_PASS_THROUGH_DIRECT; PSCSI_PASS_THROUGH_DIRECT = ^SCSI_PASS_THROUGH_DIRECT; SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER = record spt : SCSI_PASS_THROUGH_DIRECT; Filler : ULONG; ucSenseBuf : array[0..31] of UCHAR; end; TSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER = SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER = ^SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; const METHOD_BUFFERED = 0; METHOD_IN_DIRECT = 1; METHOD_OUT_DIRECT = 2; METHOD_NEITHER = 3; FILE_ANY_ACCESS = 0; FILE_READ_ACCESS = $0001; FILE_WRITE_ACCESS = $0002; IOCTL_CDROM_BASE = $00000002; IOCTL_SCSI_BASE = $00000004; SCSI_IOCTL_DATA_OUT = 0; SCSI_IOCTL_DATA_IN = 1; SCSI_IOCTL_DATA_UNSPECIFIED = 2; { --------------------------------------------------------------------- Initialization code. ---------------------------------------------------------------------} procedure InitWinCD; Var TheCDOSVER : TOSVersionInfo; begin TheCDOSVer.dwOSVersionInfoSize:=SizeOf(TheCDOSver); GetVersionEx(TheCDOSVer); CDOSVer:=TheCDOSVer.dwMajorVersion; If AspiLoaded then CurrentAccessMethod := camASPI else begin if (CDOSver<1) then CurrentAccessMethod := camNone else { It is better to use SPTI on windows, but the problem with that is that administrative priviledges are needed. A detection algorithm for these priviledges here would be nice. } CurrentAccessMethod := camSPTI; end; end; { --------------------------------------------------------------------- Actual reading of table of contents. ---------------------------------------------------------------------} { --------------------------------------------------------------------- 1. SPTI ---------------------------------------------------------------------} Function sptiReadTOC(Device : String; var TOC: TToC) : Integer; Var DriveHandle : THandle; len : Cardinal; buf : Array[0..31] of char; ID,retVal : Integer; Returned,Flags : Cardinal; swb : TSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; begin Flags := Cardinal(GENERIC_READ); if (CDOSVer>4) then Flags:=Flags or Cardinal(GENERIC_WRITE); Device:=Upcase('\\.\'+Device); DriveHandle:=CreateFileA(pchar(Device),Flags,FILE_SHARE_READ, nil,OPEN_EXISTING, 0, 0 ); if (DriveHandle=INVALID_HANDLE_VALUE) then begin Result:=-1; Exit; end; Try Returned:= 0; len:= sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); FillChar(swb, len ,0); With swb.spt do begin Length := sizeof(swb.spt); // SCSI_PASS_THROUGH); CdbLength := 10; DataIn := SCSI_IOCTL_DATA_IN; DataTransferLength := SizeOf(tOC); TimeOutValue := 5; DataBuffer := @TOC; SenseInfoOffset := @swb.ucSenseBuf[0]-pbyte(@swb.spt); Cdb[0] := $43; // read TOC Cdb[1] := $02; // MSF mode Cdb[7] := $03; Cdb[8] := $24; end; if (Not DeviceIoControl(DriveHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, @swb, len, @swb, len, @Returned, Nil)) then begin Result:=-1; Exit; end; With TOC do Result:=LastTrack-FirstTrack+1; finally CloseHandle(DriveHandle); end; end; { --------------------------------------------------------------------- 2. ASPI ---------------------------------------------------------------------} Function AspiGetNumAdapters : Integer; Var D : DWORD; Count, Status : Byte; begin d:= GetASPI32SupportInfo(); Count:=D and $FF; Status:=(D shr 8) and $ff; if (Status<>SS_COMP) and (Status<>SS_NO_ADAPTERS) then Result:=-1 else Result:=Count; end; Function DriveToSCSIParm (Device : String; Var HID,TGT,LUN : Byte) : Boolean; Var Code : Integer; begin Result:=False; Code:=Pos('[',Device); if Code<>0 then begin Delete(Device,1,Code); Code:=Pos(';',Device); HID:=StrToIntDef(Copy(Device,1,Code-1),-1); Result:=HID<>-1; If result then begin Delete(DEvice,1,Code); Code:=Pos(';',Device); Tgt:=StrToIntDef(Copy(Device,1,Code-1),-1); Result:=tgt<>-1; If result then begin Delete(DEvice,1,Code); Code:=Pos(']',Device); Lun:=StrToIntDef(Copy(Device,1,Code-1),-1); Result:=Lun<>-1; end; end; end; end; Var Atoc : TTOc; Function AspiReadTOC(Device : String; Var TOC : TTOC) : Integer; Var HAID,TGT,LUN : Byte; Status : DWord; S,T : SRB_ExecSCSICmd; HEvent : THANDLE; begin If Not DriveToSCSIParm(Device,HAID,TGT,lun) then begin Result:=-1; Exit; end; Writeln('About to read toc from ',haid,' ',tgt,' ',lun); hEvent:=CreateEvent( nil, TRUE, FALSE, nil ); Writeln('Resetting event'); ResetEvent(hEvent); Writeln('Reset event'); Try FillChar(S,sizeof(s),0); s.SRB_Cmd := SC_EXEC_SCSI_CMD; s.SRB_HaID := HaID; s.SRB_Target := Tgt; s.SRB_Lun := lun; s.SRB_Flags := SRB_DIR_IN or SRB_EVENT_NOTIFY; s.SRB_BufLen := SizeOf(Toc); s.SRB_BufPointer := @TOC; s.SRB_SenseLen := SENSE_LEN; s.SRB_CDBLen := $0A; s.SRB_PostProc := LPVOID(hEvent); s.CDBByte[0] := SCSI_READ_TOC; // read TOC command s.CDBByte[1] := $02; // MSF mode s.CDBByte[7] := HiByte(Word(S.SRB_BufLen)); // high-order byte of buffer len s.CDBByte[8] := LoByte(Word(S.SRB_BUFLEN)); // low-order byte of buffer len Writeln('Sending Command'); SendASPI32Command(LPSRB(@s)); Writeln('Sent Command'); Status:=S.SRB_STATUS; Writeln('Command status,',Status); if (Status=SS_PENDING ) then begin Writeln('Waiting for object'); WaitForSingleObject( hEvent, 10000 ); // wait up to 10 secs Writeln('Waiting ended'); end; Finally CloseHandle( hEvent ); end; if (S.SRB_Status<>SS_COMP ) then begin Result:=-1; Exit; end; Writeln('Command completed'); With TOC do Result:=LastTrack-FirstTrack+1; end; { --------------------------------------------------------------------- 3. IOCTL ---------------------------------------------------------------------} Function ioctlReadTOC(Device : String; Var TOC : TTOC) : Integer; Var DriveHandle : Thandle; Retval : Longint; Returned : DWord; Flags : Cardinal; begin Flags:=Cardinal(GENERIC_READ); device:=Upcase('\\.\'+device); DriveHandle:=CreateFileA(PChar(Device), Flags, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0 ); if (DriveHandle = INVALID_HANDLE_VALUE) then begin result:=-1; exit; end; Try Returned := 0; FillChar(Toc, sizeof(TOC),0 ); if Not DeviceIoControl(DriveHandle, IOCTL_CDROM_READ_TOC, Nil, 0, @TOC, sizeof(TTOC), @Returned, NiL) then begin Result:=-1; exit; end; With TOC do Result:=LastTrack-FirstTrack+1; Finally CloseHandle(DriveHandle); end; end; Function NtDriveInfo(CopyDrives : Boolean;Var CDDrives : Array of string): Integer; var I : Integer; Drives : Array[0..105] of char; P : PChar; begin FillChar(Drives,SizeOf(Drives),0); GetLogicalDriveStringsA(105,Drives); P:=@Drives[0]; Result:=0; While P[0]<>#0 do begin If GetDriveTypeA(p)=DRIVE_CDROM then begin If CopyDrives and (Result