// Cdvd.cpp: implementation of the CCdvd class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Cdvd.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#if defined(CDVD_SIMPLE_INTERFACE)
// our fuckup method of interfacing ;p
CCdvd             OBJECT_HOLDER_CDVD;
CDVD_INTF_FUNC_T1 FUNCTION_GETNUMSECTORS;
CDVD_INTF_FUNC_T1 FUNCTION_GETTOC;
CDVD_INTF_FUNC_T1 FUNCTION_TESTREADY;
CDVD_INTF_FUNC_T2 FUNCTION_SETSPEED;
CDVD_INTF_FUNC_T1 FUNCTION_STOP;
CDVD_INTF_FUNC_T2 FUNCTION_SETSECTORSIZE;
CDVD_INTF_FUNC_T3 FUNCTION_PLAY;
CDVD_INTF_FUNC_T4 FUNCTION_READSECTOR;

#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CCdvd::CCdvd()
{
    m_hIoctlDrv         = NULL;
    m_hAspi             = NULL;
    m_bAspiInitialized  = FALSE;
    m_bIoctlInitialized = FALSE;
    m_bDriveOpened      = FALSE;  
    m_nCurrentDrive     = -1;
    m_nCurrentBuffer    = 0;
    m_nDrives           = 0;
    m_IntfType          = CDVD_INTF_UNKNOWN;
    m_nMmcDataMode      = CDVD_MMC_DATAMODE_RAW;
    m_nCurrentReadMode  = CDVD_READ_MMC;
    
    memset(m_drvDetails,    0, CDVD_MAX_SUPPORTED_DRIVES * sizeof(ADAPTERINFO));
    memset(m_srbReadCd,     0, CDVD_NUM_BUFFERS * sizeof(SRB_ExecSCSICmd));
    memset(m_srbRead10,     0, CDVD_NUM_BUFFERS * sizeof(SRB_ExecSCSICmd));     
    memset(m_srbReadMat,    0, CDVD_NUM_BUFFERS * sizeof(SRB_ExecSCSICmd));     
    memset(m_srbReadSony,   0, CDVD_NUM_BUFFERS * sizeof(SRB_ExecSCSICmd));     
    memset(m_srbReadNec,    0, CDVD_NUM_BUFFERS * sizeof(SRB_ExecSCSICmd));  
    memset(m_sptwbReadCd,   0, CDVD_NUM_BUFFERS * sizeof(SPTD_WITH_SENSE_BUFF));
    memset(m_sptwbRead10,   0, CDVD_NUM_BUFFERS * sizeof(SPTD_WITH_SENSE_BUFF));
    memset(m_sptwbReadMat,  0, CDVD_NUM_BUFFERS * sizeof(SPTD_WITH_SENSE_BUFF));
    memset(m_sptwbReadSony, 0, CDVD_NUM_BUFFERS * sizeof(SPTD_WITH_SENSE_BUFF));
    memset(m_sptwbReadNec,  0, CDVD_NUM_BUFFERS * sizeof(SPTD_WITH_SENSE_BUFF));
    
    memset(&m_tocDetails, 0, sizeof(TOCDATA));

    GetNumSectors = Dummy_T1;
    GetToc        = Dummy_T1;
    Stop          = Dummy_T1;
    Play          = Dummy_T3;
    TestReady     = Dummy_T1;
//  GetHeader     = Dummy_T1;
    SetSpeed      = Dummy_T2;
    SetSectorSize = Dummy_T2;
    ReadSector    = Dummy_T4;

    UpdateInterfaceObject();
}

CCdvd::~CCdvd()
{
    CloseDrive();
    Shutdown();
}

// based on msdn example
WIN32OSTYPE CCdvd::GetWin32OSType()
{
	WIN32OSTYPE wintype;
	UI32 version;
	OSVERSIONINFO *osvi;
	
	version = GetVersion();
	if(version < 0x80000000)
    {
		osvi = (OSVERSIONINFO *) malloc(sizeof(OSVERSIONINFO));
        if (osvi)
        {
			memset(osvi, 0, sizeof(OSVERSIONINFO));
			osvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
			GetVersionEx(osvi);
			if (osvi->dwMajorVersion >= 4L)
				wintype = WINNTNEW;
            else
                wintype = WINNTOLD;

			free(osvi);
		}
	}
	else if  (LOBYTE(LOWORD(version)) < 4L)
		wintype = WIN32S; 	
    else
	    wintype = WIN95;

    return wintype;
}

// Initializes an interface, only one should be initialized. :)
int CCdvd::Init(CDVD_INTERFACE_TYPE intf_type)
{
    int retval = CDVD_ERROR_SUCCESS;

    switch(intf_type)
    {
    case CDVD_INTF_ASPI:
         m_IntfType = CDVD_INTF_ASPI;
         Aspi_Init();
         break;
    //case CDVD_INTF_IOCTL:
    //     m_IntfType = CDVD_INTF_IOCTL;
    //     Ioctl_Init();
    //     break;
    case CDVD_INTF_UNKNOWN:
         if(m_bAspiInitialized) 
             retval = CDVD_INTF_ASPI;
         else 
             if(m_bIoctlInitialized) retval = CDVD_INTF_IOCTL;
         else 
             retval = CDVD_INTF_UNKNOWN;
         break;

    default:
         retval = CDVD_ERROR_FAIL;
         break;
    }

    return retval;
}

// shutsdown previously opened interface
int CCdvd::Shutdown()
{
    int retval = CDVD_ERROR_SUCCESS;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:
         Aspi_Shutdown();
         break;
    //case CDVD_INTF_IOCTL:
    //     Ioctl_Shutdown();
    //     break;
    default:  break;
    }

    m_IntfType = CDVD_INTF_UNKNOWN;

    return retval;
}

// retrieve number of c/dvd drives
int CCdvd::GetNumDrives() const
{
    return m_nDrives;
}

// open a drive for reading
int CCdvd::OpenDrive(int drv_num)
{
    int retval = CDVD_ERROR_FAIL;
    _ASSERT(drv_num < CDVD_MAX_SUPPORTED_DRIVES);
    
    // run-time check, for now.
    if(drv_num > (int) m_nDrives) 
        return CDVD_ERROR_FAIL;

    m_bDriveOpened = FALSE;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:
         if(Aspi_OpenDrive(drv_num) == CDVD_ERROR_SUCCESS)
         {
             m_bDriveOpened = TRUE;
             retval = CDVD_ERROR_SUCCESS;
         }
         break;
    //case CDVD_INTF_IOCTL:
    //    if(Ioctl_OpenDrive(drv_num) == CDVD_ERROR_SUCCESS)
    //    {
    //        m_bDriveOpened = TRUE;
    //        retval = CDVD_ERROR_SUCCESS;
    //    }
    default: break;
    }

    return retval;
}

// close a previously opened drive
int CCdvd::CloseDrive()
{
    int retval = CDVD_ERROR_SUCCESS;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:  Aspi_CloseDrive(); break;
    //case CDVD_INTF_IOCTL: Ioctl_CloseDrive(); break;
    default: break;
    }

    return retval;
}

// allocate and initialize buffers for BOTH aspi and ioctl
int CCdvd::InitializeBuffers()
{
    int retval = CDVD_ERROR_SUCCESS;
    UI32 result = 0xFFFFFFFF;    
    for(int i=0; i < CDVD_NUM_BUFFERS; i++)
    {
        if(m_ReadBuffer[i].AB_BufPointer != NULL) continue;
        m_ReadBuffer[i].AB_BufLen = CDVD_NUM_SECTORS_PER_BUFF * CDVD_SECTOR_SIZE_CD;
        m_ReadBuffer[i].AB_BufPointer = (PBYTE) VirtualAlloc
        (
            NULL,
            m_ReadBuffer[i].AB_BufLen,
            MEM_COMMIT,
            PAGE_READWRITE
        );

        result &= (UI32) m_ReadBuffer[i].AB_BufPointer;
        if(m_ReadBuffer[i].AB_BufPointer != NULL)
            memset(m_ReadBuffer[i].AB_BufPointer, 0, m_ReadBuffer[i].AB_BufLen);
    }

    if(result == 0)
    {
        ShutdownBuffers();
        retval = CDVD_ERROR_FAIL;
        TRACE("unable to allocate all buffers\n");
    }

    return retval;
}

// free-up previously allocated buffers
int CCdvd::ShutdownBuffers()
{
    int retval = CDVD_ERROR_SUCCESS;
    
    for(int i=0; i < CDVD_NUM_BUFFERS; i++)
    {
        if(m_ReadBuffer[i].AB_BufPointer != NULL)
            VirtualFree( m_ReadBuffer[i].AB_BufPointer, 0, MEM_RELEASE );
        m_ReadBuffer[i].AB_BufPointer = NULL;
        m_ReadBuffer[i].AB_BufLen = 0;
    }

    return retval;
}

// set the current buffer to be used
// only 2! at the moment
int CCdvd::SetCurrentBuffer(int buf_num)
{
    int retval = CDVD_ERROR_SUCCESS;
    if(buf_num >= CDVD_NUM_BUFFERS) 
        return CDVD_ERROR_FAIL;
    
    m_nCurrentBuffer = buf_num;

    return retval;
}

// this is terribly WRONG :P, but i dont care, works most of the time :p
int CCdvd::ExtractTocData(UI08 *buffer)
{
    int retval = CDVD_ERROR_SUCCESS;
    LPTOCDATA pdata = (LPTOCDATA) buffer;
    
    memcpy(&m_tocDetails, buffer, sizeof(TOCDATA));

    int total_tracks = 0;
    int mediatype = GetCdvdMediaType();

    UI32 numRead = ((UI32) pdata->datalen_byte1) << 8 | (UI32) (pdata->datalen_byte0);
    SetCurrentBuffer(0);
    
    memset(&m_trkDetails, 0, sizeof(DECODEDTRACKINFO));

    int offset = 4;
    for(int i = 0; i < pdata->last_track_num; i++)
    {
        offset = i*8+4;
        LPTOCTRACKDESCRIPTOR pdesc = (LPTOCTRACKDESCRIPTOR) (buffer + offset);   
 
        UI32 start_track = ((UI32) pdesc->lba_byte3) << 24 |
                           ((UI32) pdesc->lba_byte2) << 16 |
				           ((UI32) pdesc->lba_byte1) << 8  |
				            (UI32) pdesc->lba_byte0;

        // track types, 0x00 - cdda, 0x01 - data, 0x02 - cdxa
        UI08 tracktype = 0;
        if(mediatype == CDVD_MEDIATYPE_DVD)
        {
            tracktype = 0x01; // data sector
        }
        else if(((this)->*(ReadSector))(start_track, NULL) == CDVD_ERROR_SUCCESS)
        { 
             tracktype = (UI08) * (m_ReadBuffer[m_nCurrentBuffer].AB_BufPointer+15);
        }
        else
        {
            // skip setting info for this track, and try to proceed with others
            ++total_tracks;
            continue;
        }
        // okay, something is fucked.. just lie and say its a cdda :p
        if(tracktype > 2) tracktype = 0; 
       
        m_trkDetails.track_offset[i] = start_track;
        m_trkDetails.track_type[i]   = tracktype;
        
        TRACE("track %ld -> start_at: %ld with type: %ld\n", i, start_track, tracktype);
        ++total_tracks;
    }
    
    if(total_tracks) m_trkDetails.total_tracks = total_tracks;

    return retval;
}

// set read mode (i.e. mmc, scsi10 etc).
// Note: only mmc and scsi10 is enabled
//       i've disabled all goddamn proprietary read modes 
//       on this version (sony, nec, matsushita)
int CCdvd::SetReadMode(CDVD_READ_MODE read_mode, int data_mode)
{
    _ASSERT(read_mode >= CDVD_READ_MMC && read_mode <= CDVD_READ_SCSI10);

    m_nCurrentReadMode = read_mode;
    m_nMmcDataMode     = data_mode;

    int retval = CDVD_ERROR_SUCCESS;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:
         retval = Aspi_SetReadMode(read_mode);
         break;
    //case CDVD_INTF_IOCTL:
    //   retval = Ioctl_SetReadMode();
    //   break;
    default: 
        retval = CDVD_ERROR_FAIL;
        break;
    }

    return CDVD_ERROR_SUCCESS;
}

// returns CDVD_ERROR_PENDING if SS_PENDING, CDVD_ERROR_SUCCESS if SS_COMP
// and CDVD_ERROR_FAIL if you are a dumbass and you haven't set any read_mode
int CCdvd::GetSrbStatus(int srb_num)
{
    int retval = CDVD_ERROR_SUCCESS;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:
         retval = Aspi_GetSrbStatus(srb_num);
         break;
    //case CDVD_INTF_IOCTL:
    //   retval = Ioctl_GetSrbStatus(srb_num);
    //   break;
    default: 
        retval = CDVD_ERROR_FAIL;
        break;
    }

    return retval;
}

int CCdvd::GetCdvdConfiguration(UI16 feature, UI08 *dest, UI32 dest_size)
{
    int retval = CDVD_ERROR_SUCCESS;
    switch(m_IntfType)
    {
    case CDVD_INTF_ASPI:
         retval = Aspi_GetConfiguration(feature, dest, dest_size);
         break;
    //case CDVD_INTF_IOCTL:
    //   retval = Ioctl_GetConfiguration(feature, dest, dest_size);
    //   break;
    default: 
        retval = CDVD_ERROR_FAIL;
        break;
    }

    return retval;
}

const UI16 CDVD_FEATURE_READCD  = 0x001E;
const UI16 CDVD_FEATURE_READDVD = 0x001F;

// as the name says
int CCdvd::GetCdvdDriveType() 
{   
    int drivetype = CDVD_DRIVETYPE_UNKNOWN;
    UI08 buffer[1024];
    UI16 feature_available = 0;
    BOOL is_cdromdrive = FALSE;
    BOOL is_dvddrive   = FALSE;

    if(GetCdvdConfiguration(CDVD_FEATURE_READCD, buffer, sizeof(buffer)) 
        == CDVD_ERROR_SUCCESS)
    {
        feature_available = (UI16) buffer[8] << 8 | (UI16) buffer[9];
        if(feature_available == CDVD_FEATURE_READCD)
        {
            is_cdromdrive = TRUE;
        }
    }

    if(GetCdvdConfiguration(CDVD_FEATURE_READDVD, buffer, sizeof(buffer)) 
        == CDVD_ERROR_SUCCESS)
    {
        feature_available = (UI16) buffer[8] << 8 | (UI16) buffer[9];
        if(feature_available == CDVD_FEATURE_READDVD)
        {
            is_dvddrive = TRUE;
        }
    }

    if(is_dvddrive && is_cdromdrive)  drivetype = CDVD_DRIVETYPE_CDVD;
    if(is_dvddrive && !is_cdromdrive) drivetype = CDVD_DRIVETYPE_DVD;
    if(!is_dvddrive && is_cdromdrive) drivetype = CDVD_DRIVETYPE_CD;

    return drivetype;
}

// as the naem says
int CCdvd::GetCdvdMediaType()
{
    int mediatype = CDVD_MEDIATYPE_UNKNOWN;
    UI08 buffer[1024];
    UI08 media;

    if(GetCdvdConfiguration(CDVD_FEATURE_READCD, buffer, sizeof(buffer)) 
        == CDVD_ERROR_SUCCESS)
    {
        media = buffer[10] & 0x01; // current loaded media is a cdrom if "Current" bit is on
        if(media)
        {
            return CDVD_MEDIATYPE_CD;
        }
    }

    if(GetCdvdConfiguration(CDVD_FEATURE_READDVD, buffer, sizeof(buffer)) 
        == CDVD_ERROR_SUCCESS)
    {
        media = buffer[10] & 0x01; // current loaded media is a dvd if "Current" bit is on
        if(media)
        {
            return CDVD_MEDIATYPE_DVD;
        }
    }

    return mediatype;
}

// automatically detect read_mode as well as set it.
int CCdvd::DetectAndSetReadMode()
{
#define CHECKMODE(m) memset(m_ReadBuffer[m_nCurrentBuffer].AB_BufPointer, 0xCC, m_ReadBuffer[m_nCurrentBuffer].AB_BufLen); \
        SetReadMode((m), m_nMmcDataMode);\
        if(((this)->*(ReadSector))(0, NULL) == CDVD_ERROR_SUCCESS){\
        if(*((UI32*) m_ReadBuffer[m_nCurrentBuffer].AB_BufPointer) != 0xCCCCCCCC){\
            TRACE("data: %08Xh, detected read mode: %ld\n", *((UI32*) m_ReadBuffer[m_nCurrentBuffer].AB_BufPointer), (m));\
                return (m);\
            }\
        }

    int retval = CDVD_ERROR_FAIL;
    if(GetCdvdDriveType() != CDVD_DRIVETYPE_UNKNOWN)
    {
        int mediatype = GetCdvdMediaType();
        if(mediatype != CDVD_MEDIATYPE_UNKNOWN) // no freaking media present dipshit
        {
            if(mediatype == CDVD_MEDIATYPE_DVD)
            {
                m_nMmcDataMode = CDVD_MMC_DATAMODE_USER;
            }
            else if(mediatype == CDVD_MEDIATYPE_CD)
            {
                m_nMmcDataMode = CDVD_MMC_DATAMODE_RAW;
            }

            // start checking which one will work;

            SetCurrentBuffer(0); 

            // lets try scsi10 first, since scsi10/scsi12 is mandatory for dvd's
            if(((this)->*(SetSectorSize))((m_nMmcDataMode == CDVD_MMC_DATAMODE_RAW) ? 
                CDVD_SECTOR_SIZE_CD : CDVD_SECTOR_SIZE_DVD) == CDVD_ERROR_SUCCESS)
            {
                CHECKMODE(CDVD_READ_SCSI10);
            }
            
            // then check for mmc
            CHECKMODE(CDVD_READ_MMC);
            // mmc doesnt work? lets try the others
            
            TRACE("cdvdlib: failed to retrieve mode. \n");
            // disabled all other read_modes for now.
            //CHECKMODE(CDVD_READ_D8);
            //CHECKMODE(CDVD_READ_D410);
            //CHECKMODE(CDVD_READ_D412);
        }
    }

    return retval;
}

// update the global interface for some magic
void CCdvd::UpdateInterfaceObject()
{
#if defined(CDVD_SIMPLE_INTERFACE)

    FUNCTION_GETNUMSECTORS = GetNumSectors;
    FUNCTION_GETTOC        = GetToc;
    FUNCTION_TESTREADY     = TestReady;
    FUNCTION_SETSPEED      = SetSpeed;
    FUNCTION_STOP          = Stop;
    FUNCTION_SETSECTORSIZE = SetSectorSize;
    FUNCTION_PLAY          = Play;
    FUNCTION_READSECTOR    = ReadSector;

#endif

}

TOCDATA CCdvd::GetTocData()
{   
    TOCDATA dummy;

    if(m_nCurrentDrive == -1)
    {
        memset(&dummy, 0, sizeof(TOCDATA));
        return dummy;
    }

    return m_tocDetails;
}

DECODEDTRACKINFO CCdvd::GetTrackDetail()
{   
    DECODEDTRACKINFO dummy;

    if(m_nCurrentDrive == -1)
    {
        memset(&dummy, 0, sizeof(DECODEDTRACKINFO));
        return dummy;
    }

    return m_trkDetails;
}

UI08 *CCdvd::GetBufferAddress(int buff_num) const
{
    _ASSERT(buff_num < CDVD_NUM_BUFFERS);

    return m_ReadBuffer[buff_num].AB_BufPointer;
}

ADAPTERINFO CCdvd::GetAdapterDetail(int drv_num)
{
    _ASSERT(drv_num < CDVD_MAX_SUPPORTED_DRIVES);
    ADAPTERINFO dummy;
  
    if(m_nDrives == 0) 
    {
        memset(&dummy, 0, sizeof(ADAPTERINFO));
        return dummy;
    }

    return m_drvDetails[drv_num];
}


/***********************************************************/
/*              DUMMY C/DVD Methods                        */
/***********************************************************/
int CCdvd::Dummy_T1()
{ 
    TRACE("cdvdlib: dummy_t1\n");
    return CDVD_ERROR_UNINITIALIZED; 
}

int CCdvd::Dummy_T2(int a)
{ 
    TRACE("cdvdlib: dummy_t2\n");
    return CDVD_ERROR_UNINITIALIZED; 
}

int CCdvd::Dummy_T3(int a, int b)
{ 
    TRACE("cdvdlib: dummy_t3\n");
    return CDVD_ERROR_UNINITIALIZED; 
}

int CCdvd::Dummy_T4(int a, HANDLE *b)
{ 
    TRACE("cdvdlib: dummy_t4\n");
    return CDVD_ERROR_UNINITIALIZED; 
}

