/*++

Copyright (c) 1991, Microsoft Corporation

Module Name:

    vcdex.c

Abstract:

    Virtual Device Driver for MSCDEX

Environment:

    NT-MVDM (User Mode VDD)

Author:

    Neil Sandlin (neilsa), 3/20/93

Notes:

    Implementation Restrictions-

    Currently, the starting and ending locations returned by the mscdex
    "audio status info" are not returned by NT drivers. This makes it
    difficult to maintain these values when the calling applications
    exit, or when multiple applications are controlling a single drive.

    Currently, this implementation does not validate the length argument of
    the IOCTL calls. This needs to be added for robustness, but will not
    affect well-behaved apps.


Revision History:



--*/

//
// Include files.
//

#include "windows.h"
#include "winerror.h"
#include "stdio.h"
#include "malloc.h"
#include <vddsvc.h>
#include <mscdex.h>
#include "devioctl.h"
#include "ntddcdrm.h"
#include "ntdddisk.h"
#include "vcdex.h"

//
// Global variables.
//

PFNSVC  apfnSVC [] = {
    ApiGetNumberOfCDROMDrives,
    ApiGetCDROMDriveList,
    ApiGetCopyrightFileName,
    ApiGetAbstractFileName,
    ApiGetBDFileName,
    ApiReadVTOC,
    ApiReserved,
    ApiReserved,
    ApiAbsoluteDiskRead,
    ApiAbsoluteDiskWrite,
    ApiReserved,
    ApiCDROMDriveCheck,
    ApiMSCDEXVersion,
    ApiGetCDROMDriveLetters,
    ApiGetSetVolDescPreference,
    ApiGetDirectoryEntry,
    ApiSendDeviceRequest
};

PDRIVE_INFO DrivePointers[MAXDRIVES];
PDRIVE_INFO DrvInfo;
LPREQUESTHEADER VdmReq;                     // for "send device request"
USHORT NumDrives = 0, FirstDrive = 0xff;
DWORD DeviceHeader;                         // for "get CDROM drive list"
BYTE LastRealStatus = AUDIO_STATUS_NO_STATUS;

#define IS_DRIVE_CDROM(drive)	    \
                        (drive < MAXDRIVES && DrivePointers[drive] != NULL)


HANDLE hVdd;



VOID UserBlockHook(VOID);
VOID UserTerminateHook(USHORT);



BOOL
VDDInitialize(
    HANDLE hDll,
    DWORD dwReason,
    LPVOID lpReserved
    )

/*++

Routine Description:

    The entry point for the Vdd which handles intialization and termination.

Arguments:

    hVdd   - The handle to the VDD

    Reason - flag word thatindicates why Dll Entry Point was invoked

    lpReserved - Unused

Return Value:
    BOOL bRet - if (dwReason == DLL_PROCESS_ATTACH)
                   TRUE    - Dll Intialization successful
                   FALSE   - Dll Intialization failed
                else
                   always returns TRUE
--*/

{
    int i;

    switch ( dwReason ) {

        case DLL_PROCESS_ATTACH:
            hVdd = hDll;

            DisableThreadLibraryCalls(hDll);

            DebugPrint (DEBUG_MOD, "VCDEX: Process Attach\n");
            break;

        case DLL_PROCESS_DETACH:

            for (i=0; i<MAXDRIVES; i++)

                if (DrivePointers[i] != NULL) {

                    if (DrivePointers[i]->Handle != INVALID_HANDLE_VALUE) {
                        CloseHandle(DrivePointers[i]->Handle);
                    }
                    free (DrivePointers[i]);

                }

            DebugPrint (DEBUG_MOD, "VCDEX: Process Detach\n");
            break;

        default:

            break;

    }

    return TRUE;

}



VOID
VDDRegisterInit(
    VOID
    )
/*++

Routine Description:

    This routine is called when the MSCDEXNT TSR makes its RegisterModule
    call. Most of the initialization is done here instead of in the
    VDDInitialize routine to improve performance in the case where the
    VDD is loaded, but not used.

    The main point of this routine is to search for CDROM drives and set
    up an array of pointers to point to DRIVE_INFO structures. The array
    is a fixed size array, one for each possible DOS drive. The structures
    are allocated only if a CDROM drive exists at the corresponding drive
    letter in the array.

    By doing a CreateFile() to the drive letter of the drive, a handle to
    the SCSI CDROM class driver is returned. This handle is used for all
    communication with the drive.



Return Value:

    SUCCESS - Client carry is clear
              Client CX = # of CDROM drives

    FAILURE - Client carry is set

--*/


{
    CHAR chRoot [] = "?:\\";
    DWORD DriveMap;
    USHORT i;
    HANDLE hDriver;
    PDRIVE_INFO drvp;
    static BOOLEAN Initialized = FALSE;

    if (Initialized) {
        setCF(0);
        return;
    }


    // Make far pointer with offset zero (DX is para aligned)
    DeviceHeader = (DWORD) ((getCX() << 16) + (getDX() << 12));

    DriveMap = GetLogicalDrives();

    for (i=0; i<MAXDRIVES; i++) {

        if (DriveMap & 1) {

            chRoot[0] = i + 'A';

            if (GetDriveType((LPSTR) chRoot) == DRIVE_CDROM) {

                hDriver = OpenPhysicalDrive(i);

                if (hDriver != INVALID_HANDLE_VALUE) {


		    drvp = (PDRIVE_INFO) malloc (sizeof (DRIVE_INFO));

		    if(drvp == NULL) {
			DebugPrint (DEBUG_MOD, "VCDEX: Out of memory on initializetion\n");
			Initialized = FALSE;
			setCF(1);
			return;
		    }

                    DrivePointers[i]   = drvp;
                    drvp->Handle       = hDriver;
                    drvp->DriveNum     = i;
                    drvp->ValidVTOC    = FALSE;
                    drvp->MediaStatus  = MEDCHNG_CHANGED;

                    drvp->PlayStart.dw = 0;
                    drvp->PlayCount    = 0;
                    GetAudioStatus (drvp);

                    NumDrives++;
                    if (FirstDrive == 0xff)
                        FirstDrive = i;


                    //
                    // Keep the handle close until app really wants to use it
                    //
                    drvp->Handle  = INVALID_HANDLE_VALUE;
                    CloseHandle(hDriver);


                } else {
                    DrivePointers[i] = NULL;
                }

            }
        }
        DriveMap = DriveMap >> 1;
    }

    if (NumDrives == 0) {

        setCF(1);

    } else {

        VDDInstallUserHook(hVdd, NULL, UserTerminateHook, UserBlockHook, NULL);

        DebugPrint (DEBUG_MOD, "VCDEX: Initialized\n");
        Initialized = TRUE;

        setCF(0);

    }

    return;
}


VOID UserTerminateHook(USHORT Pdb)
{
    UserBlockHook();
}


VOID UserBlockHook(VOID)
{

    int DrvNum;

    DrvNum = MAXDRIVES;
    while (DrvNum--) {
       if (DrivePointers[DrvNum] &&
           DrivePointers[DrvNum]->Handle != INVALID_HANDLE_VALUE )
         {
           CloseHandle(DrivePointers[DrvNum]->Handle);
           DrivePointers[DrvNum]->Handle = INVALID_HANDLE_VALUE;
       }
    }
}









VOID
VDDDispatch(
    VOID
    )
/*++

Routine Description:

    This is the main MSCDEX api function dispatcher. When this routine
    is entered, an int2f has just been executed. Client registers are
    set to what they were at the time of the call with the exception
    of AX, which must contain a handle for the DispatchCall(). The
    value of AX was pushed on the stack. So, this routine restores it,
    and uses AL to index into the function call table apfnSVC[].


Return Value:

    SUCCESS - Client carry is clear
    FAILURE - Client carry is set

--*/

{

    LPWORD  VdmWordPtr;
    WORD    VdmAX;
    ULONG   VdmAddress;

    //
    // The TSR pushes AX on the stack. Pick up the value here
    //

    VdmAddress = ( getSS() << 16 ) | getSP();

    VdmWordPtr = (LPWORD) GetVDMPointer ( VdmAddress, 2, FALSE);

    VdmAX = *VdmWordPtr;

    //
    // AL has the MSCDEX function code
    //
    setAX (VdmAX);                      //restore AX

    (apfnSVC [VdmAX & 0xFF])();

    return;
}



/****************************************************************************

        MSCDEX API SUBROUTINES

    The following routines perform the individual functions specified by
    the MSCDEX extensions.


 ****************************************************************************/
VOID
ApiReserved(
    VOID
    )

{

    DebugFmt (DEBUG_API, "VCDEX: Reserved Function call, ax=%.4X\n", getAX());

}


VOID
ApiGetNumberOfCDROMDrives(
    VOID
    )

{

    DebugPrint (DEBUG_API, "VCDEX: Get # of CDROM drives\n");

    setBX (NumDrives);

    if (NumDrives)
        setCX (FirstDrive);

}


VOID
ApiGetCDROMDriveList(
    VOID
    )

{

    PDRIVE_DEVICE_LIST devlist, devlist0;
    ULONG   VdmAddress;
    USHORT  Drive;
    BYTE    Unit;

    DebugPrint (DEBUG_API, "VCDEX: Get CDROM drive list\n");

    VdmAddress = ( getES() << 16 ) | getBX();
    devlist = devlist0 = (PDRIVE_DEVICE_LIST) GetVDMPointer (VdmAddress,
                                          MAXDRIVES*sizeof(DRIVE_DEVICE_LIST),
                                          FALSE);

    for (Drive=0, Unit=0; Drive<MAXDRIVES; Drive++)
        if (DrivePointers[Drive] != NULL) {
            devlist->Unit = Unit;
            devlist->DeviceHeader = DeviceHeader;
            devlist++;
            Unit++;
        }

    FreeVDMPointer (VdmAddress,
                    MAXDRIVES*sizeof(DRIVE_DEVICE_LIST),
                    devlist0,
                    FALSE);


}

VOID
ApiGetCopyrightFileName(
    VOID
    )
{
    ULONG   VdmAddress;
    LPBYTE  fnBuffer;

    DebugPrint (DEBUG_API, "VCDEX: Get Copyright File Name\n");

    if (!IS_DRIVE_CDROM(getCX())) {	// Is drive CDROM?
        setAX (15);                         // no
        setCF (1);
    }

    VdmAddress = ( getES() << 16 ) | getBX();
    fnBuffer = (LPBYTE) GetVDMPointer (VdmAddress, 38, FALSE);

    *fnBuffer = 0;                  // currently not implemented

    FreeVDMPointer (VdmAddress, 38, fnBuffer, FALSE);

}

VOID
ApiGetAbstractFileName(
    VOID
    )
{

    ULONG   VdmAddress;
    LPBYTE  fnBuffer;

    DebugPrint (DEBUG_API, "VCDEX: Get Abstract File Name\n");

    if (!IS_DRIVE_CDROM(getCX())) {	// Is drive CDROM?
        setAX (15);                         // no
        setCF (1);
    }

    VdmAddress = ( getES() << 16 ) | getBX();
    fnBuffer = (LPBYTE) GetVDMPointer (VdmAddress, 38, FALSE);

    *fnBuffer = 0;                  // currently not implemented

    FreeVDMPointer (VdmAddress, 38, fnBuffer, FALSE);

}


VOID
ApiGetBDFileName(
    VOID
    )
{

    ULONG   VdmAddress;
    LPBYTE  fnBuffer;

    DebugPrint (DEBUG_API, "VCDEX: Get Bibliographic Doc File Name\n");

    if (!IS_DRIVE_CDROM(getCX())) {	// Is drive CDROM?
        setAX (15);                         // no
        setCF (1);
    }

    VdmAddress = ( getES() << 16 ) | getBX();
    fnBuffer = (LPBYTE) GetVDMPointer (VdmAddress, 38, FALSE);

    *fnBuffer = 0;                  // currently not implemented

    FreeVDMPointer (VdmAddress, 38, fnBuffer, FALSE);

}

VOID
ApiReadVTOC(
    VOID
    )
{

    DebugPrint (DEBUG_API, "VCDEX: Read VTOC\n");
    setCF(1);                       // currently not implemented

}



VOID
ApiAbsoluteDiskRead(
    VOID
    )
{

    DebugPrint (DEBUG_API, "VCDEX: Absolute Disk Read\n");
    setCF(1);                       // currently not implemented

}

VOID
ApiAbsoluteDiskWrite(
    VOID
    )
{
    DebugPrint (DEBUG_API, "VCDEX: Absolute Disk Write\n");
    setCF(1);                       // read only
}


VOID
ApiCDROMDriveCheck(
    VOID
    )

{

    DebugPrint (DEBUG_API, "VCDEX: CDROM drive check\n");

    setBX (0xADAD);                     // MSCDEX Signature

    if (IS_DRIVE_CDROM(getCX()))	// is CD ROM drive
        setAX (1);                      // yes
    else
        setAX (0);                      // no

}

VOID
ApiMSCDEXVersion(
    VOID
    )

{
    DebugPrint (DEBUG_API, "VCDEX: MSCDEX Version\n");
    setBX (MSCDEX_VERSION);                     // MSCDEX Version #

}

VOID
ApiGetCDROMDriveLetters(
    VOID
    )

{
    ULONG   VdmAddress;
    LPBYTE  VdmPtr, VdmPtr0;
    USHORT  Drive;

    DebugPrint (DEBUG_API, "VCDEX: Get CDROM Drive Letters\n");

    VdmAddress = (getES() << 16) | getBX();
    VdmPtr = VdmPtr0 = (LPBYTE) GetVDMPointer (VdmAddress, MAXDRIVES, FALSE);

    for (Drive=0; Drive<MAXDRIVES; Drive++)
        if (DrivePointers[Drive] != NULL)
            *VdmPtr++ = (BYTE) Drive;

    FreeVDMPointer (VdmAddress, MAXDRIVES, VdmPtr0, FALSE);

}


VOID
ApiGetSetVolDescPreference(
    VOID
    )
{

    DebugPrint (DEBUG_API, "VCDEX: Set Volume Descriptor Preference\n");
    setCF(1);                       // currently not implemented

}

VOID
ApiGetDirectoryEntry(
    VOID
    )
{

    DebugPrint (DEBUG_API, "VCDEX: Get Directory Entry\n");
    setCF(1);                       // currently not implemented

}



VOID
ApiSendDeviceRequest(
    VOID
    )
{

    ULONG   VdmAddress;
    BOOL    Success;
    DWORD   BytesReturned;
    DWORD absStart, absEnd;
    int     DrvNum;

    VdmAddress = ( getES() << 16 ) | getBX();
    VdmReq = (LPREQUESTHEADER) GetVDMPointer (VdmAddress,
                                              sizeof (REQUESTHEADER),
                                              FALSE);


    DebugFmt (DEBUG_IO, ">RQ %d ", (DWORD) VdmReq->rhFunction);

    DrvNum = getCX();

    if (!IS_DRIVE_CDROM(DrvNum)) {
        VdmReq->rhStatus = CDSTAT_ERROR | CDSTAT_DONE | CDERR_UNKNOWN_UNIT;
        return;

    }

    DrvInfo = DrivePointers[DrvNum];

    if (DrvInfo->Handle == INVALID_HANDLE_VALUE) {
        DrvInfo->Handle = OpenPhysicalDrive(DrvInfo->DriveNum);
        if (DrvInfo->Handle == INVALID_HANDLE_VALUE) {
            VdmReq->rhStatus = CDSTAT_ERROR | CDSTAT_DONE | CDERR_UNKNOWN_UNIT;
            free(DrvInfo);
            DrivePointers[DrvNum] = NULL;
            NumDrives--;
            if (FirstDrive == DrvNum) {
                FirstDrive = 0xff;
                while (++DrvNum < MAXDRIVES) {
                    if (DrivePointers[DrvNum]) {
                        FirstDrive = DrvNum;
                        break;
                    }
                }
            }

            return;
        }
    }


    GetAudioStatus (DrvInfo);

    if (DrvInfo->Playing)
        VdmReq->rhStatus |= CDSTAT_BUSY;

    switch (VdmReq->rhFunction) {

        case IOCTL_READ:

            IOCTLRead();

            break;

        case IOCTL_WRITE:

            IOCTLWrite();

            break;

        case INPUT_FLUSH:
        case OUTPUT_FLUSH:
        case DEVICE_OPEN:
        case DEVICE_CLOSE:
        case READ_LONG:
        case READ_LONG_PREFETCH:
        case SEEK:
            DebugPrint (DEBUG_API, "Unsupported MSCDEX Device Request\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;
            break;

        case PLAY_AUDIO: {

            CDROM_PLAY_AUDIO_MSF PlayAudioMSF;
            PPLAY_AUDIO_BLOCK playreq = (PPLAY_AUDIO_BLOCK) VdmReq;

            if (playreq->addrmode == MODE_HSG) {

                absStart = playreq->startsect.dw;
                PlayAudioMSF.StartingM = (BYTE) (absStart / (75 * 60));
                PlayAudioMSF.StartingS = (BYTE) ((absStart / 75) % 60);
                PlayAudioMSF.StartingF = (BYTE) (absStart % 75);

            } else if (playreq->addrmode == MODE_REDBOOK) {

                PlayAudioMSF.StartingM = playreq->startsect.b[2];
                PlayAudioMSF.StartingS = playreq->startsect.b[1];
                PlayAudioMSF.StartingF = playreq->startsect.b[0];

                absStart = (PlayAudioMSF.StartingM * 75 * 60) +
                           (PlayAudioMSF.StartingS * 75) +
                           (PlayAudioMSF.StartingF);
            } else {

                VdmReq->rhStatus = CDSTAT_ERROR | CDERR_PARAMETER;
                break;

            }

            absEnd = absStart + playreq->numsect - 1;

            PlayAudioMSF.EndingM = (BYTE) (absEnd / (75 * 60));
            PlayAudioMSF.EndingS = (BYTE) ((absEnd / 75) % 60);
            PlayAudioMSF.EndingF = (BYTE) (absEnd % 75);

            DebugPrint (DEBUG_IO, "Play ");

            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_PLAY_AUDIO_MSF,
                                      (LPVOID) &PlayAudioMSF,
                                      sizeof (CDROM_PLAY_AUDIO_MSF),
                                      (LPVOID) NULL, 0,
                                      &BytesReturned, (LPVOID) NULL);

            if (!Success)

                ProcessError (DrvInfo, PLAY_AUDIO,0);

            else {

                DrvInfo->Playing = TRUE;
                DrvInfo->Paused = FALSE;
                DrvInfo->PlayStart.dw = playreq->startsect.dw;
                DrvInfo->PlayCount = playreq->numsect;

            }

            break;
        }

        case STOP_AUDIO:

            if (DrvInfo->Playing) {

                DebugPrint (DEBUG_IO, "Pause ");
                Success = DeviceIoControl (DrvInfo->Handle,
                                          (DWORD) IOCTL_CDROM_PAUSE_AUDIO,
                                          (LPVOID) NULL, 0,
                                          (LPVOID) NULL, 0,
                                          &BytesReturned, (LPVOID) NULL);

                if (!Success)

                    ProcessError (DrvInfo, STOP_AUDIO,0);

                else {
                    DrvInfo->Playing = FALSE;
                    DrvInfo->Paused = TRUE;
                }

            } else {

                DebugPrint (DEBUG_IO, "Stop ");

                Success = DeviceIoControl (DrvInfo->Handle,
                                          (DWORD) IOCTL_CDROM_STOP_AUDIO,
                                          (LPVOID) NULL, 0,
                                          (LPVOID) NULL, 0,
                                          &BytesReturned, (LPVOID) NULL);

                // Fake out GetAudioStatus to simulate stop
                DrvInfo->Playing = FALSE;
                DrvInfo->Paused = FALSE;
                LastRealStatus = AUDIO_STATUS_PLAY_COMPLETE;

                if (!Success) {
                    DWORD dwErr;

                    dwErr = GetLastError();
                    if (dwErr == ERROR_MR_MID_NOT_FOUND ||
                        dwErr == ERROR_NO_MEDIA_IN_DRIVE )
                      {
                       CloseHandle(DrvInfo->Handle);
                       DrvInfo->Handle = INVALID_HANDLE_VALUE;
                    }
                }

            }

            break;

        case WRITE_LONG:
        case WRITE_LONG_VERIFY:

            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_WRITE_PROTECT;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;

            break;

        case RESUME_AUDIO:

            if (DrvInfo->Paused) {

                DebugPrint (DEBUG_IO, "Resume ");
                Success = DeviceIoControl (DrvInfo->Handle,
                                          (DWORD) IOCTL_CDROM_RESUME_AUDIO,
                                          (LPVOID) NULL, 0,
                                          (LPVOID) NULL, 0,
                                          &BytesReturned, (LPVOID) NULL);

                if (!Success)

                    ProcessError (DrvInfo, RESUME_AUDIO,0);

            } else {

                VdmReq->rhStatus = CDSTAT_ERROR | CDERR_GENERAL;
                CloseHandle(DrvInfo->Handle);
                DrvInfo->Handle = INVALID_HANDLE_VALUE;
            }

            break;

        default:
            DebugPrint (DEBUG_API, "Invalid MSCDEX Device Request\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;

    }

    VdmReq->rhStatus |= CDSTAT_DONE;

    DebugFmt (DEBUG_IO, ": %.4X   ", VdmReq->rhStatus);

}

VOID
IOCTLRead(
    VOID
    )

{

    LPBYTE Buffer;
    BOOL Success;
    DWORD   BytesReturned;

    Buffer = GetVDMPointer ((ULONG)VdmReq->irwrBuffer, 16, FALSE);

    DebugFmt (DEBUG_IO, "iord %d ", (DWORD) *Buffer);

    switch (*Buffer) {

        case IOCTLR_AUDINFO: {

            PIOCTLR_AUDINFO_BLOCK audinfo = (PIOCTLR_AUDINFO_BLOCK) Buffer;
            VOLUME_CONTROL VolumeControl;

            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_GET_VOLUME,
                                      (LPVOID) NULL, 0,
                                      (LPVOID) &VolumeControl,
                                      sizeof (VOLUME_CONTROL),
                                      &BytesReturned, (LPVOID) NULL);

            if (Success) {

                // no support for input=>output channel manipulation
                audinfo->chan0 = 0;
                audinfo->chan1 = 1;
                audinfo->chan2 = 2;
                audinfo->chan3 = 3;

                audinfo->vol0 = VolumeControl.PortVolume[0];
                audinfo->vol1 = VolumeControl.PortVolume[1];
                audinfo->vol2 = VolumeControl.PortVolume[2];
                audinfo->vol3 = VolumeControl.PortVolume[3];

            } else {
                CloseHandle(DrvInfo->Handle);
                DrvInfo->Handle = INVALID_HANDLE_VALUE;
            }

            break;
        }

        case IOCTLR_DEVSTAT: {

            PIOCTLR_DEVSTAT_BLOCK devstat = (PIOCTLR_DEVSTAT_BLOCK) Buffer;

            devstat->devparms = DEVSTAT_DOOR_UNLOCKED |
                                DEVSTAT_SUPPORTS_RBOOK;


            if (!DrvInfo->StatusAvailable) {

                DrvInfo->MediaStatus = MEDCHNG_CHANGED;
                CloseHandle(DrvInfo->Handle);
                DrvInfo->Handle = INVALID_HANDLE_VALUE;

                switch (DrvInfo->LastError) {
                    case ERROR_NO_MEDIA_IN_DRIVE:
                        devstat->devparms |= DEVSTAT_NO_DISC |
                                             DEVSTAT_DOOR_OPEN;

                        DebugFmt (DEBUG_STATUS, ":%.4X ", devstat->devparms);

                        break;
                    //BUGBUG case for recently inserted (80000016) - see below
                }

                break;
            }

            if (!(DrvInfo->current.Control & AUDIO_DATA_TRACK))
                devstat->devparms |= DEVSTAT_PLAYS_AV;

            break;
        }

        case IOCTLR_VOLSIZE: {

            PIOCTLR_VOLSIZE_BLOCK volsize = (PIOCTLR_VOLSIZE_BLOCK) Buffer;
            PTRACK_DATA Track;
            PCDROM_TOC cdromtoc;

            if ((cdromtoc = ReadTOC (DrvInfo))!=NULL) {

                Track = &cdromtoc->TrackData[cdromtoc->LastTrack];

                volsize->size = (DWORD) ( (Track->Address[1]*60*75) +
                                          (Track->Address[2]*75) +
                                           Track->Address[3]      );

            }
            break;
        }

        case IOCTLR_MEDCHNG: {

            PIOCTLR_MEDCHNG_BLOCK medptr = (PIOCTLR_MEDCHNG_BLOCK) Buffer;
            BYTE status = DrvInfo->MediaStatus;

            if (status == MEDCHNG_NOT_CHANGED) {

                Success = DeviceIoControl (DrvInfo->Handle,
                                          (DWORD) IOCTL_CDROM_CHECK_VERIFY,
                                          (LPVOID) NULL, 0,
                                          (LPVOID) NULL, 0,
                                          &BytesReturned, (LPVOID) NULL);

                if (Success)

                    medptr->medbyte = MEDCHNG_NOT_CHANGED;

                else {

                    medptr->medbyte = MEDCHNG_CHANGED;
                    DrvInfo->MediaStatus = MEDCHNG_CHANGED;
                    CloseHandle(DrvInfo->Handle);
                    DrvInfo->Handle = INVALID_HANDLE_VALUE;
                }

            } else
                medptr->medbyte = DrvInfo->MediaStatus;

            break;
        }

        case IOCTLR_DISKINFO: {

            PIOCTLR_DISKINFO_BLOCK diskinfo = (PIOCTLR_DISKINFO_BLOCK) Buffer;
            PTRACK_DATA Track;
            PCDROM_TOC cdromtoc;

            if ((cdromtoc = ReadTOC (DrvInfo))!=NULL) {
                diskinfo->tracklow = cdromtoc->FirstTrack;
                diskinfo->trackhigh = cdromtoc->LastTrack;

                Track = &cdromtoc->TrackData[cdromtoc->LastTrack];

                diskinfo->startleadout.b[0] = Track->Address[3];
                diskinfo->startleadout.b[1] = Track->Address[2];
                diskinfo->startleadout.b[2] = Track->Address[1];
                diskinfo->startleadout.b[3] = Track->Address[0];

            } else {

                // zeroes apparently needed when not there physically
                diskinfo->tracklow = 0;
                diskinfo->trackhigh = 0;
                diskinfo->startleadout.dw = 0;

            }
            break;
        }

        case IOCTLR_TNOINFO: {

            PIOCTLR_TNOINFO_BLOCK tnoinfo = (PIOCTLR_TNOINFO_BLOCK) Buffer;
            PTRACK_DATA Track;
            PCDROM_TOC cdromtoc;

            if ((cdromtoc = ReadTOC (DrvInfo))!=NULL) {

                if (tnoinfo->trknum > cdromtoc->LastTrack) {
                    VdmReq->rhStatus = CDSTAT_ERROR | CDERR_SECT_NOTFOUND;
                    break;
                }

                Track = &cdromtoc->TrackData[tnoinfo->trknum-1];
                tnoinfo->start.b[0] = Track->Address[3];
                tnoinfo->start.b[1] = Track->Address[2];
                tnoinfo->start.b[2] = Track->Address[1];
                tnoinfo->start.b[3] = Track->Address[0];

                tnoinfo->trkctl = Track->Control;
            }

            break;
        }

        case IOCTLR_QINFO: {

            PIOCTLR_QINFO_BLOCK qinfo = (PIOCTLR_QINFO_BLOCK) Buffer;

            if (DrvInfo->StatusAvailable) {

                qinfo->ctladr = DrvInfo->current.Control | DrvInfo->current.ADR<<4;
                qinfo->trknum = DrvInfo->current.TrackNumber;
                qinfo->pointx = DrvInfo->current.IndexNumber;
                qinfo->min = DrvInfo->current.TrackRelativeAddress[1];
                qinfo->sec = DrvInfo->current.TrackRelativeAddress[2];
                qinfo->frame = DrvInfo->current.TrackRelativeAddress[3];

                qinfo->zero = DrvInfo->current.AbsoluteAddress[0];
                qinfo->apmin = DrvInfo->current.AbsoluteAddress[1];
                qinfo->apsec = DrvInfo->current.AbsoluteAddress[2];
                qinfo->apframe = DrvInfo->current.AbsoluteAddress[3];

            } else {
                CloseHandle(DrvInfo->Handle);
                DrvInfo->Handle = INVALID_HANDLE_VALUE;
            }

            break;
        }

        case IOCTLR_UPCCODE: {

            PIOCTLR_UPCCODE_BLOCK upccode = (PIOCTLR_UPCCODE_BLOCK) Buffer;
            SUB_Q_MEDIA_CATALOG_NUMBER MediaCatalog;
            static CDROM_SUB_Q_DATA_FORMAT subqfmt = {IOCTL_CDROM_MEDIA_CATALOG};
            int i;

            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_READ_Q_CHANNEL,
                                      (LPVOID) &subqfmt,
                                      sizeof (CDROM_SUB_Q_DATA_FORMAT),
                                      (LPVOID) &MediaCatalog,
                                      sizeof (SUB_Q_MEDIA_CATALOG_NUMBER),
                                      &BytesReturned, (LPVOID) NULL);

            if (!Success)

                ProcessError (DrvInfo, IOCTL_READ, IOCTLR_UPCCODE);

            else {

                if (MediaCatalog.Mcval) {

                    // The author is uncertain that this is the correct method,
                    // but it appears to work empirically.
                    for (i=0; i<7; i++)
                        upccode->upcean[i] = MediaCatalog.MediaCatalog[i];

                } else

                    VdmReq->rhStatus = CDSTAT_ERROR | CDERR_SECT_NOTFOUND;

            }

            break;
        }

        case IOCTLR_AUDSTAT: {
            PIOCTLR_AUDSTAT_BLOCK audstat = (PIOCTLR_AUDSTAT_BLOCK) Buffer;

            audstat->audstatbits = 0;

            if (DrvInfo->Paused)
                audstat->audstatbits |= AUDSTAT_PAUSED;

            audstat->startloc.dw = DrvInfo->PlayStart.dw;
            audstat->endloc.dw = DrvInfo->PlayCount;

            break;
        }


        case IOCTLR_RADDR:
        case IOCTLR_LOCHEAD:
        case IOCTLR_ERRSTAT:
        case IOCTLR_DRVBYTES:
        case IOCTLR_SECTSIZE:
        case IOCTLR_SUBCHANINFO:
            DebugPrint (DEBUG_API, "Unsupported MSCDEX IOCTL Read\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;
            break;

        default:
            DebugPrint (DEBUG_API, "Invalid MSCDEX IOCTL Read\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;

    }
}

VOID
IOCTLWrite(
    VOID
    )

{
    LPBYTE Buffer;
    BOOL Success;
    DWORD   BytesReturned;

    Buffer = GetVDMPointer ((ULONG)VdmReq->irwrBuffer, 16, FALSE);

    DebugFmt (DEBUG_IO, "iowt %d ", (DWORD) *Buffer);

    switch (*Buffer) {

        case IOCTLW_EJECT:
            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_EJECT_MEDIA,
                                      (LPVOID) NULL, 0,
                                      (LPVOID) NULL, 0,
                                      &BytesReturned, (LPVOID) NULL);

            if (!Success)
                ProcessError (DrvInfo, IOCTL_WRITE, IOCTLW_EJECT);
            break;

        case IOCTLW_LOCKDOOR: {

            PREVENT_MEDIA_REMOVAL MediaRemoval;
            PIOCTLW_LOCKDOOR_BLOCK lockdoor = (PIOCTLW_LOCKDOOR_BLOCK) Buffer;

            MediaRemoval.PreventMediaRemoval = (BOOLEAN) lockdoor->lockfunc;

            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_MEDIA_REMOVAL,
                                      (LPVOID) &MediaRemoval,
                                      sizeof(PREVENT_MEDIA_REMOVAL),
                                      (LPVOID) NULL, 0,
                                      &BytesReturned, (LPVOID) NULL);

            if (!Success)
                ProcessError (DrvInfo, IOCTL_WRITE, IOCTLW_LOCKDOOR);
            break;
        }

        case IOCTLW_AUDINFO: {
            PIOCTLW_AUDINFO_BLOCK audinfo = (PIOCTLW_AUDINFO_BLOCK) Buffer;
            VOLUME_CONTROL VolumeControl;

            // note: no support for input=>output channel manipulation
            VolumeControl.PortVolume[0] = audinfo->vol0;
            VolumeControl.PortVolume[1] = audinfo->vol1;
            VolumeControl.PortVolume[2] = audinfo->vol2;
            VolumeControl.PortVolume[3] = audinfo->vol3;

            Success = DeviceIoControl (DrvInfo->Handle,
                                      (DWORD) IOCTL_CDROM_SET_VOLUME,
                                      (LPVOID) &VolumeControl,
                                      sizeof (VOLUME_CONTROL),
                                      (LPVOID) NULL, 0,
                                      &BytesReturned, (LPVOID) NULL);

            if (!Success)
                ProcessError (DrvInfo, IOCTL_WRITE, IOCTLW_AUDINFO);
            break;
        }



        case IOCTLW_RESETDRV:
        case IOCTLW_DRVBYTES:
        case IOCTLW_CLOSETRAY:
            DebugPrint (DEBUG_API, "Unsupported MSCDEX IOCTL Write\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;
            break;

        default:
            DebugPrint (DEBUG_API, "Invalid MSCDEX IOCTL Write\n");
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_UNKNOWN_CMD;
            CloseHandle(DrvInfo->Handle);
            DrvInfo->Handle = INVALID_HANDLE_VALUE;

    }


}


/**************************************************************************

        INTERNAL UTILITY ROUTINES

 **************************************************************************/

PCDROM_TOC
ReadTOC (
    PDRIVE_INFO DrvInfo
    )
/*++

Routine Description:

    Because several MSCDEX functions return information that is in the
    Volume Table Of Contents (VTOC), this routine is called to cache
    the TOC in the DRIVE_INFO structure. Subsequent operations that
    request information from the VTOC will not have to get it from
    the drive.

Return Value:

    DWORD value from GetLastError()

--*/

{
    BOOL    Success = TRUE;
    DWORD   BytesReturned;

    if ((DrvInfo->ValidVTOC) &&
        (DrvInfo->MediaStatus == MEDCHNG_NOT_CHANGED))
        return(&DrvInfo->VTOC);

    Success = DeviceIoControl (DrvInfo->Handle,
                              (DWORD) IOCTL_CDROM_READ_TOC,
                              (LPVOID) NULL, 0,
                              (LPVOID) &DrvInfo->VTOC, sizeof (CDROM_TOC),
                              &BytesReturned, (LPVOID) NULL);

    if (!Success) {
        DrvInfo->ValidVTOC = FALSE;
        ProcessError (DrvInfo, 0, 0);
        return (NULL);
        }

    DrvInfo->ValidVTOC = TRUE;
    DrvInfo->MediaStatus = MEDCHNG_NOT_CHANGED;
    return (&DrvInfo->VTOC);


}

BOOLEAN
GetAudioStatus(
    PDRIVE_INFO DrvInfo
    )

/*++

    Because the AudioStatus byte does not statically reflect the difference
    between paused and stopped, we have to try to watch for the transition
    from one state to another to keep track of it.

--*/
{

    static CDROM_SUB_Q_DATA_FORMAT subqfmt = {IOCTL_CDROM_CURRENT_POSITION};
    DWORD BytesReturned;
    BYTE AudStat;

    DrvInfo->Paused       = FALSE;
    DrvInfo->Playing      = FALSE;

    DrvInfo->StatusAvailable = DeviceIoControl (DrvInfo->Handle,
                                             (DWORD) IOCTL_CDROM_READ_Q_CHANNEL,
                                             (LPVOID) &subqfmt,
                                             sizeof (CDROM_SUB_Q_DATA_FORMAT),
                                             (LPVOID) &DrvInfo->current,
                                             sizeof (SUB_Q_CURRENT_POSITION),
                                             &BytesReturned, (LPVOID) NULL);

    if (DrvInfo->StatusAvailable) {

        AudStat = DrvInfo->current.Header.AudioStatus;

        DebugFmt (DEBUG_STATUS, "+%.2X ", AudStat);

        switch (AudStat) {

            case AUDIO_STATUS_IN_PROGRESS:

                DrvInfo->Paused = FALSE;
                DrvInfo->Playing = TRUE;
                LastRealStatus = AudStat;
                break;

            case AUDIO_STATUS_PAUSED:

                if (LastRealStatus == AUDIO_STATUS_IN_PROGRESS) {

                    DrvInfo->Playing = FALSE;
                    DrvInfo->Paused = TRUE;

                }
                break;

            case AUDIO_STATUS_PLAY_ERROR:
            case AUDIO_STATUS_PLAY_COMPLETE:

                DrvInfo->Paused = FALSE;
                DrvInfo->Playing = FALSE;
                LastRealStatus = AudStat;
                break;

        }

    } else {
        DrvInfo->LastError = GetLastError();
    }

    return (DrvInfo->StatusAvailable);

}


DWORD
ProcessError(
    PDRIVE_INFO DrvInfo,
    USHORT Command,
    USHORT Subcmd
    )
/*++

Routine Description:

    This routine is called when a DeviceIoControl() fails. The extended
    error code is retrieved, and status bits are set according to the
    operation that was in progress.

    The DriveInfo Handle is closed


    BUGBUG: At the time of this writing, NT fails to remap status 80000016,
    (when a disc is re-inserted into the drive). So the case for MID_NOT_FOUND
    was added below. This should be changed to the appropriate code when
    this error status is properly mapped.


Return Value:

    DWORD value from GetLastError()

--*/


{
    DWORD err;

    err = GetLastError();

    DebugFmt (DEBUG_ERROR, "Err! %d, ", Command);
    DebugFmt (DEBUG_ERROR, "%d: ", Subcmd);
    DebugFmt (DEBUG_ERROR, "%.8X\n", err);

    switch (err) {

        case ERROR_MR_MID_NOT_FOUND:        // To cover unmapped 80000016
        case ERROR_NO_MEDIA_IN_DRIVE:

            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_NOT_READY;
            DrvInfo->MediaStatus = MEDCHNG_CHANGED;
            break;

        default:
            VdmReq->rhStatus = CDSTAT_ERROR | CDERR_GENERAL;

    }

    CloseHandle(DrvInfo->Handle);
    DrvInfo->Handle = INVALID_HANDLE_VALUE;

    return (err);

}



HANDLE
OpenPhysicalDrive(
    int DriveNum
    )
/*++

Routine Description:

   int DriveNum; Zero based (0 = A, 1 = B, 2 = C ...)

Return Value:

    HANDLE Drive Handle as returned from CreateFile

--*/
{
    HANDLE hDrive;
    CHAR chDrive [] = "\\\\.\\?:";

    chDrive[4] = DriveNum + 'A';

    hDrive = CreateFile (chDrive,
                          GENERIC_READ,
                          FILE_SHARE_READ,
                          (LPSECURITY_ATTRIBUTES) NULL,
                          OPEN_EXISTING,
                          0,
                          (HANDLE) NULL);


    return hDrive;
}
