/*************************************************************************** cdr.c - description ------------------- begin : Sun Nov 16 2003 copyright : (C) 2003 by Pete Bernert email : BlackDove@addcom.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. See also the license.txt file for * * additional informations. * * * ***************************************************************************/ //*************************************************************************// // History of changes: // // 2004/12/25 - Pete // - added an hack in CDVDgetTD for big dvds // // 2003/11/16 - Pete // - generic cleanup for the Peops cdvd release // //*************************************************************************// ///////////////////////////////////////////////////////// #include "stdafx.h" #include #include "resource.h" #define _IN_CDR #include "externals.h" #define CDVDdefs #include "PS2Etypes.h" #include "PS2Edefs.h" #include "libiso.h" #ifdef DBGOUT #define SMALLDEBUG 1 #include #endif ///////////////////////////////////////////////////////// // PCSX2 CDVD interface: EXPORT_GCC char * CALLBACK PS2EgetLibName(); EXPORT_GCC unsigned long CALLBACK PS2EgetLibType(); EXPORT_GCC unsigned long CALLBACK PS2EgetLibVersion2(unsigned long type); EXPORT_GCC long CALLBACK CDVDinit(); EXPORT_GCC void CALLBACK CDVDshutdown(); EXPORT_GCC long CALLBACK CDVDopen(const char* pTitle); EXPORT_GCC void CALLBACK CDVDclose(); EXPORT_GCC long CALLBACK CDVDtest(); EXPORT_GCC long CALLBACK CDVDreadTrack(unsigned long lsn, int mode); EXPORT_GCC unsigned char * CALLBACK CDVDgetBuffer(); EXPORT_GCC long CALLBACK CDVDgetTN(cdvdTN *Buffer); EXPORT_GCC long CALLBACK CDVDgetTD(unsigned char track, cdvdTD *Buffer); EXPORT_GCC long CALLBACK CDVDgetDiskType(); EXPORT_GCC long CALLBACK CDVDgetTrayStatus(); ///////////////////////////////////////////////////////// const unsigned char version = PS2E_CDVD_VERSION; const unsigned char revision = 1; const unsigned char build = 3; #ifdef _DEBUG char *libraryName = "P.E.Op.S. CDVD (Debug, CDDA mod)"; #else char *libraryName = "P.E.Op.S. CDVD (CDDA mod)"; #endif ///////////////////////////////////////////////////////// BOOL bIsOpen=FALSE; // flag: open called once BOOL bCDDAPlay=FALSE; // flag: audio is playing int iCDROK=0; // !=0: cd is ok int iCDType=CDVD_TYPE_UNKNOWN; // CD/DVD int iCheckTrayStatus=0; // if 0 : report tray as closed, else try a real check void *fdump; ///////////////////////////////////////////////////////// // usual info funcs EXPORT_GCC char * CALLBACK PS2EgetLibName() { return libraryName; } EXPORT_GCC unsigned long CALLBACK PS2EgetLibType() { return PS2E_LT_CDVD; } EXPORT_GCC unsigned long CALLBACK PS2EgetLibVersion2(unsigned long type) { return version<<16|revision<<8|build; } /* EXPORT_GCC unsigned long CALLBACK PS2EgetCpuPlatform(void) { return PS2E_X86; // return PS2E_X86_64; }*/ s32 msf_to_lba(u8 m, u8 s, u8 f) { u32 lsn; lsn = f; lsn+=(s - 2) * 75; lsn+= m * 75 * 60; return lsn; } void lba_to_msf(s32 lba, u8* m, u8* s, u8* f) { lba += 150; *m = (u8)(lba / (60*75)); *s = (u8)((lba / 75) % 60); *f = (u8)(lba % 75); } ///////////////////////////////////////////////////////// // init: called once at library load EXPORT_GCC long CALLBACK CDVDinit() { szSUBF[0]=0; // just init the filename buffers szPPF[0] =0; return 0; } ///////////////////////////////////////////////////////// // shutdown: called once at final exit EXPORT_GCC void CALLBACK CDVDshutdown() { } ///////////////////////////////////////////////////////// // open: called, when games starts/cd has been changed int CheckDiskType(int baseType); EXPORT_GCC long CALLBACK CDVDopen(const char* pTitle) { int i,audioTracks,dataTracks; cdvdTD T; if(bIsOpen) // double-open check (if the main emu coder doesn't know what he is doing ;) { if(iCDROK<=0) return -1; else return 0; } bIsOpen=TRUE; // ok, open func called once ReadConfig(); // read user config BuildPPFCache(); // build ppf cache BuildSUBCache(); // build sub cache CreateREADBufs(); // setup generic read buffers CreateGenEvent(); // create read event iCDROK=OpenGenCD(iCD_AD,iCD_TA,iCD_LU); // generic open, setup read func if(iCDROK<=0) {iCDROK=0;return -1;} ReadTOC(); // read the toc SetGenCDSpeed(0); // try to change the reading speed (if wanted) iCDType=CDVD_TYPE_UNKNOWN; // let's look after the disc type // (funny stuff taken from Xobro's/Florin's bin plugin) if(CDVDreadTrack(16,CDVD_MODE_2048)==0) { struct cdVolDesc *volDesc; volDesc=(struct cdVolDesc *)CDVDgetBuffer(); if(volDesc) { //todo: CDVD_TYPE_CDDA if(volDesc->rootToc.tocSize==2048) iCDType = CDVD_TYPE_DETCTCD; else iCDType = CDVD_TYPE_DETCTDVDS; } } fprintf(stderr," * CDVD Disk Open: %d tracks (%d to %d):\n",sTOC.cLastTrack-sTOC.cFirstTrack+1,sTOC.cFirstTrack,sTOC.cLastTrack); audioTracks=dataTracks=0; for(i=sTOC.cFirstTrack;i<=sTOC.cLastTrack;i++) { CDVDgetTD(i,&T); if(T.type==CDVD_AUDIO_TRACK) { audioTracks++; fprintf(stderr," * * Track %d: Audio (%d sectors)\n",i,T.lsn); } else { dataTracks++; fprintf(stderr," * * Track %d: Data (Mode %d) (%d sectors)\n",i,((T.type==CDVD_MODE1_TRACK)?1:2),T.lsn); } } if((dataTracks==0)&&(audioTracks>0)) iCDType=CDVD_TYPE_CDDA; else if(dataTracks>0) iCDType=CheckDiskType(iCDType); if((iCDType==CDVD_TYPE_ILLEGAL)&&(audioTracks>0)) iCDType=CDVD_TYPE_CDDA; else if((iCDType==CDVD_TYPE_PS2CD)&&(audioTracks>0)) iCDType=CDVD_TYPE_PS2CDDA; else if((iCDType==CDVD_TYPE_PSCD)&&(audioTracks>0)) iCDType=CDVD_TYPE_PSCDDA; switch(iCDType) { case CDVD_TYPE_ILLEGAL: // Illegal Disc fprintf(stderr," * Disk Type: Illegal Disk.\n");break; case CDVD_TYPE_DVDV: // DVD Video fprintf(stderr," * Disk Type: DVD Video.\n");break; case CDVD_TYPE_CDDA: // Audio CD fprintf(stderr," * Disk Type: CDDA.\n");break; case CDVD_TYPE_PS2DVD: // PS2 DVD fprintf(stderr," * Disk Type: PS2 DVD.\n");break; case CDVD_TYPE_PS2CDDA: // PS2 CD (with audio) fprintf(stderr," * Disk Type: PS2 CD+Audio.\n");break; case CDVD_TYPE_PS2CD: // PS2 CD fprintf(stderr," * Disk Type: PS2 CD.\n");break; case CDVD_TYPE_PSCDDA: // PS CD (with audio) fprintf(stderr," * Disk Type: PS1 CD+Audio.\n");break; case CDVD_TYPE_PSCD: // PS CD fprintf(stderr," * Disk Type: PS1 CD.\n");break; case CDVD_TYPE_UNKNOWN: // Unknown fprintf(stderr," * Disk Type: Unknown.\n");break; case CDVD_TYPE_NODISC: // No Disc fprintf(stderr," * Disk Type: No Disc.\n");break; } /* if (iBlockDump)*/ { // fdump = isoCreate("block.dump", ISOFLAGS_BLOCKDUMP); fdump = NULL; if (fdump) { cdvdTD buf; CDVDgetTD(0, &buf); isoSetFormat(fdump, 0, 2352, buf.lsn); } } /*else { fdump = NULL; }*/ return 0; // ok, done } ///////////////////////////////////////////////////////// // close: called when emulation stops EXPORT_GCC void CALLBACK CDVDclose() { if(!bIsOpen) return; // no open? no close... if (fdump != NULL) { isoClose(fdump); } bIsOpen=FALSE; // no more open LockGenCDAccess(); // make sure that no more reading is happening if(iCDROK) // cd was ok? { if(bCDDAPlay) {DoCDDAPlay(0);bCDDAPlay=FALSE;} // -> cdda playing? stop it SetGenCDSpeed(1); // -> repair speed CloseGenCD(); // -> cd not used anymore } UnlockGenCDAccess(); FreeREADBufs(); // free read bufs FreeGenEvent(); // free event FreePPFCache(); // free ppf cache FreeSUBCache(); // free sub cache } ///////////////////////////////////////////////////////// // test: ah, well, always fine EXPORT_GCC long CALLBACK CDVDtest() { return 0; } ///////////////////////////////////////////////////////// // readSubQ: read subq from disc (only cds have subq data) EXPORT_GCC long CALLBACK CDVDreadSubQ(u32 lsn, cdvdSubQ* subq) { u8 min, sec, frm; if(!bIsOpen) CDVDopen("DVD"); // usual checks if(!iCDROK) return -1; // fake it subq->ctrl = 4; subq->mode = 1; subq->trackNum = itob(1); subq->trackIndex= itob(1); lba_to_msf(lsn, &min, &sec, &frm); subq->trackM = itob(min); subq->trackS = itob(sec); subq->trackF = itob(frm); subq->pad = 0; lba_to_msf(lsn + (2*75), &min, &sec, &frm); subq->discM = itob(min); subq->discS = itob(sec); subq->discF = itob(frm); return 0; } ///////////////////////////////////////////////////////// // gettoc: ps2 style TOC static int layer1start = -1; EXPORT_GCC long CALLBACK CDVDgetTOC(void* toc) { u32 type; u8* tocBuff = (u8*)toc; if(!bIsOpen) CDVDopen("DVD"); // not open? funny emu... if(!iCDROK) return -1; // cd not ok? type = CDVDgetDiskType(); if( type == CDVD_TYPE_DVDV || type == CDVD_TYPE_PS2DVD) { u32 lastaddr; // get dvd structure format // scsi command 0x43 memset(tocBuff, 0, 2048); lastaddr = GetLastTrack1Addr(); if(layer1start > 0 || (layer1start != -2 && lastaddr > 0x280000) ) { int off = 0; FRAMEBUF* f = (FRAMEBUF*)malloc(sizeof(FRAMEBUF)); f->dwBufLen = iUsedBlockSize; f->dwFrameCnt = 1; // dual sided tocBuff[ 0] = 0x24; tocBuff[ 1] = 0x02; tocBuff[ 2] = 0xF2; tocBuff[ 3] = 0x00; tocBuff[ 4] = 0x41; tocBuff[ 5] = 0x95; tocBuff[14] = 0x60; // dual sided, ptp tocBuff[16] = 0x00; tocBuff[17] = 0x03; tocBuff[18] = 0x00; tocBuff[19] = 0x00; if( layer1start == -1 ) { // search for it printf("PeopsCDVD: searching for layer1... "); for(layer1start = (lastaddr/2-0x10)&~0xf; layer1start < 0x200010; layer1start += 16) { f->dwFrame = layer1start; if( pReadFunc(TRUE,f) != SS_COMP ) { layer1start = 0x200010; break; } // CD001 if( f->BufData[off+1] == 0x43 && f->BufData[off+2] == 0x44 && f->BufData[off+3] == 0x30 && f->BufData[off+4] == 0x30 && f->BufData[off+5] == 0x31 ) { break; } } if( layer1start >= 0x200010 ) { printf("Couldn't find second layer on dual layer... ignoring\n"); // fake it tocBuff[ 0] = 0x04; tocBuff[ 1] = 0x02; tocBuff[ 2] = 0xF2; tocBuff[ 3] = 0x00; tocBuff[ 4] = 0x86; tocBuff[ 5] = 0x72; tocBuff[16] = 0x00; tocBuff[17] = 0x03; tocBuff[18] = 0x00; tocBuff[19] = 0x00; layer1start = -2; return 0; } printf("found at 0x%8.8x\n", layer1start); layer1start = layer1start+0x30000-1; } tocBuff[20] = layer1start>>24; tocBuff[21] = (layer1start>>16)&0xff; tocBuff[22] = (layer1start>>8)&0xff; tocBuff[23] = (layer1start>>0)&0xff; free(f); } else { // fake it tocBuff[ 0] = 0x04; tocBuff[ 1] = 0x02; tocBuff[ 2] = 0xF2; tocBuff[ 3] = 0x00; tocBuff[ 4] = 0x86; tocBuff[ 5] = 0x72; tocBuff[16] = 0x00; tocBuff[17] = 0x03; tocBuff[18] = 0x00; tocBuff[19] = 0x00; } } else if(type == CDVD_TYPE_CDDA || type == CDVD_TYPE_PS2CDDA || type == CDVD_TYPE_PS2CD || type == CDVD_TYPE_PSCDDA || type == CDVD_TYPE_PSCD) { // cd toc // (could be replaced by 1 command that reads the full toc) u8 min, sec, frm,i; s32 err; cdvdTN diskInfo; cdvdTD trackInfo; memset(tocBuff, 0, 1024); if (CDVDgetTN(&diskInfo) == -1) { diskInfo.etrack = 0;diskInfo.strack = 1; } if (CDVDgetTD(0, &trackInfo) == -1) trackInfo.lsn = 0; tocBuff[0] = 0x41; tocBuff[1] = 0x00; //Number of FirstTrack tocBuff[2] = 0xA0; tocBuff[7] = itob(diskInfo.strack); //Number of LastTrack tocBuff[12] = 0xA1; tocBuff[17] = itob(diskInfo.etrack); //DiskLength lba_to_msf(trackInfo.lsn, &min, &sec, &frm); tocBuff[22] = 0xA2; tocBuff[27] = itob(min); tocBuff[28] = itob(sec); tocBuff[29] = itob(frm); fprintf(stderr,"Track 0: %d mins %d secs %d frames\n",min,sec,frm); for (i=diskInfo.strack; i<=diskInfo.etrack; i++) { err = CDVDgetTD(i, &trackInfo); lba_to_msf(trackInfo.lsn, &min, &sec, &frm); tocBuff[i*10+30] = trackInfo.type; tocBuff[i*10+32] = err == -1 ? 0 : itob(i); //number tocBuff[i*10+37] = itob(min); tocBuff[i*10+38] = itob(sec); tocBuff[i*10+39] = itob(frm); fprintf(stderr,"Track %d: %d mins %d secs %d frames\n",i,min,sec,frm); } } else return -1; return 0; } ///////////////////////////////////////////////////////// // gettn: first/last track num EXPORT_GCC long CALLBACK CDVDgetTN(cdvdTN *Buffer) { if(!bIsOpen) CDVDopen("DVD"); // not open? funny emu... if(!iCDROK) // cd not ok? { Buffer->strack=1; Buffer->etrack=1; return -1; } ReadTOC(); // read the TOC Buffer->strack=sTOC.cFirstTrack; // get the infos Buffer->etrack=sTOC.cLastTrack; return 0; } ///////////////////////////////////////////////////////// // gettd: track addr EXPORT_GCC long CALLBACK CDVDgetTD(unsigned char track, cdvdTD *Buffer) { unsigned long lu,i; unsigned char buffer[2352]; unsigned char *buf; u8 t1; if(!bIsOpen) CDVDopen("DVD"); // not open? funny emu... if(!iCDROK) return -1; // cd not ok? bye ReadTOC(); // read toc /* // PSEmu style: if(track==0) // 0 = last track { lu=reOrder(sTOC.tracks[sTOC.cLastTrack].lAddr); addr2time(lu,buffer); } else // others: track n { lu=reOrder(sTOC.tracks[track-1].lAddr); addr2time(lu,buffer); } Buffer->minute = buffer[1]; Buffer->second = buffer[2]; Buffer->frame = buffer[3]; Buffer->type = iCDType; #ifdef DBGOUT auxprintf("Read Toc %d: %u\n",track,lu); #endif */ lu=0; if(track==0) lu=reOrder(sTOC.tracks[sTOC.cLastTrack].lAddr); else lu=reOrder(sTOC.tracks[track].lAddr); //addr2time(lu,buffer); Buffer->lsn=lu; if(track==0) Buffer->type = iCDType; else { lu=0; for(i=sTOC.cFirstTrack;itype=t1; } return 0; } ///////////////////////////////////////////////////////// // readtrack: start reading at given address EXPORT_GCC long CALLBACK CDVDreadTrack(unsigned long lsn, int mode) { if(!bIsOpen) CDVDopen("DVD"); // usual checks if(!iCDROK) return -1; if(bCDDAPlay) bCDDAPlay=FALSE; #ifdef DBGOUT auxprintf("Read Track %u: %d\n",lsn,mode); #endif lLastAccessedAddr=lsn; // store read track values (for getbuffer) iLastAccessedMode=mode; if(!pReadTrackFunc(lLastAccessedAddr)) // start reading return -1; return 0; } ///////////////////////////////////////////////////////// // getbuffer: will be called after readtrack, to get ptr // to data // small helper buffer to get bigger block sizes unsigned char cDataAndSub[2368]; EXPORT_GCC unsigned char * CALLBACK CDVDgetBuffer() { unsigned char * pbuffer; if(!bIsOpen) CDVDopen("DVD"); if(pGetPtrFunc) pGetPtrFunc(); // get ptr on thread modes pbuffer=pCurrReadBuf; // init buffer pointer if (fdump != NULL) { isoWriteBlock(fdump, pbuffer, lLastAccessedAddr); } if(iLastAccessedMode!=iUsedMode) { switch(iLastAccessedMode) // what does the emu want? {//------------------------------------------------// case CDVD_MODE_2048: { if(iUsedBlockSize==2352) pbuffer+=24; }break; //------------------------------------------------// case CDVD_MODE_2352: { if(iUsedBlockSize==2048) { memset(cDataAndSub,0,2368); memcpy(cDataAndSub+24,pbuffer,2048); pbuffer=cDataAndSub; } }break; //------------------------------------------------// case CDVD_MODE_2340: { if(iUsedBlockSize==2048) { memset(cDataAndSub,0,2368); memcpy(cDataAndSub+12,pbuffer,2048); pbuffer=cDataAndSub; } else pbuffer+=12; }break; //------------------------------------------------// case CDVD_MODE_2328: { if(iUsedBlockSize==2048) { memset(cDataAndSub,0,2368); memcpy(cDataAndSub+0,pbuffer,2048); pbuffer=cDataAndSub; } else pbuffer+=24; }break; //------------------------------------------------// case CDVD_MODE_2368: { if(iUsedBlockSize==2048) { memset(cDataAndSub,0,2368); memcpy(cDataAndSub+24,pbuffer,2048); pbuffer=cDataAndSub; /* // NO SUBCHANNEL SUPPORT RIGHT NOW!!! { if(subHead) // some sub file? CheckSUBCache(lLastAccessedAddr); // -> get cached subs else if(iUseSubReading!=1 && pCurrSubBuf) // no direct cd sub read? FakeSubData(lLastAccessedAddr); // -> fake the data memcpy(cDataAndSub,pCurrReadBuf,2352); if(pCurrSubBuf) memcpy(cDataAndSub+2352,pCurrSubBuf+12,16); pbuffer=cDataAndSub; } */ } }break; //------------------------------------------------// } } #ifdef DBGOUT auxprintf("get buf %d\n",iLastAccessedMode); /* { int k; for(k=0;k<2352;k++) auxprintf("%02x ",*(pbuffer+k)); auxprintf("\n\n"); } */ #endif return pbuffer; } ///////////////////////////////////////////////////////// EXPORT_GCC long CALLBACK CDVDgetDiskType() { return iCDType; } ///////////////////////////////////////////////////////// // CDVDgetTrayStatus EXPORT_GCC long CALLBACK CDVDgetTrayStatus() { static time_t to=0; static long lLastTrayState=CDVD_TRAY_CLOSE; if(to==time(NULL)) return lLastTrayState; // we only check once per second to = time(NULL); lLastTrayState=CDVD_TRAY_CLOSE; // init state with "closed" if(iCheckTrayStatus) // user really want a tray check { int iStatus; LockGenCDAccess(); // make sure that no more reading is happening iStatus=GetSCSIStatus(iCD_AD,iCD_TA,iCD_LU); // get device status UnlockGenCDAccess(); if(iStatus==SS_ERR) lLastTrayState=CDVD_TRAY_OPEN; } #ifdef DBGOUT auxprintf("check %d -> %d\n",to,lLastTrayState); #endif return lLastTrayState; } EXPORT_GCC s32 CALLBACK CDVDctrlTrayOpen() { return 0; } EXPORT_GCC s32 CALLBACK CDVDctrlTrayClose() { return 0; } ///////////////////////////////////////////////////////// // configure: shows config window EXPORT_GCC void CALLBACK CDVDconfigure() { if(iCDROK) // mmm... someone has already called Open? bad {MessageBeep((UINT)-1);return;} CreateGenEvent(); // we need an event handle DialogBox(hInst,MAKEINTRESOURCE(IDD_CONFIG), // call dialog GetActiveWindow(),(DLGPROC)CDRDlgProc); FreeGenEvent(); // free event handle } ///////////////////////////////////////////////////////// // about: shows about window EXPORT_GCC void CALLBACK CDVDabout() { DialogBox(hInst,MAKEINTRESOURCE(IDD_ABOUT), GetActiveWindow(),(DLGPROC)AboutDlgProc); } ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// /* // CURRENTLY UNUSED OLD STUFF FROM PSX CD PLUGIN: ///////////////////////////////////////////////////////// // audioplay: PLAYSECTOR is NOT BCD coded !!! EXPORT_GCC long CALLBACK CDRplay(unsigned char * sector) { if(!bIsOpen) CDVDopen(); if(!iCDROK) return PSE_ERR_FATAL; if(!DoCDDAPlay(time2addr(sector))) // start playing return PSE_CDR_ERR_NOREAD; bCDDAPlay=TRUE; // raise flag: we are playing return PSE_CDR_ERR_SUCCESS; } ///////////////////////////////////////////////////////// // audiostop: stops cdda playing EXPORT_GCC long CALLBACK CDRstop(void) { if(!bCDDAPlay) return PSE_ERR_FATAL; DoCDDAPlay(0); // stop cdda bCDDAPlay=FALSE; // reset flag: no more playing return PSE_CDR_ERR_SUCCESS; } ///////////////////////////////////////////////////////// // getdriveletter EXPORT_GCC char CALLBACK CDRgetDriveLetter(void) { if(!iCDROK) return 0; // not open? no way to get the letter if(iInterfaceMode==2 || iInterfaceMode==3) // w2k/xp: easy { return MapIOCTLDriveLetter(iCD_AD,iCD_TA,iCD_LU); } else // but with aspi??? { // -> no idea yet (maybe registry read...pfff) } return 0; } ///////////////////////////////////////////////////////// // getstatus: pcsx func... poorly supported here // problem is: func will be called often, which // would block all of my cdr reading if I would use // lotsa scsi commands struct CdrStat { unsigned long Type; unsigned long Status; unsigned char Time[3]; // current playing time }; struct CdrStat ostat; // reads cdr status // type: // 0x00 - unknown // 0x01 - data // 0x02 - audio // 0xff - no cdrom // status: // 0x00 - unknown // 0x02 - error // 0x08 - seek error // 0x10 - shell open // 0x20 - reading // 0x40 - seeking // 0x80 - playing // time: // byte 0 - minute // byte 1 - second // byte 2 - frame EXPORT_GCC long CALLBACK CDRgetStatus(struct CdrStat *stat) { int iStatus; static time_t to; if(!bCDDAPlay) // if not playing update stat only once in a second { if(to get pos stat->Type = 0x02; // -> audio if(pB) { stat->Status|=0x80; // --> playing flag stat->Time[0]=pB[18]; // --> and curr play time stat->Time[1]=pB[19]; stat->Time[2]=pB[20]; } } else // cdda not playing? { stat->Type = 0x01; // -> data } LockGenCDAccess(); // make sure that no more reading is happening iStatus=GetSCSIStatus(iCD_AD,iCD_TA,iCD_LU); // get device status UnlockGenCDAccess(); if(iStatus==SS_ERR) { // no cdrom? stat->Type = 0xff; stat->Status|= 0x10; } memcpy(&ostat, stat, sizeof(struct CdrStat)); return 0; } ///////////////////////////////////////////////////////// */