winamp/Src/Plugins/Input/in_cdda/windac/NTScsi.cpp

501 lines
11 KiB
C++

#include <stdio.h>
#include <stddef.h>
#include "NTScsi.h"
typedef struct {
BYTE ha;
BYTE tgt;
BYTE lun;
BYTE driveLetter;
BOOL bUsed;
HANDLE hDevice;
BYTE inqData[36];
} NTSCSIDRIVE;
typedef struct
{
BYTE numAdapters;
NTSCSIDRIVE drive[26];
} NTSCSIDRIVES;
void GetDriveInformation( BYTE i, NTSCSIDRIVE *pDrive );
static HANDLE GetFileHandle( BYTE i );
static BOOL bNtScsiAvailable = FALSE;
static NTSCSIDRIVES NtScsiDrives;
static BOOL bUseNtScsi = FALSE;
/*
* Initialization of SCSI Pass Through Interface code. Responsible for
* setting up the array of SCSI devices. This code will be a little
* different from the normal code -- it will query each drive letter from
* C: through Z: to see if it is a CD. When we identify a CD, we then
* send CDB with the INQUIRY command to it -- NT will automagically fill in
* the PathId, TargetId, and Lun for us.
*/
int NtScsiInit( void )
{
BYTE i;
wchar_t buf[4] = {0};
UINT uDriveType;
int retVal = 0;
if ( bNtScsiAvailable )
{
for( i = 2; i < 26; i++ ) if ( NtScsiDrives.drive[i].bUsed ) retVal++;
bUseNtScsi = (retVal > 0 );
return retVal;
}
memset( &NtScsiDrives, 0x00, sizeof(NtScsiDrives) );
for( i = 0; i < 26; i++ )
{
NtScsiDrives.drive[i].hDevice = INVALID_HANDLE_VALUE;
}
for( i = 2; i < 26; i++ )
{
wsprintf( buf, L"%c:\\", (wchar_t)('A'+i) );
uDriveType = GetDriveType( buf );
/* check if this is a CDROM drive */
if ( uDriveType == DRIVE_CDROM )
{
GetDriveInformation( i, &NtScsiDrives.drive[i] );
if ( NtScsiDrives.drive[i].bUsed )
retVal++;
}
}
NtScsiDrives.numAdapters = NtScsiGetNumAdapters( );
bNtScsiAvailable = TRUE;
if ( retVal > 0 )
{
bUseNtScsi = TRUE;
}
return retVal;
}
int NtScsiDeInit( void )
{
BYTE i;
if ( !bNtScsiAvailable )
return 0;
for( i = 2; i < 26; i++ )
{
if ( NtScsiDrives.drive[i].bUsed )
{
CloseHandle( NtScsiDrives.drive[i].hDevice );
}
}
NtScsiDrives.numAdapters = NtScsiGetNumAdapters( );
ZeroMemory( &NtScsiDrives, sizeof(NtScsiDrives) );
bNtScsiAvailable = FALSE;
return -1;
}
/*
* Returns the number of "adapters" present.
*/
BYTE NtScsiGetNumAdapters( void )
{
BYTE buf[256] = {0};
WORD i;
BYTE numAdapters = 0;
// PortNumber 0 should exist, so pre-mark it. This avoids problems
// when the primary IDE drives are on PortNumber 0, but can't be opened
// because of insufficient privelege (ie. non-admin).
buf[0] = 1;
for( i = 0; i < 26; i++ )
{
if ( NtScsiDrives.drive[i].bUsed )
buf[NtScsiDrives.drive[i].ha] = 1;
}
for( i = 0; i <= 255; i++ )
{
if ( buf[i] )
numAdapters++;
}
return numAdapters;
}
/*
* Replacement for GetASPI32SupportInfo from wnaspi32.dll
*/
DWORD NtScsiGetASPI32SupportInfo( void )
{
DWORD retVal;
if ( !NtScsiDrives.numAdapters )
retVal = (DWORD)(MAKEWORD(0,SS_NO_ADAPTERS));
else
retVal = (DWORD)(MAKEWORD(NtScsiDrives.numAdapters,SS_COMP));
return retVal;
}
/*
* Needs to call the appropriate function for the lpsrb->SRB_Cmd specified.
* Valid types are SC_HA_INQUIRY, SC_GET_DEV_TYPE, SC_EXEC_SCSI_CMD,
* and SC_RESET_DEV.
*/
DWORD NtScsiSendASPI32Command( LPSRB lpsrb )
{
if ( !lpsrb )
return SS_ERR;
switch( lpsrb->SRB_Cmd )
{
case SC_HA_INQUIRY:
return NtScsiHandleHaInquiry( (LPSRB_HAINQUIRY)lpsrb );
break;
case SC_GET_DEV_TYPE:
return NtScsiGetDeviceType( (LPSRB_GDEVBLOCK)lpsrb );
break;
case SC_EXEC_SCSI_CMD:
return NtScsiExecSCSICommand( (LPSRB_EXECSCSICMD)lpsrb, FALSE );
break;
case SC_RESET_DEV:
default:
lpsrb->SRB_Status = SS_ERR;
return SS_ERR;
break;
}
return SS_ERR; // should never get to here...
}
/*
* Universal function to get a file handle to the CD device. Since
* NT 4.0 wants just the GENERIC_READ flag, and Win2K wants both
* GENERIC_READ and GENERIC_WRITE (why a read-only CD device needs
* GENERIC_WRITE access is beyond me...), the easist workaround is to just
* try them both.
*/
static HANDLE GetFileHandle( BYTE i )
{
wchar_t buf[12] = {0};
HANDLE fh = NULL;
OSVERSIONINFO osver;
DWORD dwFlags;
memset( &osver, 0x00, sizeof(osver) );
osver.dwOSVersionInfoSize = sizeof(osver);
GetVersionEx( &osver );
// if Win2K or greater, add GENERIC_WRITE
dwFlags = GENERIC_READ;
if ( (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osver.dwMajorVersion > 4) )
{
dwFlags |= GENERIC_WRITE;
}
wsprintf( buf, L"\\\\.\\%c:", (wchar_t)('A'+i) );
fh = CreateFile( buf, dwFlags, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING, 0, NULL );
if ( fh == INVALID_HANDLE_VALUE )
{
// it went foobar somewhere, so try it with the GENERIC_WRITE bit flipped
dwFlags ^= GENERIC_WRITE;
fh = CreateFile( buf, dwFlags, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
}
if ( fh == INVALID_HANDLE_VALUE )
{
}
else
{
}
return fh;
}
/*
* fills in a pDrive structure with information from a SCSI_INQUIRY
* and obtains the ha:tgt:lun values via IOCTL_SCSI_GET_ADDRESS
*/
void GetDriveInformation( BYTE i, NTSCSIDRIVE *pDrive )
{
HANDLE fh;
char buf[2048] = {0};
BOOL status;
PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER pswb;
PSCSI_ADDRESS pscsiAddr;
ULONG length, returned;
BYTE inqData[100] = {0};
fh = GetFileHandle( i );
if ( fh == INVALID_HANDLE_VALUE )
{
return;
}
/*
* Get the drive inquiry data
*/
ZeroMemory( &buf, 2048 );
ZeroMemory( inqData, 100 );
pswb = (PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)buf;
pswb->spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
pswb->spt.CdbLength = 6;
pswb->spt.SenseInfoLength = 24;
pswb->spt.DataIn = SCSI_IOCTL_DATA_IN;
pswb->spt.DataTransferLength = 100;
pswb->spt.TimeOutValue = 2;
pswb->spt.DataBuffer = inqData;
pswb->spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf );
pswb->spt.Cdb[0] = 0x12;
pswb->spt.Cdb[4] = 100;
length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER);
status = DeviceIoControl( fh,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
pswb,
length,
pswb,
length,
&returned,
NULL );
if ( !status )
{
CloseHandle( fh );
return;
}
memcpy( pDrive->inqData, inqData, 36 );
/*
* get the address (path/tgt/lun) of the drive via IOCTL_SCSI_GET_ADDRESS
*/
ZeroMemory( &buf, 2048 );
pscsiAddr = (PSCSI_ADDRESS)buf;
pscsiAddr->Length = sizeof(SCSI_ADDRESS);
if ( DeviceIoControl( fh, IOCTL_SCSI_GET_ADDRESS, NULL, 0,
pscsiAddr, sizeof(buf), &returned,
NULL ) )
{
pDrive->bUsed = TRUE;
pDrive->ha = pscsiAddr->PortNumber;
pDrive->tgt = pscsiAddr->TargetId;
pDrive->lun = pscsiAddr->Lun;
pDrive->driveLetter = i;
pDrive->hDevice = INVALID_HANDLE_VALUE;
}
else if (50 == GetLastError()) // usb/firewire
{
pDrive->bUsed = TRUE;
pDrive->ha = i;
pDrive->tgt = 0;
pDrive->lun = 0;
pDrive->driveLetter = i;
pDrive->hDevice = INVALID_HANDLE_VALUE;
}
else
{
pDrive->bUsed = FALSE;
}
CloseHandle( fh );
}
DWORD NtScsiHandleHaInquiry( LPSRB_HAINQUIRY lpsrb )
{
DWORD *pMTL;
lpsrb->HA_Count = NtScsiDrives.numAdapters;
if ( lpsrb->SRB_HaId >= NtScsiDrives.numAdapters )
{
lpsrb->SRB_Status = SS_INVALID_HA;
return SS_INVALID_HA;
}
lpsrb->HA_SCSI_ID = 7; // who cares... we're not really an ASPI manager
memcpy( lpsrb->HA_ManagerId, "blahblahblahblah", 16 );
memcpy( lpsrb->HA_Identifier, "blahblahblahblah", 16 );
lpsrb->HA_Identifier[13] = (char)('0'+lpsrb->SRB_HaId);
ZeroMemory( lpsrb->HA_Unique, 16 );
lpsrb->HA_Unique[3] = 8;
pMTL = (LPDWORD)&lpsrb->HA_Unique[4];
*pMTL = 64 * 1024;
lpsrb->SRB_Status = SS_COMP;
return SS_COMP;
}
/*
* Scans through the drive array and returns DTYPE_CDROM type for all items
* found, and DTYPE_UNKNOWN for all others.
*/
DWORD NtScsiGetDeviceType( LPSRB_GDEVBLOCK lpsrb )
{
lpsrb->SRB_Status = SS_NO_DEVICE;
if ( NtScsiGetDeviceIndex( lpsrb->SRB_HaId, lpsrb->SRB_Target, lpsrb->SRB_Lun ) )
lpsrb->SRB_Status = SS_COMP;
if ( lpsrb->SRB_Status == SS_COMP )
lpsrb->SRB_DeviceType = DTC_CDROM;
else
lpsrb->SRB_DeviceType = DTC_UNKNOWN;
return lpsrb->SRB_Status;
}
/*
* Looks up the index in the drive array for a given ha:tgt:lun triple
*/
BYTE NtScsiGetDeviceIndex( BYTE ha, BYTE tgt, BYTE lun )
{
BYTE i;
for( i = 2; i < 26; i++ )
{
if ( NtScsiDrives.drive[i].bUsed )
{
NTSCSIDRIVE *lpd;
lpd = &NtScsiDrives.drive[i];
if ( (lpd->ha == ha) && (lpd->tgt == tgt) && (lpd->lun == lun) )
return i;
}
}
return 0;
}
/*
* Converts ASPI-style SRB to SCSI Pass Through IOCTL
*/
DWORD NtScsiExecSCSICommand( LPSRB_EXECSCSICMD lpsrb, BOOL bBeenHereBefore )
{
BOOL status;
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb;
ULONG length, returned;
BYTE idx;
idx = NtScsiGetDeviceIndex( lpsrb->SRB_HaId, lpsrb->SRB_Target, lpsrb->SRB_Lun );
if ( idx == 0 )
{
lpsrb->SRB_Status = SS_ERR;
return SS_ERR;
}
if ( lpsrb->CDBByte[0] == 0x12 ) // is it an INQUIRY?
{
lpsrb->SRB_Status = SS_COMP;
memcpy( lpsrb->SRB_BufPointer, NtScsiDrives.drive[idx].inqData, 36 );
return SS_COMP;
}
if ( NtScsiDrives.drive[idx].hDevice == INVALID_HANDLE_VALUE )
NtScsiDrives.drive[idx].hDevice = GetFileHandle( NtScsiDrives.drive[idx].driveLetter );
ZeroMemory( &swb, sizeof(swb) );
swb.spt.Length = sizeof(SCSI_PASS_THROUGH);
swb.spt.CdbLength = lpsrb->SRB_CDBLen;
if ( lpsrb->SRB_Flags & SRB_DIR_IN )
swb.spt.DataIn = SCSI_IOCTL_DATA_IN;
else if ( lpsrb->SRB_Flags & SRB_DIR_OUT )
swb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
else
swb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
swb.spt.DataTransferLength = lpsrb->SRB_BufLen;
swb.spt.TimeOutValue = 5;
swb.spt.DataBuffer = lpsrb->SRB_BufPointer;
swb.spt.SenseInfoOffset =
offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf );
memcpy( swb.spt.Cdb, lpsrb->CDBByte, lpsrb->SRB_CDBLen );
length = sizeof(swb);
status = DeviceIoControl( NtScsiDrives.drive[idx].hDevice,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&swb,
length,
&swb,
length,
&returned,
NULL );
if ( status )
{
lpsrb->SRB_Status = SS_COMP;
}
else
{
DWORD dwErrCode;
lpsrb->SRB_Status = SS_ERR;
lpsrb->SRB_TargStat = 0x0004;
dwErrCode = GetLastError();
/*
* KLUDGE ALERT! KLUDGE ALERT! KLUDGE ALERT!
* Whenever a disk changer switches disks, it may render the device
* handle invalid. We try to catch these errors here and recover
* from them.
*/
if ( !bBeenHereBefore &&
((dwErrCode == ERROR_MEDIA_CHANGED) || (dwErrCode == ERROR_INVALID_HANDLE)) )
{
if ( dwErrCode != ERROR_INVALID_HANDLE )
CloseHandle( NtScsiDrives.drive[idx].hDevice );
GetDriveInformation( idx, &NtScsiDrives.drive[idx] );
return NtScsiExecSCSICommand( lpsrb, TRUE );
}
}
return lpsrb->SRB_Status;
}
BOOL UsingSCSIPT( void )
{
return bUseNtScsi;
}
/*
* Calls GetFileHandle for the CD refered to by ha:tgt:lun to open it for
* use
*/
void NtScsiOpenCDHandle( BYTE ha, BYTE tgt, BYTE lun )
{
BYTE idx;
idx = NtScsiGetDeviceIndex( ha, tgt, lun );
if ( idx && NtScsiDrives.drive[idx].hDevice == INVALID_HANDLE_VALUE )
NtScsiDrives.drive[idx].hDevice = GetFileHandle( NtScsiDrives.drive[idx].driveLetter );
}