DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/dos_mscdex.cpp
00001 /*
00002  *  Copyright (C) 2002-2020  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 
00020 #include <string.h>
00021 #include <ctype.h>
00022 #include "regs.h"
00023 #include "callback.h"
00024 #include "dos_system.h"
00025 #include "dos_inc.h"
00026 #include "setup.h"
00027 #include "support.h"
00028 #include "bios_disk.h"
00029 #include "cpu.h"
00030 
00031 #include "cdrom.h"
00032 
00033 #define MSCDEX_LOG LOG(LOG_MISC, LOG_DEBUG)
00034 #define MSCDEX_LOG_ERROR LOG(LOG_MISC, LOG_ERROR)
00035 
00036 #define MSCDEX_VERSION_HIGH     2
00037 #define MSCDEX_VERSION_LOW      23
00038 #define MSCDEX_MAX_DRIVES       16
00039 
00040 // Error Codes
00041 #define MSCDEX_ERROR_INVALID_FUNCTION   1
00042 #define MSCDEX_ERROR_BAD_FORMAT                 11
00043 #define MSCDEX_ERROR_UNKNOWN_DRIVE              15
00044 #define MSCDEX_ERROR_DRIVE_NOT_READY    21
00045 
00046 // Request Status
00047 #define REQUEST_STATUS_DONE             0x0100
00048 #define REQUEST_STATUS_ERROR    0x8000
00049 
00050 // Use cdrom Interface
00051 int useCdromInterface   = CDROM_USE_SDL;
00052 int forceCD                             = -1;
00053 
00054 static Bitu MSCDEX_Strategy_Handler(void); 
00055 static Bitu MSCDEX_Interrupt_Handler(void);
00056 
00057 class DOS_DeviceHeader:public MemStruct {
00058 public:
00059         DOS_DeviceHeader(PhysPt ptr)                            { pt = ptr; };
00060         
00061         void    SetNextDeviceHeader     (RealPt ptr)    { sSave(sDeviceHeader,nextDeviceHeader,ptr);    };
00062         RealPt  GetNextDeviceHeader     (void)                  { return sGet(sDeviceHeader,nextDeviceHeader);  };
00063         void    SetAttribute            (Bit16u atr)    { sSave(sDeviceHeader,devAttributes,atr);               };
00064         void    SetDriveLetter          (Bit8u letter)  { sSave(sDeviceHeader,driveLetter,letter);              };
00065         void    SetNumSubUnits          (Bit8u num)             { sSave(sDeviceHeader,numSubUnits,num);                 };
00066         Bit8u   GetNumSubUnits          (void)                  { return sGet(sDeviceHeader,numSubUnits);               };
00067         void    SetName                         (char const* _name)     { MEM_BlockWrite(pt+offsetof(sDeviceHeader,name),_name,8); };
00068         void    SetInterrupt            (Bit16u ofs)    { sSave(sDeviceHeader,interrupt,ofs);                   };
00069         void    SetStrategy                     (Bit16u ofs)    { sSave(sDeviceHeader,strategy,ofs);                    };
00070 
00071         #ifdef _MSC_VER
00072         #pragma pack(1)
00073         #endif
00074         struct sDeviceHeader{
00075                 RealPt  nextDeviceHeader;
00076                 Bit16u  devAttributes;
00077                 Bit16u  strategy;
00078                 Bit16u  interrupt;
00079                 Bit8u   name[8];
00080                 Bit16u  wReserved;
00081                 Bit8u   driveLetter;
00082                 Bit8u   numSubUnits;
00083     } GCC_ATTRIBUTE(packed) TDeviceHeader = {};
00084         #ifdef _MSC_VER
00085         #pragma pack()
00086         #endif
00087 };
00088 
00089 class CMscdex {
00090 public:
00091         CMscdex         (void);
00092         ~CMscdex        (void);
00093 
00094         Bit16u          GetVersion                      (void)  { return (MSCDEX_VERSION_HIGH<<8)+MSCDEX_VERSION_LOW; };
00095         Bit16u          GetNumDrives            (void)  { return numDrives;                     };
00096         Bit16u          GetFirstDrive           (void)  { return dinfo[0].drive; };
00097         Bit8u           GetSubUnit                      (Bit16u _drive);
00098         bool            GetUPC                          (Bit8u subUnit, Bit8u& attr, char* upc);
00099 
00100         void            InitNewMedia            (Bit8u subUnit);
00101         bool            PlayAudioSector         (Bit8u subUnit, Bit32u sector, Bit32u length);
00102         bool            PlayAudioMSF            (Bit8u subUnit, Bit32u start, Bit32u length);
00103         bool            StopAudio                       (Bit8u subUnit);
00104         bool            GetAudioStatus          (Bit8u subUnit, bool& playing, bool& pause, TMSF& start, TMSF& end);
00105 
00106         bool            GetSubChannelData       (Bit8u subUnit, Bit8u& attr, Bit8u& track, Bit8u &index, TMSF& rel, TMSF& abs);
00107 
00108         int                     RemoveDrive                     (Bit16u _drive);
00109         int                     AddDrive                        (Bit16u _drive, char* physicalPath, Bit8u& subUnit);
00110         bool            HasDrive                        (Bit16u drive);
00111         void            ReplaceDrive            (CDROM_Interface* newCdrom, Bit8u subUnit);
00112         void            GetDrives                       (PhysPt data);
00113         void            GetDriverInfo           (PhysPt data);
00114         bool            GetVolumeName           (Bit8u subUnit, char* data);
00115         bool            GetFileName                     (Bit16u drive, Bit16u pos, PhysPt data);
00116         bool            GetDirectoryEntry       (Bit16u drive, bool copyFlag, PhysPt pathname, PhysPt buffer, Bit16u& error);
00117         bool            ReadVTOC                        (Bit16u drive, Bit16u volume, PhysPt data, Bit16u& offset, Bit16u& error);
00118         bool            ReadSectors                     (Bit16u drive, Bit32u sector, Bit16u num, PhysPt data);
00119         bool            ReadSectors                     (Bit8u subUnit, bool raw, Bit32u sector, Bit16u num, PhysPt data);
00120         bool            ReadSectorsMSF          (Bit8u subUnit, bool raw, Bit32u start, Bit16u num, PhysPt data);
00121         bool            SendDriverRequest       (Bit16u drive, PhysPt data);
00122         bool            IsValidDrive            (Bit16u _drive);
00123         bool            GetCDInfo                       (Bit8u subUnit, Bit8u& tr1, Bit8u& tr2, TMSF& leadOut);
00124         Bit32u          GetVolumeSize           (Bit8u subUnit);
00125         bool            GetTrackInfo            (Bit8u subUnit, Bit8u track, Bit8u& attr, TMSF& start);
00126         Bit16u          GetStatusWord           (Bit8u subUnit,Bit16u status);
00127         bool            GetCurrentPos           (Bit8u subUnit, TMSF& pos);
00128         Bit32u          GetDeviceStatus         (Bit8u subUnit);
00129         bool            GetMediaStatus          (Bit8u subUnit, Bit8u& status);
00130         bool            LoadUnloadMedia         (Bit8u subUnit, bool unload);
00131         bool            ResumeAudio                     (Bit8u subUnit);
00132         bool            GetMediaStatus          (Bit8u subUnit, bool& media, bool& changed, bool& trayOpen);
00133 
00134         PhysPt          GetDefaultBuffer        (void);
00135         PhysPt          GetTempBuffer           (void);
00136 
00137         void SaveState( std::ostream& stream );
00138         void LoadState( std::istream& stream );
00139 
00140         Bit16u          numDrives = 0;
00141 
00142         typedef struct SDriveInfo {
00143                 Bit8u   drive;                  // drive letter in dosbox
00144                 Bit8u   physDrive;              // drive letter in system
00145                 bool    audioPlay;              // audio playing active
00146                 bool    audioPaused;    // audio playing paused
00147                 Bit32u  audioStart;             // StartLoc for resume
00148                 Bit32u  audioEnd;               // EndLoc for resume
00149                 bool    locked;                 // drive locked ?
00150                 bool    lastResult;             // last operation success ?
00151                 Bit32u  volumeSize;             // for media change
00152                 TCtrl   audioCtrl;              // audio channel control
00153         } TDriveInfo;
00154 
00155         Bit16u                          defaultBufSeg = 0;
00156         TDriveInfo                      dinfo[MSCDEX_MAX_DRIVES];
00157         CDROM_Interface*                cdrom[MSCDEX_MAX_DRIVES];
00158         Bit16u          rootDriverHeaderSeg = 0;
00159 
00160         bool            ChannelControl          (Bit8u subUnit, TCtrl ctrl);
00161         bool            GetChannelControl       (Bit8u subUnit, TCtrl& ctrl);
00162 };
00163 
00164 CMscdex::CMscdex(void) {
00165         memset(dinfo,0,sizeof(dinfo));
00166         for (Bit32u i=0; i<MSCDEX_MAX_DRIVES; i++) cdrom[i] = 0;
00167 }
00168 
00169 CMscdex::~CMscdex(void) {
00170         defaultBufSeg = 0;
00171         for (Bit16u i=0; i<GetNumDrives(); i++) {
00172                 delete cdrom[i];
00173                 cdrom[i] = 0;
00174         }
00175 }
00176 
00177 void CMscdex::GetDrives(PhysPt data)
00178 {
00179         for (Bit16u i=0; i<GetNumDrives(); i++) mem_writeb(data+i,dinfo[i].drive);
00180 }
00181 
00182 bool CMscdex::IsValidDrive(Bit16u _drive)
00183 {
00184         _drive &= 0xff; //Only lowerpart (Ultimate domain)
00185         for (Bit16u i=0; i<GetNumDrives(); i++) if (dinfo[i].drive==_drive) return true;
00186         return false;
00187 }
00188 
00189 Bit8u CMscdex::GetSubUnit(Bit16u _drive)
00190 {
00191         _drive &= 0xff; //Only lowerpart (Ultimate domain)
00192         for (Bit16u i=0; i<GetNumDrives(); i++) if (dinfo[i].drive==_drive) return (Bit8u)i;
00193         return 0xff;
00194 }
00195 
00196 int CMscdex::RemoveDrive(Bit16u _drive)
00197 {
00198         Bit16u idx = MSCDEX_MAX_DRIVES;
00199         for (Bit16u i=0; i<GetNumDrives(); i++) {
00200                 if (dinfo[i].drive == _drive) {
00201                         idx = i;
00202                         break;
00203                 }
00204         }
00205 
00206         if (idx == MSCDEX_MAX_DRIVES || (idx!=0 && idx!=GetNumDrives()-1)) return 0;
00207         delete cdrom[idx];
00208         if (idx==0) {
00209                 for (Bit16u i=0; i<GetNumDrives(); i++) {
00210                         if (i == MSCDEX_MAX_DRIVES-1) {
00211                                 cdrom[i] = 0;
00212                                 memset(&dinfo[i],0,sizeof(TDriveInfo));
00213                         } else {
00214                                 dinfo[i] = dinfo[i+1];
00215                                 cdrom[i] = cdrom[i+1];
00216                         }
00217                 }
00218         } else {
00219                 cdrom[idx] = 0;
00220                 memset(&dinfo[idx],0,sizeof(TDriveInfo));
00221         }
00222         numDrives--;
00223 
00224         if (GetNumDrives() == 0) {
00225                 DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0));
00226                 Bit16u off = sizeof(DOS_DeviceHeader::sDeviceHeader);
00227                 devHeader.SetStrategy(off+4);           // point to the RETF (To deactivate MSCDEX)
00228                 devHeader.SetInterrupt(off+4);          // point to the RETF (To deactivate MSCDEX)
00229                 devHeader.SetDriveLetter(0);
00230         } else if (idx==0) {
00231                 DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0));
00232                 devHeader.SetDriveLetter(GetFirstDrive()+1);
00233         }
00234         return 1;
00235 }
00236 
00237 int CMscdex::AddDrive(Bit16u _drive, char* physicalPath, Bit8u& subUnit)
00238 {
00239         subUnit = 0;
00240         if ((Bitu)GetNumDrives()+1>=MSCDEX_MAX_DRIVES) return 4;
00241         if (GetNumDrives()) {
00242                 // Error check, driveletter have to be in a row
00243                 if (dinfo[0].drive-1!=_drive && dinfo[numDrives-1].drive+1!=_drive) 
00244                         return 1;
00245         }
00246         // Set return type to ok
00247         int result = 0;
00248         // Get Mounttype and init needed cdrom interface
00249         switch (CDROM_GetMountType(physicalPath,forceCD)) {
00250         case 0x00: {    
00251                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting physical cdrom: %s"  ,physicalPath);
00252 #if defined (WIN32)
00253                 // Check OS
00254                 OSVERSIONINFO osi;
00255                 osi.dwOSVersionInfoSize = sizeof(osi);
00256                 GetVersionEx(&osi);
00257                 if ((osi.dwPlatformId==VER_PLATFORM_WIN32_NT) && (osi.dwMajorVersion>4)) {
00258                         // only WIN NT/200/XP
00259                         if (useCdromInterface==CDROM_USE_IOCTL_DIO) {
00260 //                              cdrom[numDrives] = new CDROM_Interface_Ioctl(CDROM_Interface_Ioctl::CDIOCTL_CDA_DIO);
00261 //                              LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface.");
00262 //                              break;
00263                         }
00264                         if (useCdromInterface==CDROM_USE_IOCTL_DX) {
00265 //                              cdrom[numDrives] = new CDROM_Interface_Ioctl(CDROM_Interface_Ioctl::CDIOCTL_CDA_DX);
00266 //                              LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface (digital audio extraction).");
00267 //                              break;
00268                         }
00269                         if (useCdromInterface==CDROM_USE_IOCTL_MCI) {
00270 //                              cdrom[numDrives] = new CDROM_Interface_Ioctl(CDROM_Interface_Ioctl::CDIOCTL_CDA_MCI);
00271 //                              LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface (media control interface).");
00272 //                              break;
00273                         }
00274                 }
00275                 if (useCdromInterface==CDROM_USE_ASPI) {
00276                         // all Wins - ASPI
00277 //                      cdrom[numDrives] = new CDROM_Interface_Aspi();
00278 //                      LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: ASPI Interface.");
00279 //                      break;
00280                 }
00281 #endif
00282 #if defined (LINUX) || defined(OS2)
00283                 // Always use IOCTL in Linux or OS/2
00284                 cdrom[numDrives] = new CDROM_Interface_Ioctl();
00285                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface.");
00286 #else
00287                 // Default case windows and other oses
00288                 cdrom[numDrives] = new CDROM_Interface_SDL();
00289                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: SDL Interface.");
00290 #endif
00291                 } break;
00292         case 0x01:      // iso cdrom interface  
00293                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting iso file as cdrom: %s", physicalPath);
00294                 cdrom[numDrives] = new CDROM_Interface_Image((Bit8u)numDrives);
00295                 break;
00296         case 0x02:      // fake cdrom interface (directories)
00297                 cdrom[numDrives] = new CDROM_Interface_Fake;
00298                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting directory as cdrom: %s",physicalPath);
00299                 LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: You wont have full MSCDEX support !");
00300                 result = 5;
00301                 break;
00302         default :       // weird result
00303                 return 6;
00304         }
00305 
00306         if (!cdrom[numDrives]->SetDevice(physicalPath,forceCD)) {
00307 //              delete cdrom[numDrives] ; mount seems to delete it
00308                 return 3;
00309         }
00310 
00311 
00312         if (rootDriverHeaderSeg==0) {
00313                 
00314                 Bit16u driverSize = sizeof(DOS_DeviceHeader::sDeviceHeader) + 10; // 10 = Bytes for 3 callbacks
00315                 
00316                 // Create Device Header
00317                 Bit16u seg = DOS_GetMemory(driverSize/16u+((driverSize%16u)>0u),"MSCDEX device header");
00318                 DOS_DeviceHeader devHeader(PhysMake(seg,0u));
00319                 devHeader.SetNextDeviceHeader   (0xFFFFFFFFul);
00320                 devHeader.SetAttribute(0xc800u);
00321                 devHeader.SetDriveLetter                (_drive+1u);
00322                 devHeader.SetNumSubUnits                (1u);
00323                 devHeader.SetName                               ("MSCD001 ");
00324 
00325                 //Link it in the device chain
00326                 Bit32u start = dos_infoblock.GetDeviceChain();
00327                 Bit16u segm  = (Bit16u)(start>>16ul);
00328                 Bit16u offm  = (Bit16u)(start&0xFFFFu);
00329                 while(start != 0xFFFFFFFFul) {
00330                         segm  = (Bit16u)(start>>16u);
00331                         offm  = (Bit16u)(start&0xFFFFu);
00332                         start = real_readd(segm,offm);
00333                 }
00334                 real_writed(segm,offm,(unsigned int)seg<<16u);
00335 
00336                 // Create Callback Strategy
00337                 Bit16u off = sizeof(DOS_DeviceHeader::sDeviceHeader);
00338                 Bit16u call_strategy=CALLBACK_Allocate();
00339                 CallBack_Handlers[call_strategy]=MSCDEX_Strategy_Handler;
00340                 real_writeb(seg,off+0,(Bit8u)0xFE);             //GRP 4
00341                 real_writeb(seg,off+1,(Bit8u)0x38);             //Extra Callback instruction
00342                 real_writew(seg,off+2,call_strategy);   //The immediate word
00343                 real_writeb(seg,off+4,(Bit8u)0xCB);             //A RETF Instruction
00344                 devHeader.SetStrategy(off);
00345                 
00346                 // Create Callback Interrupt
00347                 off += 5;
00348                 Bit16u call_interrupt=CALLBACK_Allocate();
00349                 CallBack_Handlers[call_interrupt]=MSCDEX_Interrupt_Handler;
00350                 real_writeb(seg,off+0,(Bit8u)0xFE);             //GRP 4
00351                 real_writeb(seg,off+1,(Bit8u)0x38);             //Extra Callback instruction
00352                 real_writew(seg,off+2,call_interrupt);  //The immediate word
00353                 real_writeb(seg,off+4,(Bit8u)0xCB);             //A RETF Instruction
00354                 devHeader.SetInterrupt(off);
00355                 
00356                 rootDriverHeaderSeg = seg;
00357         
00358         } else if (GetNumDrives() == 0) {
00359                 DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0));
00360                 Bit16u off = sizeof(DOS_DeviceHeader::sDeviceHeader);
00361                 devHeader.SetDriveLetter(_drive+1);
00362                 devHeader.SetStrategy(off);
00363                 devHeader.SetInterrupt(off+5);
00364         }
00365 
00366         // Set drive
00367         DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0));
00368         devHeader.SetNumSubUnits(devHeader.GetNumSubUnits()+1);
00369 
00370         if (dinfo[0].drive-1==_drive) {
00371                 CDROM_Interface *_cdrom = cdrom[numDrives];
00372                 CDROM_Interface_Image *_cdimg = CDROM_Interface_Image::images[numDrives];
00373                 for (Bit16u i=GetNumDrives(); i>0; i--) {
00374                         dinfo[i] = dinfo[i-1];
00375                         cdrom[i] = cdrom[i-1];
00376                         CDROM_Interface_Image::images[i] = CDROM_Interface_Image::images[i-1];
00377                 }
00378                 cdrom[0] = _cdrom;
00379                 CDROM_Interface_Image::images[0] = _cdimg;
00380                 dinfo[0].drive          = (Bit8u)_drive;
00381                 dinfo[0].physDrive      = (Bit8u)toupper(physicalPath[0]);
00382                 subUnit = 0;
00383         } else {
00384                 dinfo[numDrives].drive          = (Bit8u)_drive;
00385                 dinfo[numDrives].physDrive      = (Bit8u)toupper(physicalPath[0]);
00386                 subUnit = (Bit8u)numDrives;
00387         }
00388         numDrives++;
00389         // init channel control
00390         for (Bit8u chan=0;chan<4;chan++) {
00391                 dinfo[subUnit].audioCtrl.out[chan]=chan;
00392                 dinfo[subUnit].audioCtrl.vol[chan]=0xff;
00393         }
00394         // stop audio
00395         StopAudio(subUnit);
00396         return result;
00397 }
00398 
00399 bool CMscdex::HasDrive(Bit16u drive) {
00400         return (GetSubUnit(drive) != 0xff);
00401 }
00402 
00403 void CMscdex::ReplaceDrive(CDROM_Interface* newCdrom, Bit8u subUnit) {
00404         if (cdrom[subUnit] != NULL) {
00405                 StopAudio(subUnit);
00406                 delete cdrom[subUnit];
00407         }
00408         cdrom[subUnit] = newCdrom;
00409 }
00410 
00411 PhysPt CMscdex::GetDefaultBuffer(void) {
00412         if (defaultBufSeg==0) {
00413                 Bit16u size = (2352*2+15)/16;
00414                 defaultBufSeg = DOS_GetMemory(size,"MSCDEX default buffer");
00415         }
00416         return PhysMake(defaultBufSeg,2352);
00417 }
00418 
00419 PhysPt CMscdex::GetTempBuffer(void) {
00420         if (defaultBufSeg==0) {
00421                 Bit16u size = (2352*2+15)/16;
00422                 defaultBufSeg = DOS_GetMemory(size,"MSCDEX temp buffer");
00423         }
00424         return PhysMake(defaultBufSeg,0);
00425 }
00426 
00427 void CMscdex::GetDriverInfo     (PhysPt data) {
00428         for (Bit16u i=0; i<GetNumDrives(); i++) {
00429                 mem_writeb(data  ,(Bit8u)i);    // subunit
00430                 mem_writed(data+1,RealMake(rootDriverHeaderSeg,0));
00431                 data+=5;
00432         }
00433 }
00434 
00435 bool CMscdex::GetCDInfo(Bit8u subUnit, Bit8u& tr1, Bit8u& tr2, TMSF& leadOut) {
00436         if (subUnit>=numDrives) return false;
00437         int tr1i,tr2i;
00438         // Assume Media change
00439         cdrom[subUnit]->InitNewMedia();
00440         dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioTracks(tr1i,tr2i,leadOut);
00441         if (!dinfo[subUnit].lastResult) {
00442                 tr1 = tr2 = 0;
00443                 memset(&leadOut,0,sizeof(leadOut));
00444         } else {
00445                 tr1 = (Bit8u) tr1i;
00446                 tr2 = (Bit8u) tr2i;
00447         }
00448         return dinfo[subUnit].lastResult;
00449 }
00450 
00451 bool CMscdex::GetTrackInfo(Bit8u subUnit, Bit8u track, Bit8u& attr, TMSF& start) {
00452         if (subUnit>=numDrives) return false;
00453         dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioTrackInfo(track,start,attr);        
00454         if (!dinfo[subUnit].lastResult) {
00455                 attr = 0;
00456                 memset(&start,0,sizeof(start));
00457         }
00458         return dinfo[subUnit].lastResult;
00459 }
00460 
00461 bool CMscdex::PlayAudioSector(Bit8u subUnit, Bit32u sector, Bit32u length) {
00462         if (subUnit>=numDrives) return false;
00463         // If value from last stop is used, this is meant as a resume
00464         // better start using resume command
00465         if (dinfo[subUnit].audioPaused && (sector==dinfo[subUnit].audioStart) && (dinfo[subUnit].audioEnd!=0)) {
00466                 dinfo[subUnit].lastResult = cdrom[subUnit]->PauseAudio(true);
00467         } else 
00468                 dinfo[subUnit].lastResult = cdrom[subUnit]->PlayAudioSector(sector,length);
00469 
00470         if (dinfo[subUnit].lastResult) {
00471                 dinfo[subUnit].audioPlay        = true;
00472                 dinfo[subUnit].audioPaused      = false;
00473                 dinfo[subUnit].audioStart       = sector;
00474                 dinfo[subUnit].audioEnd         = length;
00475         }
00476         return dinfo[subUnit].lastResult;
00477 }
00478 
00479 bool CMscdex::PlayAudioMSF(Bit8u subUnit, Bit32u start, Bit32u length) {
00480         if (subUnit>=numDrives) return false;
00481         Bit8u min               = (Bit8u)(start>>16) & 0xFF;
00482         Bit8u sec               = (Bit8u)(start>> 8) & 0xFF;
00483         Bit8u fr                = (Bit8u)(start>> 0) & 0xFF;
00484         Bit32u sector   = min*60u*75u+sec*75u+fr - 150u;
00485         return dinfo[subUnit].lastResult = PlayAudioSector(subUnit,sector,length);
00486 }
00487 
00488 bool CMscdex::GetSubChannelData(Bit8u subUnit, Bit8u& attr, Bit8u& track, Bit8u &index, TMSF& rel, TMSF& abs) {
00489         if (subUnit>=numDrives) return false;
00490         dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioSub(attr,track,index,rel,abs);
00491         if (!dinfo[subUnit].lastResult) {
00492                 attr = track = index = 0;
00493                 memset(&rel,0,sizeof(rel));
00494                 memset(&abs,0,sizeof(abs));
00495         }
00496         return dinfo[subUnit].lastResult;
00497 }
00498 
00499 bool CMscdex::GetAudioStatus(Bit8u subUnit, bool& playing, bool& pause, TMSF& start, TMSF& end) {
00500         if (subUnit>=numDrives) return false;
00501         dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioStatus(playing,pause);
00502         if (dinfo[subUnit].lastResult) {
00503                 if (playing) {
00504                         // Start
00505                         Bit32u addr     = dinfo[subUnit].audioStart + 150;
00506                         start.fr        = (Bit8u)(addr%75);     addr/=75;
00507                         start.sec       = (Bit8u)(addr%60); 
00508                         start.min       = (Bit8u)(addr/60);
00509                         // End
00510                         addr            = dinfo[subUnit].audioEnd + 150;
00511                         end.fr          = (Bit8u)(addr%75);     addr/=75;
00512                         end.sec         = (Bit8u)(addr%60); 
00513                         end.min         = (Bit8u)(addr/60);
00514                 } else {
00515                         memset(&start,0,sizeof(start));
00516                         memset(&end,0,sizeof(end));
00517                 }
00518         } else {
00519                 playing         = false;
00520                 pause           = false;
00521                 memset(&start,0,sizeof(start));
00522                 memset(&end,0,sizeof(end));
00523         }
00524         
00525         return dinfo[subUnit].lastResult;
00526 }
00527 
00528 bool CMscdex::StopAudio(Bit8u subUnit) {
00529         if (subUnit>=numDrives) return false;
00530         if (dinfo[subUnit].audioPlay) {
00531                 // Check if audio is still playing....
00532                 TMSF start,end;
00533                 bool playing,pause;
00534                 if (GetAudioStatus(subUnit,playing,pause,start,end))
00535                         dinfo[subUnit].audioPlay = playing;
00536                 else
00537                         dinfo[subUnit].audioPlay = false;
00538         }
00539         if (dinfo[subUnit].audioPlay)
00540                 dinfo[subUnit].lastResult = cdrom[subUnit]->PauseAudio(false);
00541         else
00542                 dinfo[subUnit].lastResult = cdrom[subUnit]->StopAudio();
00543         
00544         if (dinfo[subUnit].lastResult) {
00545                 if (dinfo[subUnit].audioPlay) {
00546                         TMSF pos;
00547                         GetCurrentPos(subUnit,pos);
00548                         dinfo[subUnit].audioStart       = pos.min*60u*75u+pos.sec*75u+pos.fr - 150u;
00549                         dinfo[subUnit].audioPaused  = true;
00550                 } else {        
00551                         dinfo[subUnit].audioPaused  = false;
00552                         dinfo[subUnit].audioStart       = 0;
00553                         dinfo[subUnit].audioEnd         = 0;
00554                 }
00555                 dinfo[subUnit].audioPlay = false;
00556         }
00557         return dinfo[subUnit].lastResult;
00558 }
00559 
00560 bool CMscdex::ResumeAudio(Bit8u subUnit) {
00561         if (subUnit>=numDrives) return false;
00562         return dinfo[subUnit].lastResult = PlayAudioSector(subUnit,dinfo[subUnit].audioStart,dinfo[subUnit].audioEnd);
00563 }
00564 
00565 Bit32u CMscdex::GetVolumeSize(Bit8u subUnit) {
00566         if (subUnit>=numDrives) return false;
00567         Bit8u tr1,tr2;
00568         TMSF leadOut;
00569         dinfo[subUnit].lastResult = GetCDInfo(subUnit,tr1,tr2,leadOut);
00570         if (dinfo[subUnit].lastResult) return (leadOut.min*60u*75u)+(leadOut.sec*75u)+leadOut.fr;
00571         return 0;
00572 }
00573 
00574 bool CMscdex::ReadVTOC(Bit16u drive, Bit16u volume, PhysPt data, Bit16u& offset, Bit16u& error) {
00575         Bit8u subunit = GetSubUnit(drive);
00576 /*      if (subunit>=numDrives) {
00577                 error=MSCDEX_ERROR_UNKNOWN_DRIVE;
00578                 return false;
00579         } */
00580         if (!ReadSectors(subunit,false,16u+volume,1u,data)) {
00581                 error=MSCDEX_ERROR_DRIVE_NOT_READY;
00582                 return false;
00583         }
00584         char id[5];
00585         MEM_BlockRead(data + 1, id, 5);
00586         if (strncmp("CD001", id, 5)==0) offset = 0;
00587         else {
00588                 MEM_BlockRead(data + 9, id, 5);
00589                 if (strncmp("CDROM", id, 5)==0) offset = 8;
00590                 else {
00591                         error = MSCDEX_ERROR_BAD_FORMAT;
00592                         return false;
00593                 }
00594         }
00595         Bit8u type = mem_readb(data + offset);
00596         error = (type == 1) ? 1 : (type == 0xFF) ? 0xFF : 0;
00597         return true;
00598 }
00599 
00600 bool CMscdex::GetVolumeName(Bit8u subUnit, char* data) {        
00601         if (subUnit>=numDrives) return false;
00602         Bit16u drive = dinfo[subUnit].drive;
00603 
00604         Bit16u offset = 0, error;
00605         bool success = false;
00606         PhysPt ptoc = GetTempBuffer();
00607         success = ReadVTOC(drive,0x00,ptoc,offset,error);
00608         if (success) {
00609                 MEM_StrCopy(ptoc+offset+40,data,31);
00610                 data[31] = 0;
00611                 rtrim(data);
00612         }
00613 
00614         return success; 
00615 }
00616 
00617 bool CMscdex::GetFileName(Bit16u drive, Bit16u pos, PhysPt data) {
00618         Bit16u offset = 0, error;
00619         bool success = false;
00620         PhysPt ptoc = GetTempBuffer();
00621         success = ReadVTOC(drive,0x00,ptoc,offset,error);
00622         if (success) {
00623                 Bit8u len;
00624                 for (len=0;len<37;len++) {
00625                         Bit8u c=mem_readb(ptoc+offset+pos+len);
00626                         if (c==0 || c==0x20) break;
00627                 }
00628                 MEM_BlockCopy(data,ptoc+offset+pos,len);
00629                 mem_writeb(data+len,0);
00630         }
00631         return success; 
00632 }
00633 
00634 bool CMscdex::GetUPC(Bit8u subUnit, Bit8u& attr, char* upc)
00635 {
00636         if (subUnit>=numDrives) return false;
00637         return dinfo[subUnit].lastResult = cdrom[subUnit]->GetUPC(attr,&upc[0]);
00638 }
00639 
00640 bool CMscdex::ReadSectors(Bit8u subUnit, bool raw, Bit32u sector, Bit16u num, PhysPt data) {
00641         if (subUnit>=numDrives) return false;
00642         if ((cpu_cycles_count_t)(4u*num*2048u+5u) < CPU_Cycles) CPU_Cycles -= cpu_cycles_count_t(4u*num*2048u);
00643         else CPU_Cycles = 5u;
00644         dinfo[subUnit].lastResult = cdrom[subUnit]->ReadSectors(data,raw,sector,num);
00645         return dinfo[subUnit].lastResult;
00646 }
00647 
00648 bool CMscdex::ReadSectorsMSF(Bit8u subUnit, bool raw, Bit32u start, Bit16u num, PhysPt data) {
00649         if (subUnit>=numDrives) return false;
00650         Bit8u min               = (Bit8u)(start>>16) & 0xFF;
00651         Bit8u sec               = (Bit8u)(start>> 8) & 0xFF;
00652         Bit8u fr                = (Bit8u)(start>> 0) & 0xFF;
00653         Bit32u sector   = min*60u*75u+sec*75u+fr - 150u;
00654         return ReadSectors(subUnit,raw,sector,num,data);
00655 }
00656 
00657 // Called from INT 2F
00658 bool CMscdex::ReadSectors(Bit16u drive, Bit32u sector, Bit16u num, PhysPt data) {
00659         return ReadSectors(GetSubUnit(drive),false,sector,num,data);
00660 }
00661 
00662 bool CMscdex::GetDirectoryEntry(Bit16u drive, bool copyFlag, PhysPt pathname, PhysPt buffer, Bit16u& error) {
00663         char    volumeID[6] = {0};
00664         char    searchName[256];
00665         char    entryName[256];
00666         bool    foundComplete = false;
00667         bool    nextPart = true;
00668     const char* useName = NULL;
00669     Bit8u   entryLength, nameLength;
00670         // clear error
00671         error = 0;
00672         MEM_StrCopy(pathname+1,searchName,mem_readb(pathname));
00673         upcase(searchName);
00674         char* searchPos = searchName;
00675 
00676         //strip of tailing . (XCOM APOCALYPSE)
00677         size_t searchlen = strlen(searchName);
00678         if (searchlen > 1 && strcmp(searchName,".."))
00679                 if (searchName[searchlen-1] =='.')  searchName[searchlen-1] = 0;
00680 
00681         //LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Get DirEntry : Find : %s",searchName);
00682         // read vtoc
00683         PhysPt defBuffer = GetDefaultBuffer();
00684         if (!ReadSectors(GetSubUnit(drive),false,16,1,defBuffer)) return false;
00685         MEM_StrCopy(defBuffer+1,volumeID,5); volumeID[5] = 0;
00686         bool iso = (strcmp("CD001",volumeID)==0);
00687         if (!iso) {
00688                 MEM_StrCopy(defBuffer+9,volumeID,5);
00689                 if (strcmp("CDROM",volumeID)!=0) E_Exit("MSCDEX: GetDirEntry: Not an ISO 9660 or HSF CD.");
00690         }
00691         Bit16u offset = iso ? 156:180;
00692         // get directory position
00693         Bit32u dirEntrySector   = mem_readd(defBuffer+offset+2);
00694         Bits dirSize            = (Bit32s)mem_readd(defBuffer+offset+10);
00695         while (dirSize>0) {
00696                 Bit16u index = 0;
00697                 if (!ReadSectors(GetSubUnit(drive),false,dirEntrySector,1,defBuffer)) return false;
00698                 // Get string part
00699                 bool foundName = false;
00700                 if (nextPart) {
00701                         if (searchPos) { 
00702                                 useName = searchPos; 
00703                                 searchPos = strchr(searchPos,'\\'); 
00704                         }
00705                         if (searchPos) { *searchPos = 0; searchPos++; }
00706                         else foundComplete = true;
00707                 }
00708 
00709                 do {
00710                         entryLength = mem_readb(defBuffer+index);
00711                         if (entryLength==0) break;
00712                         if (mem_readb(defBuffer + index + (iso?0x19:0x18) ) & 4) {
00713                                 // skip associated files
00714                                 index += entryLength;
00715                                 continue;
00716                         }
00717                         nameLength  = mem_readb(defBuffer+index+32);
00718                         MEM_StrCopy(defBuffer+index+33,entryName,nameLength);
00719                         // strip separator and file version number
00720                         char* separator = strchr(entryName,';');
00721                         if (separator) *separator = 0;
00722                         // strip trailing period
00723                         size_t entrylen = strlen(entryName);
00724                         if (entrylen>0 && entryName[entrylen-1]=='.') entryName[entrylen-1] = 0;
00725 
00726                         if (strcmp(entryName,useName)==0) {
00727                                 //LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Get DirEntry : Found : %s",useName);
00728                                 foundName = true;
00729                                 break;
00730                         }
00731                         index += entryLength;
00732                 } while (index+33<=2048);
00733                 
00734                 if (foundName) {
00735                         if (foundComplete) {
00736                                 if (copyFlag) {
00737                                         LOG(LOG_MISC,LOG_WARN)("MSCDEX: GetDirEntry: Copyflag structure not entirely accurate maybe");
00738                                         Bit8u readBuf[256];
00739                                         Bit8u writeBuf[256];
00740                                         MEM_BlockRead( defBuffer+index, readBuf, entryLength );
00741                                         writeBuf[0] = readBuf[1];                                               // 00h  BYTE    length of XAR in Logical Block Numbers
00742                                         memcpy( &writeBuf[1], &readBuf[0x2], 4);                // 01h  DWORD   Logical Block Number of file start
00743                                         writeBuf[5] = 0;writeBuf[6] = 8;                                // 05h  WORD    size of disk in logical blocks
00744                                         memcpy( &writeBuf[7], &readBuf[0xa], 4);                // 07h  DWORD   file length in bytes
00745                                         memcpy( &writeBuf[0xb], &readBuf[0x12], 6);             // 0bh  BYTEs   date and time
00746                                         writeBuf[0x11] = iso ? readBuf[0x18]:0;                 // 11h  BYTE    time zone
00747                                         writeBuf[0x12] = readBuf[iso ? 0x19:0x18];              // 12h  BYTE    bit flags
00748                                         writeBuf[0x13] = readBuf[0x1a];                                 // 13h  BYTE    interleave size
00749                                         writeBuf[0x14] = readBuf[0x1b];                                 // 14h  BYTE    interleave skip factor
00750                                         memcpy( &writeBuf[0x15], &readBuf[0x1c], 2);    // 15h  WORD    volume set sequence number
00751                                         writeBuf[0x17] = readBuf[0x20];
00752                                         memcpy( &writeBuf[0x18], &readBuf[21], readBuf[0x20] <= 38 ? readBuf[0x20] : 38 );
00753                                         MEM_BlockWrite( buffer, writeBuf, 0x18 + 40 );
00754                                 } else {
00755                                         // Direct copy
00756                                         MEM_BlockCopy(buffer,defBuffer+index,entryLength);
00757                                 }
00758                                 error = iso ? 1:0;
00759                                 return true;
00760                         }
00761                         // change directory
00762                         dirEntrySector = mem_readd(defBuffer+index+2);
00763                         dirSize = (Bit32s)mem_readd(defBuffer+index+10);
00764                         nextPart = true;
00765                 } else {
00766                         // continue search in next sector
00767                         dirSize -= 2048;
00768                         dirEntrySector++;
00769                         nextPart = false;
00770                 }
00771         }
00772         error = 2; // file not found
00773         return false; // not found
00774 }
00775 
00776 bool CMscdex::GetCurrentPos(Bit8u subUnit, TMSF& pos) {
00777         if (subUnit>=numDrives) return false;
00778         TMSF rel;
00779         Bit8u attr,track,index;
00780         dinfo[subUnit].lastResult = GetSubChannelData(subUnit, attr, track, index, rel, pos);
00781         if (!dinfo[subUnit].lastResult) memset(&pos,0,sizeof(pos));
00782         return dinfo[subUnit].lastResult;
00783 }
00784 
00785 bool CMscdex::GetMediaStatus(Bit8u subUnit, bool& media, bool& changed, bool& trayOpen) {
00786         if (subUnit>=numDrives) return false;
00787         dinfo[subUnit].lastResult = cdrom[subUnit]->GetMediaTrayStatus(media,changed,trayOpen);
00788         return dinfo[subUnit].lastResult;
00789 }
00790 
00791 Bit32u CMscdex::GetDeviceStatus(Bit8u subUnit) {
00792         if (subUnit>=numDrives) return false;
00793         bool media,changed,trayOpen;
00794 
00795         dinfo[subUnit].lastResult = GetMediaStatus(subUnit,media,changed,trayOpen);
00796         if (dinfo[subUnit].audioPlay) {
00797                 // Check if audio is still playing....
00798                 TMSF start,end;
00799                 bool playing,pause;
00800                 if (GetAudioStatus(subUnit,playing,pause,start,end))
00801                         dinfo[subUnit].audioPlay = playing;
00802                 else
00803                         dinfo[subUnit].audioPlay = false;
00804         }
00805 
00806         Bit32u status = ((trayOpen?1u:0u) << 0u)                                        |       // Drive is open ?
00807                                         ((dinfo[subUnit].locked?1u:0u) << 1u)           |       // Drive is locked ?
00808                                         (1u<<2u)                                                                        |       // raw + cooked sectors
00809                                         (1u<<4u)                                                                        |       // Can read sudio
00810                                         (1u<<8u)                                                                        |       // Can control audio
00811                                         (1u<<9u)                                                                        |       // Red book & HSG
00812                                         ((dinfo[subUnit].audioPlay?1u:0u) << 10u)       |       // Audio is playing ?
00813                                         ((media?0u:1u) << 11u);                                         // Drive is empty ?
00814         return status;
00815 }
00816 
00817 bool CMscdex::GetMediaStatus(Bit8u subUnit, Bit8u& status) {
00818         if (subUnit>=numDrives) return false;
00819 /*      bool media,changed,open,result;
00820         result = GetMediaStatus(subUnit,media,changed,open);
00821         status = changed ? 0xFF : 0x01;
00822         return result; */
00823         status = getSwapRequest() ? 0xFF : 0x01;
00824         return true;
00825 }
00826 
00827 bool CMscdex::LoadUnloadMedia(Bit8u subUnit, bool unload) {
00828         if (subUnit>=numDrives) return false;
00829         dinfo[subUnit].lastResult = cdrom[subUnit]->LoadUnloadMedia(unload);
00830         return dinfo[subUnit].lastResult;
00831 }
00832 
00833 bool CMscdex::SendDriverRequest(Bit16u drive, PhysPt data) {
00834         Bit8u subUnit = GetSubUnit(drive);
00835         if (subUnit>=numDrives) return false;
00836         // Get SubUnit
00837         mem_writeb(data+1,subUnit);
00838         // Call Strategy / Interrupt
00839         MSCDEX_Strategy_Handler();
00840         MSCDEX_Interrupt_Handler();
00841         return true;
00842 }
00843 
00844 Bit16u CMscdex::GetStatusWord(Bit8u subUnit,Bit16u status) {
00845         if (subUnit>=numDrives) return REQUEST_STATUS_ERROR | 0x02; // error : Drive not ready
00846 
00847         if (dinfo[subUnit].lastResult)  status |= REQUEST_STATUS_DONE;                          // ok
00848         else                                                    status |= REQUEST_STATUS_ERROR; 
00849 
00850         if (dinfo[subUnit].audioPlay) {
00851                 // Check if audio is still playing....
00852                 TMSF start,end;
00853                 bool playing,pause;
00854                 if (GetAudioStatus(subUnit,playing,pause,start,end)) {
00855                         dinfo[subUnit].audioPlay = playing;
00856                 } else
00857                         dinfo[subUnit].audioPlay = false;
00858 
00859                 status |= (dinfo[subUnit].audioPlay<<9);
00860         } 
00861         dinfo[subUnit].lastResult       = true;
00862         return status;
00863 }
00864 
00865 void CMscdex::InitNewMedia(Bit8u subUnit) {
00866         if (subUnit<numDrives) {
00867                 // Reopen new media
00868                 cdrom[subUnit]->InitNewMedia();
00869         }
00870 }
00871 
00872 bool CMscdex::ChannelControl(Bit8u subUnit, TCtrl ctrl) {
00873         if (subUnit>=numDrives) return false;
00874         // adjust strange channel mapping
00875         if (ctrl.out[0]>1) ctrl.out[0]=0;
00876         if (ctrl.out[1]>1) ctrl.out[1]=1;
00877         dinfo[subUnit].audioCtrl=ctrl;
00878         cdrom[subUnit]->ChannelControl(ctrl);
00879         return true;
00880 }
00881 
00882 bool CMscdex::GetChannelControl(Bit8u subUnit, TCtrl& ctrl) {
00883         if (subUnit>=numDrives) return false;
00884         ctrl=dinfo[subUnit].audioCtrl;
00885         return true;
00886 }
00887 
00888 static CMscdex* mscdex = 0;
00889 static PhysPt curReqheaderPtr = 0;
00890 
00891 bool GetMSCDEXDrive(unsigned char drive_letter,CDROM_Interface **_cdrom) {
00892         Bitu i;
00893 
00894         if (mscdex == NULL) {
00895                 if (_cdrom) *_cdrom = NULL;
00896                 return false;
00897         }
00898 
00899         for (i=0;i < MSCDEX_MAX_DRIVES;i++) {
00900                 if (mscdex->cdrom[i] == NULL) continue;
00901                 if (mscdex->dinfo[i].drive == drive_letter) {
00902                         if (_cdrom) *_cdrom = mscdex->cdrom[i];
00903                         return true;
00904                 }
00905         }
00906 
00907         return false;
00908 }
00909 
00910 static Bit16u MSCDEX_IOCTL_Input(PhysPt buffer,Bit8u drive_unit) {
00911         Bit8u ioctl_fct = mem_readb(buffer);
00912         MSCDEX_LOG("MSCDEX: IOCTL INPUT Subfunction %02X",(int)ioctl_fct);
00913         switch (ioctl_fct) {
00914                 case 0x00 : /* Get Device Header address */
00915                                         mem_writed(buffer+1,RealMake(mscdex->rootDriverHeaderSeg,0));
00916                                         break;
00917                 case 0x01 :{/* Get current position */
00918                                         TMSF pos;
00919                                         mscdex->GetCurrentPos(drive_unit,pos);
00920                                         Bit8u addr_mode = mem_readb(buffer+1);
00921                                         if (addr_mode==0) {                     // HSG
00922                                                 Bit32u frames=MSF_TO_FRAMES(pos.min, pos.sec, pos.fr);
00923                                                 if (frames<150) MSCDEX_LOG_ERROR("MSCDEX: Get position: invalid position %d:%d:%d", pos.min, pos.sec, pos.fr);
00924                                                 else frames-=150;
00925                                                 mem_writed(buffer+2,frames);
00926                                         } else if (addr_mode==1) {      // Red book
00927                                                 mem_writeb(buffer+2,pos.fr);
00928                                                 mem_writeb(buffer+3,pos.sec);
00929                                                 mem_writeb(buffer+4,pos.min);
00930                                                 mem_writeb(buffer+5,0x00);
00931                                         } else {
00932                                                 MSCDEX_LOG_ERROR("MSCDEX: Get position: invalid address mode %x",addr_mode);
00933                                                 return 0x03;            // invalid function
00934                                         }
00935                                    }break;
00936                 case 0x04 : /* Audio Channel control */
00937                                         TCtrl ctrl;
00938                                         if (!mscdex->GetChannelControl(drive_unit,ctrl)) return 0x01;
00939                                         for (Bit8u chan=0;chan<4;chan++) {
00940                                                 mem_writeb(buffer+chan*2u+1u,ctrl.out[chan]);
00941                                                 mem_writeb(buffer+chan*2u+2u,ctrl.vol[chan]);
00942                                         }
00943                                         break;
00944                 case 0x06 : /* Get Device status */
00945                                         mem_writed(buffer+1,mscdex->GetDeviceStatus(drive_unit)); 
00946                                         break;
00947                 case 0x07 : /* Get sector size */
00948                                         if (mem_readb(buffer+1)==0) mem_writew(buffer+2,2048);
00949                                         else if (mem_readb(buffer+1)==1) mem_writew(buffer+2,2352);
00950                                         else return 0x03;               // invalid function
00951                                         break;
00952                 case 0x08 : /* Get size of current volume */
00953                                         mem_writed(buffer+1,mscdex->GetVolumeSize(drive_unit));
00954                                         break;
00955                 case 0x09 : /* Media change ? */
00956                                         Bit8u status;
00957                                         if (!mscdex->GetMediaStatus(drive_unit,status)) {
00958                                                 status = 0;             // state unknown
00959                                         }
00960                                         mem_writeb(buffer+1,status);
00961                                         break;
00962                 case 0x0A : /* Get Audio Disk info */   
00963                                         Bit8u tr1,tr2; TMSF leadOut;
00964                                         if (!mscdex->GetCDInfo(drive_unit,tr1,tr2,leadOut)) return 0x05;
00965                                         mem_writeb(buffer+1,tr1);
00966                                         mem_writeb(buffer+2,tr2);
00967                                         mem_writeb(buffer+3,leadOut.fr);
00968                                         mem_writeb(buffer+4,leadOut.sec);
00969                                         mem_writeb(buffer+5,leadOut.min);
00970                                         mem_writeb(buffer+6,0x00);
00971                                         break;
00972                 case 0x0B :{/* Audio Track Info */
00973                                         Bit8u attr; TMSF start;
00974                                         Bit8u track = mem_readb(buffer+1);
00975                                         mscdex->GetTrackInfo(drive_unit,track,attr,start);              
00976                                         mem_writeb(buffer+2,start.fr);
00977                                         mem_writeb(buffer+3,start.sec);
00978                                         mem_writeb(buffer+4,start.min);
00979                                         mem_writeb(buffer+5,0x00);
00980                                         mem_writeb(buffer+6,attr);
00981                                         break; }
00982                 case 0x0C :{/* Get Audio Sub Channel data */
00983                                         Bit8u attr,track,index; 
00984                                         TMSF abs,rel;
00985                                         mscdex->GetSubChannelData(drive_unit,attr,track,index,rel,abs);
00986                                         mem_writeb(buffer+1,attr);
00987                                         mem_writeb(buffer+2,track);
00988                                         mem_writeb(buffer+3,index);
00989                                         mem_writeb(buffer+4,rel.min);
00990                                         mem_writeb(buffer+5,rel.sec);
00991                                         mem_writeb(buffer+6,rel.fr);
00992                                         mem_writeb(buffer+7,0x00);
00993                                         mem_writeb(buffer+8,abs.min);
00994                                         mem_writeb(buffer+9,abs.sec);
00995                                         mem_writeb(buffer+10,abs.fr);
00996                                         break;
00997                                    }
00998                 case 0x0E :{ /* Get UPC */      
00999                                         Bit8u attr; char upc[8];
01000                                         mscdex->GetUPC(drive_unit,attr,&upc[0]);
01001                                         mem_writeb(buffer+1u,attr);
01002                                         for (unsigned int i=0; i<7; i++) mem_writeb(buffer+2u+i,(unsigned char)upc[i]);
01003                                         mem_writeb(buffer+9,0x00u);
01004                                         break;
01005                                    }
01006                 case 0x0F :{ /* Get Audio Status */     
01007                                         bool playing,pause;
01008                                         TMSF resStart,resEnd;
01009                                         mscdex->GetAudioStatus(drive_unit,playing,pause,resStart,resEnd);
01010                                         mem_writeb(buffer+1u,pause);
01011                                         mem_writeb(buffer+3u,resStart.min);
01012                                         mem_writeb(buffer+4u,resStart.sec);
01013                                         mem_writeb(buffer+5u,resStart.fr);
01014                                         mem_writeb(buffer+6u,0x00u);
01015                                         mem_writeb(buffer+7u,resEnd.min);
01016                                         mem_writeb(buffer+8u,resEnd.sec);
01017                                         mem_writeb(buffer+9u,resEnd.fr);
01018                                         mem_writeb(buffer+10u,0x00u);
01019                                         break;
01020                                    }
01021                 default :       LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unsupported IOCTL INPUT Subfunction %02X",(int)ioctl_fct);
01022                                         return 0x03;    // invalid function
01023         }
01024         return 0x00;    // success
01025 }
01026 
01027 static Bit16u MSCDEX_IOCTL_Optput(PhysPt buffer,Bit8u drive_unit) {
01028         Bit8u ioctl_fct = mem_readb(buffer);
01029 //      MSCDEX_LOG("MSCDEX: IOCTL OUTPUT Subfunction %02X",ioctl_fct);
01030         switch (ioctl_fct) {
01031                 case 0x00 :     // Unload /eject media
01032                                         if (!mscdex->LoadUnloadMedia(drive_unit,true)) return 0x02;
01033                                         break;
01034                 case 0x03: //Audio Channel control
01035                                         TCtrl ctrl;
01036                                         for (Bit8u chan=0;chan<4;chan++) {
01037                                                 ctrl.out[chan]=mem_readb(buffer+chan*2u+1u);
01038                                                 ctrl.vol[chan]=mem_readb(buffer+chan*2u+2u);
01039                                         }
01040                                         if (!mscdex->ChannelControl(drive_unit,ctrl)) return 0x01;
01041                                         break;
01042                 case 0x01 : // (un)Lock door 
01043                                         // do nothing -> report as success
01044                                         break;
01045                 case 0x02 : // Reset Drive
01046                                         LOG(LOG_MISC,LOG_WARN)("cdromDrive reset");
01047                                         if (!mscdex->StopAudio(drive_unit))  return 0x02;
01048                                         break;
01049                 case 0x05 :     // load media
01050                                         if (!mscdex->LoadUnloadMedia(drive_unit,false)) return 0x02;
01051                                         break;
01052                 default :       LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unsupported IOCTL OUTPUT Subfunction %02X",(int)ioctl_fct);
01053                                         return 0x03;    // invalid function
01054         }
01055         return 0x00;    // success
01056 }
01057 
01058 static Bitu MSCDEX_Strategy_Handler(void) {
01059         curReqheaderPtr = PhysMake(SegValue(es),reg_bx);
01060 //      MSCDEX_LOG("MSCDEX: Device Strategy Routine called, request header at %x",curReqheaderPtr);
01061         return CBRET_NONE;
01062 }
01063 
01064 static Bitu MSCDEX_Interrupt_Handler(void) {
01065         if (curReqheaderPtr==0) {
01066                 MSCDEX_LOG_ERROR("MSCDEX: invalid call to interrupt handler");
01067                 return CBRET_NONE;
01068         }
01069         Bit8u   subUnit         = mem_readb(curReqheaderPtr+1);
01070         Bit8u   funcNr          = mem_readb(curReqheaderPtr+2);
01071         Bit16u  errcode         = 0;
01072         PhysPt  buffer          = 0;
01073 
01074         MSCDEX_LOG("MSCDEX: Driver Function %02X",funcNr);
01075 
01076         if ((funcNr==0x03) || (funcNr==0x0c) || (funcNr==0x80) || (funcNr==0x82)) {
01077                 buffer = PhysMake(mem_readw(curReqheaderPtr+0x10),mem_readw(curReqheaderPtr+0x0E));
01078         }
01079 
01080         switch (funcNr) {
01081                 case 0x03       : {     /* IOCTL INPUT */
01082                                                 Bit16u error=MSCDEX_IOCTL_Input(buffer,subUnit);
01083                                                 if (error) errcode = error;
01084                                                 break;
01085                                           }
01086                 case 0x0C       : {     /* IOCTL OUTPUT */
01087                                                 Bit16u error=MSCDEX_IOCTL_Optput(buffer,subUnit);
01088                                                 if (error) errcode = error;
01089                                                 break;
01090                                           }
01091                 case 0x0D       :       // device open
01092                 case 0x0E       :       // device close - dont care :)
01093                                                 break;
01094                 case 0x80       :       // Read long
01095                 case 0x82       : { // Read long prefetch -> both the same here :)
01096                                                 Bit32u start = mem_readd(curReqheaderPtr+0x14);
01097                                                 Bit16u len       = mem_readw(curReqheaderPtr+0x12);
01098                                                 bool raw         = (mem_readb(curReqheaderPtr+0x18)==1);
01099                                                 if (mem_readb(curReqheaderPtr+0x0D)==0x00) // HSG
01100                                                         mscdex->ReadSectors(subUnit,raw,start,len,buffer);
01101                                                 else 
01102                                                         mscdex->ReadSectorsMSF(subUnit,raw,start,len,buffer);
01103                                                 break;
01104                                           }
01105                 case 0x83       :       // Seek - dont care :)
01106                                                 break;
01107                 case 0x84       : {     /* Play Audio Sectors */
01108                                                 Bit32u start = mem_readd(curReqheaderPtr+0x0E);
01109                                                 Bit32u len       = mem_readd(curReqheaderPtr+0x12);
01110                                                 if (mem_readb(curReqheaderPtr+0x0D)==0x00) // HSG
01111                                                         mscdex->PlayAudioSector(subUnit,start,len);
01112                                                 else // RED BOOK
01113                                                         mscdex->PlayAudioMSF(subUnit,start,len);
01114                                                 break;
01115                                           }
01116                 case 0x85       :       /* Stop Audio */
01117                                                 mscdex->StopAudio(subUnit);
01118                                                 break;
01119                 case 0x88       :       /* Resume Audio */
01120                                                 mscdex->ResumeAudio(subUnit);
01121                                                 break;
01122                 default         :       MSCDEX_LOG_ERROR("Unsupported Driver Request %02X",funcNr);
01123                                                 break;
01124         
01125         }
01126         
01127         // Set Statusword
01128         mem_writew(curReqheaderPtr+3,mscdex->GetStatusWord(subUnit,errcode));
01129         MSCDEX_LOG("MSCDEX: Status : %04X",mem_readw(curReqheaderPtr+3));                                               
01130         return CBRET_NONE;
01131 }
01132 
01133 static bool MSCDEX_Handler(void) {
01134         if(reg_ah == 0x11) {
01135                 if(reg_al == 0x00) { 
01136                         if (mscdex->rootDriverHeaderSeg==0) return false;
01137                         PhysPt check = PhysMake(SegValue(ss),reg_sp);
01138                         if(mem_readw(check+6) == 0xDADA) {
01139                                 //MSCDEX sets word on stack to ADAD if it DADA on entry.
01140                                 mem_writew(check+6,0xADAD);
01141                         }
01142                         reg_al = 0xff;
01143                         return true;
01144                 } else {
01145                         LOG(LOG_MISC,LOG_ERROR)("NETWORK REDIRECTOR USED!!!");
01146                         reg_ax = 0x49;//NETWERK SOFTWARE NOT INSTALLED
01147                         CALLBACK_SCF(true);
01148                         return true;
01149                 }
01150         }
01151 
01152         if (reg_ah!=0x15) return false;         // not handled here, continue chain
01153         if (mscdex->rootDriverHeaderSeg==0) return false;       // not handled if MSCDEX not installed
01154 
01155         PhysPt data = PhysMake(SegValue(es),reg_bx);
01156         MSCDEX_LOG("MSCDEX: INT 2F AX=%04X BX=%04X CX=%04X",reg_ax,reg_bx,reg_cx);
01157         CALLBACK_SCF(false); // carry flag cleared for all functions (undocumented); only set on error
01158         switch (reg_ax) {
01159                 case 0x1500:    /* Install check */
01160                                                 reg_bx = mscdex->GetNumDrives();
01161                                                 if (reg_bx>0) reg_cx = mscdex->GetFirstDrive();
01162                                                 reg_al = 0xff;
01163                                                 break;
01164                 case 0x1501:    /* Get cdrom driver info */
01165                                                 mscdex->GetDriverInfo(data);
01166                                                 break;
01167                 case 0x1502:    /* Get Copyright filename */
01168                 case 0x1503:    /* Get Abstract filename */
01169                 case 0x1504:    /* Get Documentation filename */
01170                                                 if (!mscdex->GetFileName(reg_cx,702+(reg_al-2)*37,data)) {
01171                                                         reg_ax = MSCDEX_ERROR_UNKNOWN_DRIVE;
01172                                                         CALLBACK_SCF(true);                                             
01173                                                 }
01174                                                 break;          
01175                 case 0x1505: {  // read vtoc 
01176                                                 Bit16u offset = 0, error = 0;
01177                                                 bool success = mscdex->ReadVTOC(reg_cx,reg_dx,data,offset,error);
01178                                                 reg_ax = error;
01179                                                 if (!success) CALLBACK_SCF(true);
01180                                         }
01181                                                 break;
01182                 case 0x1506:    /* Debugging on */
01183                 case 0x1507:    /* Debugging off */
01184                                                 // not functional in production MSCDEX
01185                                                 break;
01186                 case 0x1508: {  // read sectors 
01187                                                 Bit32u sector = ((Bit32u)reg_si << 16u) + (Bit32u)reg_di;
01188                                                 if (mscdex->ReadSectors(reg_cx,sector,reg_dx,data)) {
01189                                                         reg_ax = 0;
01190                                                 } else {
01191                                                         // possibly: MSCDEX_ERROR_DRIVE_NOT_READY if sector is beyond total length
01192                                                         reg_ax = MSCDEX_ERROR_UNKNOWN_DRIVE;
01193                                                         CALLBACK_SCF(true);
01194                                                 }
01195                                         }
01196                                                 break;
01197                 case 0x1509:    // write sectors - not supported 
01198                                                 reg_ax = MSCDEX_ERROR_INVALID_FUNCTION;
01199                                                 CALLBACK_SCF(true);
01200                                                 break;
01201                 case 0x150A:    /* Reserved */
01202                                                 break;
01203                 case 0x150B:    /* Valid CDROM drive ? */
01204                                                 reg_ax = (mscdex->IsValidDrive(reg_cx) ? 0x5ad8 : 0x0000);
01205                                                 reg_bx = 0xADAD;
01206                                                 break;
01207                 case 0x150C:    /* Get MSCDEX Version */
01208                                                 reg_bx = mscdex->GetVersion();
01209                                                 break;
01210                 case 0x150D:    /* Get drives */
01211                                                 mscdex->GetDrives(data);
01212                                                 break;
01213                 case 0x150E:    /* Get/Set Volume Descriptor Preference */
01214                                                 if (mscdex->IsValidDrive(reg_cx)) {
01215                                                         if (reg_bx == 0) {
01216                                                                 // get preference
01217                                                                 reg_dx = 0x100; // preference?
01218                                                         } else if (reg_bx == 1) {
01219                                                                 // set preference
01220                                                                 if (reg_dh != 1) {
01221                                                                         reg_ax = MSCDEX_ERROR_INVALID_FUNCTION;
01222                                                                         CALLBACK_SCF(true);
01223                                                                 }
01224                                                         } else {
01225                                                                 reg_ax = MSCDEX_ERROR_INVALID_FUNCTION;
01226                                                                 CALLBACK_SCF(true);
01227                                                         }
01228                                                 } else {
01229                                                         reg_ax = MSCDEX_ERROR_UNKNOWN_DRIVE;
01230                                                         CALLBACK_SCF(true);
01231                                                 }
01232                                                 break;
01233                 case 0x150F: {  // Get directory entry
01234                                                 Bit16u error;
01235                                                 bool success = mscdex->GetDirectoryEntry(reg_cl,reg_ch&1,data,PhysMake(reg_si,reg_di),error);
01236                                                 reg_ax = error;
01237                                                 if (!success) CALLBACK_SCF(true);
01238                                          }
01239                                                 break;
01240                 case 0x1510:    /* Device driver request */
01241                                                 if (!mscdex->SendDriverRequest(reg_cx,data)) {
01242                                                         reg_ax = MSCDEX_ERROR_UNKNOWN_DRIVE;
01243                                                         CALLBACK_SCF(true);
01244                                                 }
01245                                                 break;
01246                 default:                LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unknown call : %04X",reg_ax);
01247                                                 reg_ax = MSCDEX_ERROR_INVALID_FUNCTION;
01248                                                 CALLBACK_SCF(true);
01249                                                 break;
01250         }
01251         return true;
01252 }
01253 
01254 class device_MSCDEX : public DOS_Device {
01255 public:
01256         device_MSCDEX() { SetName("MSCD001"); }
01257         bool Read (Bit8u * /*data*/,Bit16u * /*size*/) { return false;}
01258         bool Write(const Bit8u * /*data*/,Bit16u * /*size*/) { 
01259                 LOG(LOG_ALL,LOG_NORMAL)("Write to mscdex device");      
01260                 return false;
01261         }
01262         bool Seek(Bit32u * /*pos*/,Bit32u /*type*/){return false;}
01263         bool Close(){return false;}
01264         Bit16u GetInformation(void){return 0xc880;}
01265         bool ReadFromControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode);
01266         bool WriteToControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode);
01267 // private:
01268 //  Bit8u cache;
01269 };
01270 
01271 bool device_MSCDEX::ReadFromControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode) { 
01272         if (MSCDEX_IOCTL_Input(bufptr,0)==0) {
01273                 *retcode=size;
01274                 return true;
01275         }
01276         return false;
01277 }
01278 
01279 bool device_MSCDEX::WriteToControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode) { 
01280         if (MSCDEX_IOCTL_Optput(bufptr,0)==0) {
01281                 *retcode=size;
01282                 return true;
01283         }
01284         return false;
01285 }
01286 
01287 int MSCDEX_AddDrive(char driveLetter, const char* physicalPath, Bit8u& subUnit)
01288 {
01289         int result = mscdex->AddDrive(driveLetter-'A',(char*)physicalPath,subUnit);
01290         return result;
01291 }
01292 
01293 int MSCDEX_RemoveDrive(char driveLetter)
01294 {
01295         if(!mscdex) return 0;
01296         return mscdex->RemoveDrive(driveLetter-'A');
01297 }
01298 
01299 bool MSCDEX_HasDrive(char driveLetter)
01300 {
01301         return mscdex->HasDrive(driveLetter-'A');
01302 }
01303 
01304 void MSCDEX_ReplaceDrive(CDROM_Interface* cdrom, Bit8u subUnit)
01305 {
01306         mscdex->ReplaceDrive(cdrom, subUnit);
01307 }
01308 
01309 Bit8u MSCDEX_GetSubUnit(char driveLetter)
01310 {
01311         return mscdex->GetSubUnit(driveLetter-'A');
01312 }
01313 
01314 bool MSCDEX_GetVolumeName(Bit8u subUnit, char* name)
01315 {
01316         return mscdex->GetVolumeName(subUnit,name);
01317 }
01318 
01319 bool MSCDEX_HasMediaChanged(Bit8u subUnit)
01320 {
01321         static TMSF leadOut[MSCDEX_MAX_DRIVES];
01322 
01323         TMSF leadnew;
01324         Bit8u tr1,tr2;
01325         if (mscdex->GetCDInfo(subUnit,tr1,tr2,leadnew)) {
01326                 bool changed = (leadOut[subUnit].min!=leadnew.min) || (leadOut[subUnit].sec!=leadnew.sec) || (leadOut[subUnit].fr!=leadnew.fr);
01327                 if (changed) {
01328                         leadOut[subUnit].min = leadnew.min;
01329                         leadOut[subUnit].sec = leadnew.sec;
01330                         leadOut[subUnit].fr      = leadnew.fr;
01331                         mscdex->InitNewMedia(subUnit);
01332                 }
01333                 return changed;
01334         }
01335         if (subUnit<MSCDEX_MAX_DRIVES) {
01336                 leadOut[subUnit].min = 0;
01337                 leadOut[subUnit].sec = 0;
01338                 leadOut[subUnit].fr      = 0;
01339         }
01340         return true;
01341 }
01342 
01343 void MSCDEX_SetCDInterface(int intNr, int numCD) {
01344         useCdromInterface = intNr;
01345         forceCD = numCD;
01346 }
01347 
01348 void MSCDEX_ShutDown(Section* /*sec*/) {
01349         if (mscdex != NULL) {
01350                 delete mscdex;
01351                 mscdex = NULL;
01352         }
01353 
01354         curReqheaderPtr = 0;
01355 }
01356 
01357 /* HACK: The IDE emulation is messily tied into calling MSCDEX.EXE!
01358  *       We cannot shut down the mscdex object when booting into a guest OS!
01359  *       Need to fix this, this is backwards! */
01360 void MSCDEX_DOS_ShutDown(Section* /*sec*/) {
01361         curReqheaderPtr = 0;
01362 }
01363 
01364 void MSCDEX_Startup(Section* sec) {
01365     (void)sec;//UNUSED
01366         if (mscdex == NULL) {
01367                 LOG(LOG_MISC,LOG_DEBUG)("Allocating MSCDEX.EXE emulation");
01368 
01369                 /* Register the mscdex device */
01370                 DOS_Device * newdev = new device_MSCDEX();
01371                 DOS_AddDevice(newdev);
01372                 curReqheaderPtr = 0;
01373                 /* Add Multiplexer */
01374                 DOS_AddMultiplexHandler(MSCDEX_Handler);
01375                 /* Create MSCDEX */
01376                 mscdex = new CMscdex;
01377         }
01378 }
01379 
01380 void MSCDEX_Init() {
01381         LOG(LOG_MISC,LOG_DEBUG)("Initializing MSCDEX.EXE emulation");
01382 
01383         AddExitFunction(AddExitFunctionFuncPair(MSCDEX_ShutDown));
01384 
01385         /* in any event that the DOS kernel is shutdown or abruptly wiped from memory */
01386         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(MSCDEX_ShutDown));
01387         AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(MSCDEX_DOS_ShutDown));
01388 }
01389 
01390 void CMscdex::SaveState( std::ostream& stream )
01391 {
01392         // - pure data
01393         WRITE_POD( &defaultBufSeg, defaultBufSeg );
01394         WRITE_POD( &rootDriverHeaderSeg, rootDriverHeaderSeg );
01395 }
01396 
01397 void CMscdex::LoadState( std::istream& stream )
01398 {
01399         // - pure data
01400         READ_POD( &defaultBufSeg, defaultBufSeg );
01401         READ_POD( &rootDriverHeaderSeg, rootDriverHeaderSeg );
01402 }
01403 
01404 extern bool dos_kernel_disabled;
01405 
01406 void POD_Save_DOS_Mscdex( std::ostream& stream )
01407 {
01408         if (!dos_kernel_disabled) {
01409                 for (Bit8u drive_unit=0; drive_unit<mscdex->GetNumDrives(); drive_unit++) {
01410                         TMSF pos, start, end;
01411                         bool playing, pause;
01412 
01413                         mscdex->GetAudioStatus(drive_unit, playing, pause, start, end);
01414                         mscdex->GetCurrentPos(drive_unit,pos);
01415 
01416 
01417                         WRITE_POD( &playing, playing );
01418                         WRITE_POD( &pause, pause );
01419                         WRITE_POD( &pos, pos );
01420                         WRITE_POD( &start, start );
01421                         WRITE_POD( &end, end );
01422                 }
01423 
01424                 mscdex->SaveState(stream);
01425         }
01426 }
01427 
01428 
01429 void POD_Load_DOS_Mscdex( std::istream& stream )
01430 {
01431         if (!dos_kernel_disabled) {
01432                 for (Bit8u drive_unit=0; drive_unit<mscdex->GetNumDrives(); drive_unit++) {
01433                         TMSF pos, start, end;
01434                         Bit32u msf_time, play_len;
01435                         bool playing, pause;
01436 
01437 
01438                         READ_POD( &playing, playing );
01439                         READ_POD( &pause, pause );
01440                         READ_POD( &pos, pos );
01441                         READ_POD( &start, start );
01442                         READ_POD( &end, end );
01443 
01444 
01445                         // end = total play time (GetAudioStatus adds +150)
01446                         // pos = current play cursor
01447                         // start = start play cursor
01448                         play_len = end.min * 75 * 60 + ( end.sec * 75 ) + end.fr - 150;
01449                         play_len -= ( pos.min - start.min ) * 75 * 60 + ( pos.sec - start.sec ) * 75 + ( pos.fr - start.fr );
01450                         msf_time = ( pos.min << 16 ) + ( pos.sec << 8 ) + ( pos.fr );
01451 
01452 
01453                         // first play, then simulate pause
01454                         mscdex->StopAudio(drive_unit);
01455 
01456                         if( playing ) mscdex->PlayAudioMSF(drive_unit, msf_time, play_len);
01457                         if( pause ) mscdex->PlayAudioMSF(drive_unit, msf_time, 0);
01458                 }
01459 
01460                 mscdex->LoadState(stream);
01461         }
01462 }