/* * Copyright (C) 2002-2007 The DOSBox Team * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* $Id: dos_mscdex.cpp,v 1.50 2007/07/20 18:22:28 qbix79 Exp $ */ #include #include #include "regs.h" #include "callback.h" #include "dos_system.h" #include "dos_inc.h" #include "setup.h" #include "support.h" #include "bios.h" #include "cdrom.h" #define MSCDEX_LOG LOG(LOG_MISC,LOG_ERROR) //#define MSCDEX_LOG #define MSCDEX_VERSION_HIGH 2 #define MSCDEX_VERSION_LOW 23 #define MSCDEX_MAX_DRIVES 8 // Error Codes #define MSCDEX_ERROR_BAD_FORMAT 11 #define MSCDEX_ERROR_UNKNOWN_DRIVE 15 #define MSCDEX_ERROR_DRIVE_NOT_READY 21 // Request Status #define REQUEST_STATUS_DONE 0x0100 #define REQUEST_STATUS_ERROR 0x8000 // Use cdrom Interface int useCdromInterface = CDROM_USE_SDL; int forceCD = -1; static Bitu MSCDEX_Strategy_Handler(void); static Bitu MSCDEX_Interrupt_Handler(void); class DOS_DeviceHeader:public MemStruct { public: DOS_DeviceHeader(PhysPt ptr) { pt = ptr; }; void SetNextDeviceHeader (RealPt ptr) { sSave(sDeviceHeader,nextDeviceHeader,ptr); }; RealPt GetNextDeviceHeader (void) { return sGet(sDeviceHeader,nextDeviceHeader); }; void SetAttribute (Bit16u atr) { sSave(sDeviceHeader,devAttributes,atr); }; void SetDriveLetter (Bit8u letter) { sSave(sDeviceHeader,driveLetter,letter); }; void SetNumSubUnits (Bit8u num) { sSave(sDeviceHeader,numSubUnits,num); }; Bit8u GetNumSubUnits (void) { return sGet(sDeviceHeader,numSubUnits); }; void SetName (char const* _name) { MEM_BlockWrite(pt+offsetof(sDeviceHeader,name),_name,8); }; void SetInterrupt (Bit16u ofs) { sSave(sDeviceHeader,interrupt,ofs); }; void SetStrategy (Bit16u ofs) { sSave(sDeviceHeader,strategy,ofs); }; public: #ifdef _MSC_VER #pragma pack(1) #endif struct sDeviceHeader{ RealPt nextDeviceHeader; Bit16u devAttributes; Bit16u strategy; Bit16u interrupt; Bit8u name[8]; Bit16u wReserved; Bit8u driveLetter; Bit8u numSubUnits; } GCC_ATTRIBUTE(packed) TDeviceHeader; #ifdef _MSC_VER #pragma pack() #endif }; class CMscdex { public: CMscdex (void); ~CMscdex (void); Bit16u GetVersion (void) { return (MSCDEX_VERSION_HIGH<<8)+MSCDEX_VERSION_LOW; }; Bit16u GetNumDrives (void) { return numDrives; }; Bit16u GetFirstDrive (void) { return dinfo[0].drive; }; Bit8u GetSubUnit (Bit16u _drive); bool GetUPC (Bit8u subUnit, Bit8u& attr, char* upc); void InitNewMedia (Bit8u subUnit); bool PlayAudioSector (Bit8u subUnit, Bit32u start, Bit32u length); bool PlayAudioMSF (Bit8u subUnit, Bit32u start, Bit32u length); bool StopAudio (Bit8u subUnit); bool GetAudioStatus (Bit8u subUnit, bool& playing, bool& pause, TMSF& start, TMSF& end); bool GetSubChannelData (Bit8u subUnit, Bit8u& attr, Bit8u& track, Bit8u &index, TMSF& rel, TMSF& abs); int RemoveDrive (Bit16u _drive); int AddDrive (Bit16u _drive, char* physicalPath, Bit8u& subUnit); bool HasDrive (Bit16u drive); void ReplaceDrive (CDROM_Interface* newCdrom, Bit8u subUnit); void GetDrives (PhysPt data); void GetDriverInfo (PhysPt data); bool GetVolumeName (Bit8u subUnit, char* name); bool GetCopyrightName (Bit16u drive, PhysPt data); bool GetAbstractName (Bit16u drive, PhysPt data); bool GetDocumentationName(Bit16u drive, PhysPt data); bool GetDirectoryEntry (Bit16u drive, bool copyFlag, PhysPt pathname, PhysPt buffer, Bit16u& error); bool ReadVTOC (Bit16u drive, Bit16u volume, PhysPt data, Bit16u& error); bool ReadSectors (Bit16u drive, Bit32u sector, Bit16u num, PhysPt data); bool ReadSectors (Bit8u subUnit, bool raw, Bit32u sector, Bit16u num, PhysPt data); bool ReadSectorsMSF (Bit8u subUnit, bool raw, Bit32u sector, Bit16u num, PhysPt data); bool SendDriverRequest (Bit16u drive, PhysPt data); bool IsValidDrive (Bit16u drive); bool GetCDInfo (Bit8u subUnit, Bit8u& tr1, Bit8u& tr2, TMSF& leadOut); Bit32u GetVolumeSize (Bit8u subUnit); bool GetTrackInfo (Bit8u subUnit, Bit8u track, Bit8u& attr, TMSF& start); Bit16u GetStatusWord (Bit8u subUnit,Bit16u status); bool GetCurrentPos (Bit8u subUnit, TMSF& pos); Bit32u GetDeviceStatus (Bit8u subUnit); bool GetMediaStatus (Bit8u subUnit, Bit8u& status); bool LoadUnloadMedia (Bit8u subUnit, bool unload); bool ResumeAudio (Bit8u subUnit); bool GetMediaStatus (Bit8u subUnit, bool& media, bool& changed, bool& trayOpen); private: PhysPt GetDefaultBuffer (void); Bit16u numDrives; typedef struct SDriveInfo { Bit8u drive; // drive letter in dosbox Bit8u physDrive; // drive letter in system bool audioPlay; // audio playing active bool audioPaused; // audio playing paused Bit32u audioStart; // StartLoc for resume Bit32u audioEnd; // EndLoc for resume bool locked; // drive locked ? bool lastResult; // last operation success ? Bit32u volumeSize; // for media change } TDriveInfo; Bit16u defaultBufSeg; TDriveInfo dinfo[MSCDEX_MAX_DRIVES]; CDROM_Interface* cdrom[MSCDEX_MAX_DRIVES]; public: Bit16u rootDriverHeaderSeg; }; CMscdex::CMscdex(void) { numDrives = 0; rootDriverHeaderSeg = 0; defaultBufSeg = 0; memset(dinfo,0,sizeof(dinfo)); for (Bit32u i=0; i=MSCDEX_MAX_DRIVES) return 4; if (GetNumDrives()) { // Error check, driveletter have to be in a row if (dinfo[0].drive-1!=_drive && dinfo[numDrives-1].drive+1!=_drive) return 1; } // Set return type to ok int result = 0; // Get Mounttype and init needed cdrom interface switch (CDROM_GetMountType(physicalPath,forceCD)) { case 0x00: { LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting physical cdrom: %s" ,physicalPath); #if defined (WIN32) // Check OS OSVERSIONINFO osi; osi.dwOSVersionInfoSize = sizeof(osi); GetVersionEx(&osi); if ((osi.dwPlatformId==VER_PLATFORM_WIN32_NT) && (osi.dwMajorVersion>4)) { // only WIN NT/200/XP if (useCdromInterface==CDROM_USE_IOCTL) { cdrom[numDrives] = new CDROM_Interface_Ioctl(); LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface."); break; } } if (useCdromInterface==CDROM_USE_ASPI) { // all Wins - ASPI cdrom[numDrives] = new CDROM_Interface_Aspi(); LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: ASPI Interface."); break; } #endif #if defined (LINUX) || defined(OS2) // Always use IOCTL in Linux or OS/2 cdrom[numDrives] = new CDROM_Interface_Ioctl(); LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: IOCTL Interface."); #else // Default case windows and other oses cdrom[numDrives] = new CDROM_Interface_SDL(); LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: SDL Interface."); #endif } break; case 0x01: // iso cdrom interface LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting iso file as cdrom: %s", physicalPath); cdrom[numDrives] = new CDROM_Interface_Image((Bit8u)numDrives); break; case 0x02: // fake cdrom interface (directories) cdrom[numDrives] = new CDROM_Interface_Fake; LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: Mounting directory as cdrom: %s",physicalPath); LOG(LOG_MISC,LOG_NORMAL)("MSCDEX: You wont have full MSCDEX support !"); result = 5; break; default : // weird result return 6; }; if (!cdrom[numDrives]->SetDevice(physicalPath,forceCD)) { // delete cdrom[numDrives] ; mount seems to delete it return 3; } if (rootDriverHeaderSeg==0) { Bit16u driverSize = sizeof(DOS_DeviceHeader::sDeviceHeader) + 10; // 10 = Bytes for 3 callbacks // Create Device Header Bit16u seg = DOS_GetMemory(driverSize/16+((driverSize%16)>0)); DOS_DeviceHeader devHeader(PhysMake(seg,0)); devHeader.SetNextDeviceHeader (0xFFFFFFFF); devHeader.SetAttribute(0xc800); devHeader.SetDriveLetter (_drive+1); devHeader.SetNumSubUnits (1); devHeader.SetName ("MSCD001 "); //Link it in the device chain Bit32u start = dos_infoblock.GetDeviceChain(); Bit16u segm = (Bit16u)(start>>16); Bit16u offm = (Bit16u)(start&0xFFFF); while(start != 0xFFFFFFFF) { segm = (Bit16u)(start>>16); offm = (Bit16u)(start&0xFFFF); start = real_readd(segm,offm); } real_writed(segm,offm,seg<<16); // Create Callback Strategy Bit16u off = sizeof(DOS_DeviceHeader::sDeviceHeader); Bitu call_strategy=CALLBACK_Allocate(); CallBack_Handlers[call_strategy]=MSCDEX_Strategy_Handler; real_writeb(seg,off+0,(Bit8u)0xFE); //GRP 4 real_writeb(seg,off+1,(Bit8u)0x38); //Extra Callback instruction real_writew(seg,off+2,call_strategy); //The immediate word real_writeb(seg,off+4,(Bit8u)0xCB); //A RETF Instruction devHeader.SetStrategy(off); // Create Callback Interrupt off += 5; Bitu call_interrupt=CALLBACK_Allocate(); CallBack_Handlers[call_interrupt]=MSCDEX_Interrupt_Handler; real_writeb(seg,off+0,(Bit8u)0xFE); //GRP 4 real_writeb(seg,off+1,(Bit8u)0x38); //Extra Callback instruction real_writew(seg,off+2,call_interrupt); //The immediate word real_writeb(seg,off+4,(Bit8u)0xCB); //A RETF Instruction devHeader.SetInterrupt(off); rootDriverHeaderSeg = seg; } else if (GetNumDrives() == 0) { DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0)); Bit16u off = sizeof(DOS_DeviceHeader::sDeviceHeader); devHeader.SetDriveLetter(_drive+1); devHeader.SetStrategy(off); devHeader.SetInterrupt(off+5); } subUnit = (Bit8u)numDrives; // Set drive DOS_DeviceHeader devHeader(PhysMake(rootDriverHeaderSeg,0)); devHeader.SetNumSubUnits(devHeader.GetNumSubUnits()+1); if (dinfo[0].drive-1==_drive) { CDROM_Interface *_cdrom = cdrom[numDrives]; for (Bit16u i=GetNumDrives(); i>0; i--) { dinfo[i] = dinfo[i-1]; cdrom[i] = cdrom[i-1]; } cdrom[0] = _cdrom; dinfo[0].drive = (Bit8u)_drive; dinfo[0].physDrive = toupper(physicalPath[0]); } else { dinfo[numDrives].drive = (Bit8u)_drive; dinfo[numDrives].physDrive = toupper(physicalPath[0]); } numDrives++; // stop audio StopAudio(subUnit); return result; }; bool CMscdex::HasDrive(Bit16u drive) { return (GetSubUnit(drive) != 0xff); } void CMscdex::ReplaceDrive(CDROM_Interface* newCdrom, Bit8u subUnit) { delete cdrom[subUnit]; cdrom[subUnit] = newCdrom; StopAudio(subUnit); } PhysPt CMscdex::GetDefaultBuffer(void) { if (defaultBufSeg==0) { Bit16u size = 128; //Size in block is size in pages ? defaultBufSeg = DOS_GetMemory(size); }; return PhysMake(defaultBufSeg,0); }; void CMscdex::GetDriverInfo (PhysPt data) { for (Bit16u i=0; i=numDrives) return false; int tr1i,tr2i; // Assume Media change cdrom[subUnit]->InitNewMedia(); dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioTracks(tr1i,tr2i,leadOut); if (!dinfo[subUnit].lastResult) { tr1 = tr2 = 0; memset(&leadOut,0,sizeof(leadOut)); } else { tr1 = (Bit8u) tr1i; tr2 = (Bit8u) tr2i; } return dinfo[subUnit].lastResult; } bool CMscdex::GetTrackInfo(Bit8u subUnit, Bit8u track, Bit8u& attr, TMSF& start) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioTrackInfo(track,start,attr); if (!dinfo[subUnit].lastResult) { attr = 0; memset(&start,0,sizeof(start)); }; return dinfo[subUnit].lastResult; }; bool CMscdex::PlayAudioSector(Bit8u subUnit, Bit32u sector, Bit32u length) { if (subUnit>=numDrives) return false; // If value from last stop is used, this is meant as a resume // better start using resume command if (dinfo[subUnit].audioPaused && (sector==dinfo[subUnit].audioStart)) { dinfo[subUnit].lastResult = cdrom[subUnit]->PauseAudio(true); } else dinfo[subUnit].lastResult = cdrom[subUnit]->PlayAudioSector(sector,length); if (dinfo[subUnit].lastResult) { dinfo[subUnit].audioPlay = true; dinfo[subUnit].audioPaused = false; dinfo[subUnit].audioStart = sector; dinfo[subUnit].audioEnd = length; }; return dinfo[subUnit].lastResult; }; bool CMscdex::PlayAudioMSF(Bit8u subUnit, Bit32u start, Bit32u length) { if (subUnit>=numDrives) return false; Bit8u min = (Bit8u)(start>>16) & 0xFF; Bit8u sec = (Bit8u)(start>> 8) & 0xFF; Bit8u fr = (Bit8u)(start>> 0) & 0xFF; Bit32u sector = min*60*75+sec*75+fr - 150; return dinfo[subUnit].lastResult = PlayAudioSector(subUnit,sector,length); }; bool CMscdex::GetSubChannelData(Bit8u subUnit, Bit8u& attr, Bit8u& track, Bit8u &index, TMSF& rel, TMSF& abs) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioSub(attr,track,index,rel,abs); if (!dinfo[subUnit].lastResult) { attr = track = index = 0; memset(&rel,0,sizeof(rel)); memset(&abs,0,sizeof(abs)); }; return dinfo[subUnit].lastResult; }; bool CMscdex::GetAudioStatus(Bit8u subUnit, bool& playing, bool& pause, TMSF& start, TMSF& end) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->GetAudioStatus(playing,pause); if (dinfo[subUnit].lastResult) { // Start Bit32u addr = dinfo[subUnit].audioStart + 150; start.fr = (Bit8u)(addr%75); addr/=75; start.sec = (Bit8u)(addr%60); start.min = (Bit8u)(addr/60); // End addr = dinfo[subUnit].audioEnd + 150; end.fr = (Bit8u)(addr%75); addr/=75; end.sec = (Bit8u)(addr%60); end.min = (Bit8u)(addr/60); } else { playing = false; pause = false; memset(&start,0,sizeof(start)); memset(&end,0,sizeof(end)); }; return dinfo[subUnit].lastResult; }; bool CMscdex::StopAudio(Bit8u subUnit) { if (subUnit>=numDrives) return false; if (dinfo[subUnit].audioPlay) dinfo[subUnit].lastResult = cdrom[subUnit]->PauseAudio(false); else dinfo[subUnit].lastResult = cdrom[subUnit]->StopAudio(); if (dinfo[subUnit].lastResult) { if (dinfo[subUnit].audioPlay) { TMSF pos; GetCurrentPos(subUnit,pos); dinfo[subUnit].audioStart = pos.min*60*75+pos.sec*75+pos.fr - 150; dinfo[subUnit].audioPaused = true; } else { dinfo[subUnit].audioPaused = false; dinfo[subUnit].audioStart = 0; dinfo[subUnit].audioEnd = 0; }; dinfo[subUnit].audioPlay = false; }; return dinfo[subUnit].lastResult; }; bool CMscdex::ResumeAudio(Bit8u subUnit) { if (subUnit>=numDrives) return false; return dinfo[subUnit].lastResult = PlayAudioSector(subUnit,dinfo[subUnit].audioStart,dinfo[subUnit].audioEnd); }; Bit32u CMscdex::GetVolumeSize(Bit8u subUnit) { if (subUnit>=numDrives) return false; Bit8u tr1,tr2; TMSF leadOut; dinfo[subUnit].lastResult = GetCDInfo(subUnit,tr1,tr2,leadOut); if (dinfo[subUnit].lastResult) return (leadOut.min*60*75)+(leadOut.sec*75)+leadOut.fr; return 0; }; bool CMscdex::ReadVTOC(Bit16u drive, Bit16u volume, PhysPt data, Bit16u& error) { if (!ReadSectors(GetSubUnit(drive),false,16+volume,1,data)) { error=MSCDEX_ERROR_DRIVE_NOT_READY; return false; } char id[5]; MEM_BlockRead(data + 1, id, 5); if (strncmp("CD001",id, 5)!=0) { error = MSCDEX_ERROR_BAD_FORMAT; return false; } Bit8u type = mem_readb(data); error = (type == 1) ? 1 : (type == 0xFF) ? 0xFF : 0; return true; }; bool CMscdex::GetVolumeName(Bit8u subUnit, char* data) { if (subUnit>=numDrives) return false; Bit16u drive = dinfo[subUnit].drive; Bit16u error,seg,size = 128; bool success = false; if (DOS_AllocateMemory(&seg,&size)) { PhysPt ptoc = PhysMake(seg,0); success = ReadVTOC(drive,0x00,ptoc,error); if (success) { MEM_StrCopy(ptoc+40,data,31); data[31] = 0; rtrim(data); }; DOS_FreeMemory(seg); } else { LOG(LOG_MISC,LOG_ERROR)("MSCDEX buffer allocation failed."); }; return success; }; bool CMscdex::GetCopyrightName(Bit16u drive, PhysPt data) { Bit16u error,seg,size = 128; bool success = false; if (DOS_AllocateMemory(&seg,&size)) { PhysPt ptoc = PhysMake(seg,0); success = ReadVTOC(drive,0x00,ptoc,error); if (success) { MEM_BlockCopy(data,ptoc+702,37); mem_writeb(data+37,0); }; DOS_FreeMemory(seg); } else { LOG(LOG_MISC,LOG_ERROR)("MSCDEX buffer allocation failed."); }; return success; }; bool CMscdex::GetAbstractName(Bit16u drive, PhysPt data) { Bit16u error,seg,size = 128; bool success = false; if (DOS_AllocateMemory(&seg,&size)) { PhysPt ptoc = PhysMake(seg,0); success = ReadVTOC(drive,0x00,ptoc,error); if (success) { MEM_BlockCopy(data,ptoc+739,37); mem_writeb(data+37,0); }; DOS_FreeMemory(seg); } else { LOG(LOG_MISC,LOG_ERROR)("MSCDEX buffer allocation failed."); }; return success; }; bool CMscdex::GetDocumentationName(Bit16u drive, PhysPt data) { Bit16u error,seg,size = 128; bool success = false; if (DOS_AllocateMemory(&seg,&size)) { PhysPt ptoc = PhysMake(seg,0); success = ReadVTOC(drive,0x00,ptoc,error); if (success) { MEM_BlockCopy(data,ptoc+776,37); mem_writeb(data+37,0); }; DOS_FreeMemory(seg); } else { LOG(LOG_MISC,LOG_ERROR)("MSCDEX buffer allocation failed."); }; return success; }; bool CMscdex::GetUPC(Bit8u subUnit, Bit8u& attr, char* upc) { if (subUnit>=numDrives) return false; return dinfo[subUnit].lastResult = cdrom[subUnit]->GetUPC(attr,&upc[0]); }; bool CMscdex::ReadSectors(Bit8u subUnit, bool raw, Bit32u sector, Bit16u num, PhysPt data) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->ReadSectors(data,raw,sector,num); return dinfo[subUnit].lastResult; }; bool CMscdex::ReadSectorsMSF(Bit8u subUnit, bool raw, Bit32u start, Bit16u num, PhysPt data) { if (subUnit>=numDrives) return false; Bit8u min = (Bit8u)(start>>16) & 0xFF; Bit8u sec = (Bit8u)(start>> 8) & 0xFF; Bit8u fr = (Bit8u)(start>> 0) & 0xFF; Bit32u sector = min*60*75+sec*75+fr - 150; // TODO: Check, if num has to be converted too ?! return ReadSectors(subUnit,raw,sector,num,data); }; bool CMscdex::ReadSectors(Bit16u drive, Bit32u sector, Bit16u num, PhysPt data) // Called from INT 2F { return ReadSectors(GetSubUnit(drive),false,sector,num,data); }; bool CMscdex::GetDirectoryEntry(Bit16u drive, bool copyFlag, PhysPt pathname, PhysPt buffer, Bit16u& error) { char volumeID[6] = {0}; char searchName[256]; char entryName[256]; bool foundComplete = false; bool foundName; char* useName = 0; Bitu entryLength,nameLength; // clear error error = 0; MEM_StrCopy(pathname+1,searchName,mem_readb(pathname)); upcase(searchName); char* searchPos = searchName; //strip of tailing . (XCOM APOCALIPSE) int searchlen = strlen(searchName); if(searchlen > 1 && strcmp(searchName,"..")) if(searchName[searchlen-1] =='.') searchName[searchlen-1] = 0; //LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Get DirEntry : Find : %s",searchName); // read vtoc PhysPt defBuffer = GetDefaultBuffer(); if (!ReadSectors(GetSubUnit(drive),false,16,1,defBuffer)) return false; // TODO: has to be iso 9960 MEM_StrCopy(defBuffer+1,volumeID,5); volumeID[5] = 0; bool iso = (strcmp("CD001",volumeID)==0); if (!iso) E_Exit("MSCDEX: GetDirEntry: Not an ISO 9960 CD."); // get directory position Bitu dirEntrySector = mem_readd(defBuffer+156+2); Bits dirSize = mem_readd(defBuffer+156+10); Bitu index; while (dirSize>0) { index = 0; if (!ReadSectors(GetSubUnit(drive),false,dirEntrySector,1,defBuffer)) return false; // Get string part foundName = false; if (searchPos) { useName = searchPos; searchPos = strchr(searchPos,'\\'); } if (searchPos) { *searchPos = 0; searchPos++; } else foundComplete = true; do { entryLength = mem_readb(defBuffer+index); if (entryLength==0) break; nameLength = mem_readb(defBuffer+index+32); MEM_StrCopy(defBuffer+index+33,entryName,nameLength); if (strcmp(entryName,useName)==0) { //LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Get DirEntry : Found : %s",useName); foundName = true; break; } /* Xcom Apocalipse searches for MUSIC. and expects to find MUSIC;1 * All Files on the CDROM are of the kind blah;1 */ char* longername = strchr(entryName,';'); if(longername) { *longername = 0; if (strcmp(entryName,useName)==0) { //LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Get DirEntry : Found : %s",useName); foundName = true; break; } } index += entryLength; } while (index+33<=2048); if (foundName) { if (foundComplete) { if (copyFlag) { LOG(LOG_MISC,LOG_WARN)("MSCDEX: GetDirEntry: Copyflag structure not entirely accurate maybe"); Bit8u readBuf[256]; Bit8u writeBuf[256]; if (entryLength > 256) return false; MEM_BlockRead( defBuffer+index, readBuf, entryLength ); writeBuf[0] = readBuf[1]; // 00h BYTE length of XAR in Logical Block Numbers memcpy( &writeBuf[1], &readBuf[0x2], 4); // 01h DWORD Logical Block Number of file start writeBuf[5] = 0;writeBuf[6] = 8; // 05h WORD size of disk in logical blocks memcpy( &writeBuf[7], &readBuf[0xa], 4); // 07h DWORD file length in bytes memcpy( &writeBuf[0xb], &readBuf[0x12], 7); // 0bh DWORD date and time writeBuf[0x12] = readBuf[0x19]; // 12h BYTE bit flags writeBuf[0x13] = readBuf[0x1a]; // 13h BYTE interleave size writeBuf[0x14] = readBuf[0x1b]; // 14h BYTE interleave skip factor memcpy( &writeBuf[0x15], &readBuf[0x1c], 2); // 15h WORD volume set sequence number writeBuf[0x17] = readBuf[0x20]; memcpy( &writeBuf[0x18], &readBuf[21], readBuf[0x20] <= 38 ? readBuf[0x20] : 38 ); MEM_BlockWrite( buffer, writeBuf, 0x18 + 40 ); } else { // Direct copy MEM_BlockCopy(buffer,defBuffer+index,entryLength); } error = iso ? 1:0; return true; } // change directory dirEntrySector = mem_readd(defBuffer+index+2); dirSize = mem_readd(defBuffer+index+10); } else { // continue search in next sector dirSize -= 2048; dirEntrySector++; } }; error = 2; // file not found return false; // not found }; bool CMscdex::GetCurrentPos(Bit8u subUnit, TMSF& pos) { if (subUnit>=numDrives) return false; TMSF rel; Bit8u attr,track,index; dinfo[subUnit].lastResult = GetSubChannelData(subUnit, attr, track, index, rel, pos); if (!dinfo[subUnit].lastResult) memset(&pos,0,sizeof(pos)); return dinfo[subUnit].lastResult; }; bool CMscdex::GetMediaStatus(Bit8u subUnit, bool& media, bool& changed, bool& trayOpen) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->GetMediaTrayStatus(media,changed,trayOpen); return dinfo[subUnit].lastResult; }; Bit32u CMscdex::GetDeviceStatus(Bit8u subUnit) { if (subUnit>=numDrives) return false; bool media,changed,trayOpen; dinfo[subUnit].lastResult = GetMediaStatus(subUnit,media,changed,trayOpen); Bit32u status = (trayOpen << 0) | // Drive is open ? (dinfo[subUnit].locked << 1) | // Drive is locked ? (1<<2) | // raw + cooked sectors (1<<4) | // Can read sudio (1<<9) | // Red book & HSG ((!media) << 11); // Drive is empty ? return status; }; bool CMscdex::GetMediaStatus(Bit8u subUnit, Bit8u& status) { if (subUnit>=numDrives) return false; /* bool media,changed,open,result; result = GetMediaStatus(subUnit,media,changed,open); status = changed ? 0xFF : 0x01; return result; */ status = getSwapRequest() ? 0xFF : 0x01; return true; }; bool CMscdex::LoadUnloadMedia(Bit8u subUnit, bool unload) { if (subUnit>=numDrives) return false; dinfo[subUnit].lastResult = cdrom[subUnit]->LoadUnloadMedia(unload); return dinfo[subUnit].lastResult; }; bool CMscdex::SendDriverRequest(Bit16u drive, PhysPt data) { Bit8u subUnit = GetSubUnit(drive); if (subUnit>=numDrives) return false; // Get SubUnit mem_writeb(data+1,subUnit); // Call Strategy / Interrupt MSCDEX_Strategy_Handler(); MSCDEX_Interrupt_Handler(); return true; }; Bit16u CMscdex::GetStatusWord(Bit8u subUnit,Bit16u status) { if (subUnit>=numDrives) return REQUEST_STATUS_ERROR | 0x02; // error : Drive not ready if (dinfo[subUnit].lastResult) status |= REQUEST_STATUS_DONE; // ok else status |= REQUEST_STATUS_ERROR; if (dinfo[subUnit].audioPlay) { // Check if audio is still playing.... TMSF start,end; bool playing,pause; if (GetAudioStatus(subUnit,playing,pause,start,end)) { dinfo[subUnit].audioPlay = playing; } else dinfo[subUnit].audioPlay = false; status |= (dinfo[subUnit].audioPlay<<9); } dinfo[subUnit].lastResult = true; return status; }; void CMscdex::InitNewMedia(Bit8u subUnit) { if (subUnitInitNewMedia(); } }; static CMscdex* mscdex = 0; static PhysPt curReqheaderPtr = 0; static Bit16u MSCDEX_IOCTL_Input(PhysPt buffer,Bit8u drive_unit) { Bitu ioctl_fct = mem_readb(buffer); MSCDEX_LOG("MSCDEX: IOCTL INPUT Subfunction %02X",ioctl_fct); switch (ioctl_fct) { case 0x00 : /* Get Device Header address */ mem_writed(buffer+1,RealMake(mscdex->rootDriverHeaderSeg,0)); break; case 0x01 :{/* Get current position */ TMSF pos; mscdex->GetCurrentPos(drive_unit,pos); Bit8u addr_mode = mem_readb(buffer+1); if (addr_mode==0) { // HSG Bit32u frames=MSF_TO_FRAMES(pos.min, pos.sec, pos.fr); if (frames<150) MSCDEX_LOG("MSCDEX: Get position: invalid position %d:%d:%d", pos.min, pos.sec, pos.fr); else frames-=150; mem_writed(buffer+2,frames); } else if (addr_mode==1) { // Red book mem_writeb(buffer+2,pos.fr); mem_writeb(buffer+3,pos.sec); mem_writeb(buffer+4,pos.min); mem_writeb(buffer+5,0x00); } else { MSCDEX_LOG("MSCDEX: Get position: invalid address mode %x",addr_mode); return 0x03; // invalid function } }break; case 0x06 : /* Get Device status */ mem_writed(buffer+1,mscdex->GetDeviceStatus(drive_unit)); break; case 0x07 : /* Get sector size */ if (mem_readb(buffer+1)==0) mem_writed(buffer+2,2048); else if (mem_readb(buffer+1)==1) mem_writed(buffer+2,2352); else return 0x03; // invalid function break; case 0x08 : /* Get size of current volume */ mem_writed(buffer+1,mscdex->GetVolumeSize(drive_unit)); break; case 0x09 : /* Media change ? */ Bit8u status; if (!mscdex->GetMediaStatus(drive_unit,status)) { status = 0; // state unknown } mem_writeb(buffer+1,status); break; case 0x0A : /* Get Audio Disk info */ Bit8u tr1,tr2; TMSF leadOut; mscdex->GetCDInfo(drive_unit,tr1,tr2,leadOut); mem_writeb(buffer+1,tr1); mem_writeb(buffer+2,tr2); mem_writeb(buffer+3,leadOut.fr); mem_writeb(buffer+4,leadOut.sec); mem_writeb(buffer+5,leadOut.min); mem_writeb(buffer+6,0x00); break; case 0x0B :{/* Audio Track Info */ Bit8u attr; TMSF start; Bit8u track = mem_readb(buffer+1); mscdex->GetTrackInfo(drive_unit,track,attr,start); mem_writeb(buffer+2,start.fr); mem_writeb(buffer+3,start.sec); mem_writeb(buffer+4,start.min); mem_writeb(buffer+5,0x00); mem_writeb(buffer+6,attr); break; }; case 0x0C :{/* Get Audio Sub Channel data */ Bit8u attr,track,index; TMSF abs,rel; mscdex->GetSubChannelData(drive_unit,attr,track,index,rel,abs); mem_writeb(buffer+1,attr); mem_writeb(buffer+2,track); mem_writeb(buffer+3,index); mem_writeb(buffer+4,rel.min); mem_writeb(buffer+5,rel.sec); mem_writeb(buffer+6,rel.fr); mem_writeb(buffer+7,0x00); mem_writeb(buffer+8,abs.min); mem_writeb(buffer+9,abs.sec); mem_writeb(buffer+10,abs.fr); break; }; case 0x0E :{ /* Get UPC */ Bit8u attr; char upc[8]; mscdex->GetUPC(drive_unit,attr,&upc[0]); mem_writeb(buffer+1,attr); for (int i=0; i<7; i++) mem_writeb(buffer+2+i,upc[i]); mem_writeb(buffer+9,0x00); break; }; case 0x0F :{ /* Get Audio Status */ bool playing,pause; TMSF resStart,resEnd; mscdex->GetAudioStatus(drive_unit,playing,pause,resStart,resEnd); mem_writeb(buffer+1,pause); mem_writeb(buffer+3,resStart.min); mem_writeb(buffer+4,resStart.sec); mem_writeb(buffer+5,resStart.fr); mem_writeb(buffer+6,0x00); mem_writeb(buffer+7,resEnd.min); mem_writeb(buffer+8,resEnd.sec); mem_writeb(buffer+9,resEnd.fr); mem_writeb(buffer+10,0x00); break; }; default : LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unsupported IOCTL INPUT Subfunction %02X",ioctl_fct); return 0x03; // invalid function } return 0x00; // success } static Bit16u MSCDEX_IOCTL_Optput(PhysPt buffer,Bit8u drive_unit) { Bitu ioctl_fct = mem_readb(buffer); // MSCDEX_LOG("MSCDEX: IOCTL OUTPUT Subfunction %02X",ioctl_fct); switch (ioctl_fct) { case 0x00 : // Unload /eject media if (!mscdex->LoadUnloadMedia(drive_unit,true)) return 0x02; break; case 0x03: //Audio Channel control MSCDEX_LOG("MSCDEX: Audio Channel Control used. Not handled. Faking succes!"); case 0x01 : // (un)Lock door // do nothing -> report as success break; case 0x02 : // Reset Drive LOG(LOG_MISC,LOG_WARN)("cdromDrive reset"); if (!mscdex->StopAudio(drive_unit)) return 0x02; break; case 0x05 : // load media if (!mscdex->LoadUnloadMedia(drive_unit,false)) return 0x02; default : LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unsupported IOCTL OUTPUT Subfunction %02X",ioctl_fct); return 0x03; // invalid function } return 0x00; // success } static Bitu MSCDEX_Strategy_Handler(void) { curReqheaderPtr = PhysMake(SegValue(es),reg_bx); // MSCDEX_LOG("MSCDEX: Device Strategy Routine called, request header at %x",curReqheaderPtr); return CBRET_NONE; } static Bitu MSCDEX_Interrupt_Handler(void) { if (curReqheaderPtr==0) { MSCDEX_LOG("MSCDEX: invalid call to interrupt handler"); return CBRET_NONE; } Bit8u subUnit = mem_readb(curReqheaderPtr+1); Bit8u funcNr = mem_readb(curReqheaderPtr+2); Bit16u errcode = 0; PhysPt buffer = 0; MSCDEX_LOG("MSCDEX: Driver Function %02X",funcNr); if ((funcNr==0x03) || (funcNr==0x0c) || (funcNr==0x80) || (funcNr==0x82)) { buffer = PhysMake(mem_readw(curReqheaderPtr+0x10),mem_readw(curReqheaderPtr+0x0E)); } switch (funcNr) { case 0x03 : { /* IOCTL INPUT */ Bit16u error=MSCDEX_IOCTL_Input(buffer,subUnit); if (error) errcode = error; break; }; case 0x0C : { /* IOCTL OUTPUT */ Bit16u error=MSCDEX_IOCTL_Optput(buffer,subUnit); if (error) errcode = error; break; }; case 0x0D : // device open case 0x0E : // device close - dont care :) break; case 0x80 : // Read long case 0x82 : { // Read long prefetch -> both the same here :) Bit32u start = mem_readd(curReqheaderPtr+0x14); Bit16u len = mem_readw(curReqheaderPtr+0x12); bool raw = (mem_readb(curReqheaderPtr+0x18)==1); if (mem_readb(curReqheaderPtr+0x0D)==0x00) // HSG mscdex->ReadSectors(subUnit,raw,start,len,buffer); else mscdex->ReadSectorsMSF(subUnit,raw,start,len,buffer); break; }; case 0x83 : // Seek - dont care :) break; case 0x84 : { /* Play Audio Sectors */ Bit32u start = mem_readd(curReqheaderPtr+0x0E); Bit32u len = mem_readd(curReqheaderPtr+0x12); if (mem_readb(curReqheaderPtr+0x0D)==0x00) // HSG mscdex->PlayAudioSector(subUnit,start,len); else // RED BOOK mscdex->PlayAudioMSF(subUnit,start,len); break; }; case 0x85 : /* Stop Audio */ mscdex->StopAudio(subUnit); break; case 0x88 : /* Resume Audio */ mscdex->ResumeAudio(subUnit); break; default : LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unsupported Driver Request %02X",funcNr); break; }; // Set Statusword mem_writew(curReqheaderPtr+3,mscdex->GetStatusWord(subUnit,errcode)); MSCDEX_LOG("MSCDEX: Status : %04X",mem_readw(curReqheaderPtr+3)); return CBRET_NONE; } static bool MSCDEX_Handler(void) { if(reg_ah == 0x11) { if(reg_al == 0x00) { reg_al = 0xff; return true; } else { LOG(LOG_MISC,LOG_ERROR)("NETWORK REDIRECTOR USED!!!"); reg_ax = 0x49;//NETWERK SOFTWARE NOT INSTALLED CALLBACK_SCF(true); return true; } } if (reg_ah!=0x15) return false; // not handled here, continue chain PhysPt data = PhysMake(SegValue(es),reg_bx); MSCDEX_LOG("MSCDEX: INT 2F %04X BX= %04X CX=%04X",reg_ax,reg_bx,reg_cx); switch (reg_ax) { case 0x1500: /* Install check */ reg_bx = mscdex->GetNumDrives(); if (reg_bx>0) reg_cx = mscdex->GetFirstDrive(); return true; case 0x1501: /* Get cdrom driver info */ mscdex->GetDriverInfo(data); return true; case 0x1502: /* Get Copyright filename */ if (mscdex->GetCopyrightName(reg_cx,data)) { CALLBACK_SCF(false); } else { reg_al = MSCDEX_ERROR_UNKNOWN_DRIVE; CALLBACK_SCF(true); }; return true; case 0x1503: /* Get Abstract filename */ if (mscdex->GetAbstractName(reg_cx,data)) { CALLBACK_SCF(false); } else { reg_al = MSCDEX_ERROR_UNKNOWN_DRIVE; CALLBACK_SCF(true); }; return true; case 0x1504: /* Get Documentation filename */ if (mscdex->GetDocumentationName(reg_cx,data)) { CALLBACK_SCF(false); } else { reg_al = MSCDEX_ERROR_UNKNOWN_DRIVE; CALLBACK_SCF(true); }; return true; case 0x1505: { // read vtoc Bit16u error = 0; if (mscdex->ReadVTOC(reg_cx,reg_dx,data,error)) { CALLBACK_SCF(false); } else { reg_ax = error; CALLBACK_SCF(true); }; }; return true; case 0x1508: { // read sectors Bit32u sector = (reg_si<<16)+reg_di; if (mscdex->ReadSectors(reg_cx,sector,reg_dx,data)) { CALLBACK_SCF(false); } else { reg_al = MSCDEX_ERROR_UNKNOWN_DRIVE; CALLBACK_SCF(true); }; return true; }; case 0x1509: // write sectors - not supported reg_al = MSCDEX_ERROR_DRIVE_NOT_READY; CALLBACK_SCF(true); return true; case 0x150B: /* Valid CDROM drive ? */ reg_ax = mscdex->IsValidDrive(reg_cx); reg_bx = 0xADAD; return true; case 0x150C: /* Get MSCDEX Version */ reg_bx = mscdex->GetVersion(); return true; case 0x150D: /* Get drives */ mscdex->GetDrives(data); return true; case 0x150F: { // Get directory entry Bit16u error; bool success = mscdex->GetDirectoryEntry(reg_cl,reg_ch&1,data,PhysMake(reg_si,reg_di),error); reg_ax = error; CALLBACK_SCF(!success); }; return true; case 0x1510: /* Device driver request */ if (mscdex->SendDriverRequest(reg_cx,data)) { CALLBACK_SCF(false); } else { reg_ax = 0x0f; // invalid drive CALLBACK_SCF(true); } return true; }; LOG(LOG_MISC,LOG_ERROR)("MSCDEX: Unknwon call : %04X",reg_ax); return true; }; class device_MSCDEX : public DOS_Device { public: device_MSCDEX() { SetName("MSCD001"); } bool Read (Bit8u * /*data*/,Bit16u * /*size*/) { return false;} bool Write(Bit8u * /*data*/,Bit16u * /*size*/) { LOG(LOG_ALL,LOG_NORMAL)("Write to mscdex device"); return false; } bool Seek(Bit32u * /*pos*/,Bit32u /*type*/){return false;} bool Close(){return false;} Bit16u GetInformation(void){return 0xc880;} bool ReadFromControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode); bool WriteToControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode); private: // Bit8u cache; }; bool device_MSCDEX::ReadFromControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode) { if (MSCDEX_IOCTL_Input(bufptr,0)==0) { *retcode=size; return true; } return false; } bool device_MSCDEX::WriteToControlChannel(PhysPt bufptr,Bit16u size,Bit16u * retcode) { if (MSCDEX_IOCTL_Optput(bufptr,0)==0) { *retcode=size; return true; } return false; } int MSCDEX_AddDrive(char driveLetter, const char* physicalPath, Bit8u& subUnit) { int result = mscdex->AddDrive(driveLetter-'A',(char*)physicalPath,subUnit); return result; }; int MSCDEX_RemoveDrive(char driveLetter) { if(!mscdex) return 0; return mscdex->RemoveDrive(driveLetter-'A'); }; bool MSCDEX_HasDrive(char driveLetter) { return mscdex->HasDrive(driveLetter-'A'); }; void MSCDEX_ReplaceDrive(CDROM_Interface* cdrom, Bit8u subUnit) { mscdex->ReplaceDrive(cdrom, subUnit); }; bool MSCDEX_GetVolumeName(Bit8u subUnit, char* name) { return mscdex->GetVolumeName(subUnit,name); }; bool MSCDEX_HasMediaChanged(Bit8u subUnit) { static TMSF leadOut[MSCDEX_MAX_DRIVES]; TMSF leadnew; Bit8u tr1,tr2; if (mscdex->GetCDInfo(subUnit,tr1,tr2,leadnew)) { bool changed = (leadOut[subUnit].min!=leadnew.min) || (leadOut[subUnit].sec!=leadnew.sec) || (leadOut[subUnit].fr!=leadnew.fr); if (changed) { leadOut[subUnit].min = leadnew.min; leadOut[subUnit].sec = leadnew.sec; leadOut[subUnit].fr = leadnew.fr; mscdex->InitNewMedia(subUnit); } return changed; }; if (subUnitAddDestroyFunction(&MSCDEX_ShutDown); /* Register the mscdex device */ DOS_Device * newdev = new device_MSCDEX(); DOS_AddDevice(newdev); curReqheaderPtr = 0; /* Add Multiplexer */ DOS_AddMultiplexHandler(MSCDEX_Handler); /* Create MSCDEX */ mscdex = new CMscdex; };