/* * Copyright (C) 2002-2008 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.cpp,v 1.110 2008-04-03 18:28:19 c2woody Exp $ */ #include #include #include #include #include "dosbox.h" #include "bios.h" #include "mem.h" #include "callback.h" #include "regs.h" #include "dos_inc.h" #include "setup.h" #include "support.h" #include "serialport.h" DOS_Block dos; DOS_InfoBlock dos_infoblock; #define DOS_COPYBUFSIZE 0x10000 Bit8u dos_copybuf[DOS_COPYBUFSIZE]; void DOS_SetError(Bit16u code) { dos.errorcode=code; } #define DATA_TRANSFERS_TAKE_CYCLES 1 #ifdef DATA_TRANSFERS_TAKE_CYCLES #include "cpu.h" static inline void modify_cycles(Bits value) { if((4*value+5) < CPU_Cycles) CPU_Cycles -= 4*value; else CPU_Cycles = 5; } #else static inline void modify_cycles(Bits /* value */) { return; } #endif #define DOSNAMEBUF 256 static Bitu DOS_21Handler(void) { if (((reg_ah != 0x50) && (reg_ah != 0x51) && (reg_ah != 0x62) && (reg_ah != 0x64)) && (reg_ah<0x6c)) { DOS_PSP psp(dos.psp()); psp.SetStack(RealMake(SegValue(ss),reg_sp-18)); } char name1[DOSNAMEBUF+1]; char name2[DOSNAMEBUF+1]; switch (reg_ah) { case 0x01: /* Read character from STDIN, with echo */ { Bit8u c;Bit16u n=1; dos.echo=true; DOS_ReadFile(STDIN,&c,&n); reg_al=c; dos.echo=false; } break; case 0x02: /* Write character to STDOUT */ { Bit8u c=reg_dl;Bit16u n=1; DOS_WriteFile(STDOUT,&c,&n); } break; case 0x03: /* Read character from STDAUX */ { Bit16u port = real_readw(0x40,0); if(port!=0 && serialports[0]) { Bit8u status; // RTS/DTR on IO_WriteB(port+4,0x3); serialports[0]->Getchar(®_al, &status, true, 0xFFFFFFFF); } } break; case 0x04: /* Write Character to STDAUX */ { Bit16u port = real_readw(0x40,0); if(port!=0 && serialports[0]) { // RTS/DTR on IO_WriteB(port+4,0x3); serialports[0]->Putchar(reg_dl,true,true, 0xFFFFFFFF); // RTS off IO_WriteB(port+4,0x1); } } break; case 0x05: /* Write Character to PRINTER */ E_Exit("DOS:Unhandled call %02X",reg_ah); break; case 0x06: /* Direct Console Output / Input */ switch (reg_dl) { case 0xFF: /* Input */ { //TODO Make this better according to standards if (!DOS_GetSTDINStatus()) { reg_al=0; CALLBACK_SZF(true); break; } Bit8u c;Bit16u n=1; DOS_ReadFile(STDIN,&c,&n); reg_al=c; CALLBACK_SZF(false); break; } default: { Bit8u c = reg_dl;Bit16u n = 1; DOS_WriteFile(STDOUT,&c,&n); reg_al = reg_dl; } break; }; break; case 0x07: /* Character Input, without echo */ { Bit8u c;Bit16u n=1; DOS_ReadFile (STDIN,&c,&n); reg_al=c; break; }; case 0x08: /* Direct Character Input, without echo (checks for breaks officially :)*/ { Bit8u c;Bit16u n=1; DOS_ReadFile (STDIN,&c,&n); reg_al=c; break; }; case 0x09: /* Write string to STDOUT */ { Bit8u c;Bit16u n=1; PhysPt buf=SegPhys(ds)+reg_dx; while ((c=mem_readb(buf++))!='$') { DOS_WriteFile(STDOUT,&c,&n); } } break; case 0x0a: /* Buffered Input */ { //TODO ADD Break checkin in STDIN but can't care that much for it PhysPt data=SegPhys(ds)+reg_dx; Bit8u free=mem_readb(data); Bit8u read=0;Bit8u c;Bit16u n=1; if (!free) break; for(;;) { DOS_ReadFile(STDIN,&c,&n); if (c == 8) { // Backspace if (read) { //Something to backspace. // STDOUT treats backspace as non-destructive. DOS_WriteFile(STDOUT,&c,&n); c = ' '; DOS_WriteFile(STDOUT,&c,&n); c = 8; DOS_WriteFile(STDOUT,&c,&n); --read; } continue; } if (read >= free) { // Keyboard buffer full Bit8u bell = 7; DOS_WriteFile(STDOUT, &bell, &n); continue; } DOS_WriteFile(STDOUT,&c,&n); mem_writeb(data+read+2,c); if (c==13) break; read++; }; mem_writeb(data+1,read); break; }; case 0x0b: /* Get STDIN Status */ if (!DOS_GetSTDINStatus()) {reg_al=0x00;} else {reg_al=0xFF;} break; case 0x0c: /* Flush Buffer and read STDIN call */ { /* flush STDIN-buffer */ Bit8u c;Bit16u n; while (DOS_GetSTDINStatus()) { n=1; DOS_ReadFile(STDIN,&c,&n); } switch (reg_al) { case 0x1: case 0x6: case 0x7: case 0x8: case 0xa: { Bit8u oldah=reg_ah; reg_ah=reg_al; DOS_21Handler(); reg_ah=oldah; } break; default: // LOG_ERROR("DOS:0C:Illegal Flush STDIN Buffer call %d",reg_al); reg_al=0; break; } } break; //TODO Find out the values for when reg_al!=0 //TODO Hope this doesn't do anything special case 0x0d: /* Disk Reset */ //Sure let's reset a virtual disk break; case 0x0e: /* Select Default Drive */ DOS_SetDefaultDrive(reg_dl); reg_al=DOS_DRIVES; break; case 0x0f: /* Open File using FCB */ if(DOS_FCBOpen(SegValue(ds),reg_dx)){ reg_al=0; }else{ reg_al=0xff; } LOG(LOG_FCB,LOG_NORMAL)("DOS:0x0f FCB-fileopen used, result:al=%d",reg_al); break; case 0x10: /* Close File using FCB */ if(DOS_FCBClose(SegValue(ds),reg_dx)){ reg_al=0; }else{ reg_al=0xff; } LOG(LOG_FCB,LOG_NORMAL)("DOS:0x10 FCB-fileclose used, result:al=%d",reg_al); break; case 0x11: /* Find First Matching File using FCB */ if(DOS_FCBFindFirst(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; LOG(LOG_FCB,LOG_NORMAL)("DOS:0x11 FCB-FindFirst used, result:al=%d",reg_al); break; case 0x12: /* Find Next Matching File using FCB */ if(DOS_FCBFindNext(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; LOG(LOG_FCB,LOG_NORMAL)("DOS:0x12 FCB-FindNext used, result:al=%d",reg_al); break; case 0x13: /* Delete File using FCB */ if (DOS_FCBDeleteFile(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; LOG(LOG_FCB,LOG_NORMAL)("DOS:0x16 FCB-Delete used, result:al=%d",reg_al); break; case 0x14: /* Sequential read from FCB */ reg_al = DOS_FCBRead(SegValue(ds),reg_dx,0); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x14 FCB-Read used, result:al=%d",reg_al); break; case 0x15: /* Sequential write to FCB */ reg_al=DOS_FCBWrite(SegValue(ds),reg_dx,0); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x15 FCB-Write used, result:al=%d",reg_al); break; case 0x16: /* Create or truncate file using FCB */ if (DOS_FCBCreate(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; LOG(LOG_FCB,LOG_NORMAL)("DOS:0x16 FCB-Create used, result:al=%d",reg_al); break; case 0x17: /* Rename file using FCB */ if (DOS_FCBRenameFile(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; break; case 0x1b: /* Get allocation info for default drive */ if (!DOS_GetAllocationInfo(0,®_cx,®_al,®_dx)) reg_al=0xff; break; case 0x1c: /* Get allocation info for specific drive */ if (!DOS_GetAllocationInfo(reg_dl,®_cx,®_al,®_dx)) reg_al=0xff; break; case 0x21: /* Read random record from FCB */ reg_al = DOS_FCBRandomRead(SegValue(ds),reg_dx,1,true); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x21 FCB-Random read used, result:al=%d",reg_al); break; case 0x22: /* Write random record to FCB */ reg_al=DOS_FCBRandomWrite(SegValue(ds),reg_dx,1,true); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x22 FCB-Random write used, result:al=%d",reg_al); break; case 0x23: /* Get file size for FCB */ if (DOS_FCBGetFileSize(SegValue(ds),reg_dx)) reg_al = 0x00; else reg_al = 0xFF; break; case 0x24: /* Set Random Record number for FCB */ DOS_FCBSetRandomRecord(SegValue(ds),reg_dx); break; case 0x27: /* Random block read from FCB */ reg_al = DOS_FCBRandomRead(SegValue(ds),reg_dx,reg_cx,false); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x27 FCB-Random(block) read used, result:al=%d",reg_al); break; case 0x28: /* Random Block write to FCB */ reg_al=DOS_FCBRandomWrite(SegValue(ds),reg_dx,reg_cx,false); LOG(LOG_FCB,LOG_NORMAL)("DOS:0x28 FCB-Random(block) write used, result:al=%d",reg_al); break; case 0x29: /* Parse filename into FCB */ { Bit8u difference; char string[1024]; MEM_StrCopy(SegPhys(ds)+reg_si,string,1023); // 1024 toasts the stack reg_al=FCB_Parsename(SegValue(es),reg_di,reg_al ,string, &difference); reg_si+=difference; } LOG(LOG_FCB,LOG_NORMAL)("DOS:29:FCB Parse Filename, result:al=%d",reg_al); break; case 0x19: /* Get current default drive */ reg_al=DOS_GetDefaultDrive(); break; case 0x1a: /* Set Disk Transfer Area Address */ dos.dta(RealMakeSeg(ds,reg_dx)); break; case 0x25: /* Set Interrupt Vector */ RealSetVec(reg_al,RealMakeSeg(ds,reg_dx)); break; case 0x26: /* Create new PSP */ DOS_NewPSP(reg_dx,DOS_PSP(dos.psp()).GetSize()); break; case 0x2a: /* Get System Date */ { int a = (14 - dos.date.month)/12; int y = dos.date.year - a; int m = dos.date.month + 12*a - 2; reg_al=(dos.date.day+y+(y/4)-(y/100)+(y/400)+(31*m)/12) % 7; reg_cx=dos.date.year; reg_dh=dos.date.month; reg_dl=dos.date.day; } break; case 0x2b: /* Set System Date */ if (reg_cx<1980) { reg_al=0xff;break;} if ((reg_dh>12) || (reg_dh==0)) { reg_al=0xff;break;} if ((reg_dl>31) || (reg_dl==0)) { reg_al=0xff;break;} dos.date.year=reg_cx; dos.date.month=reg_dh; dos.date.day=reg_dl; reg_al=0; break; case 0x2c: /* Get System Time */ //TODO Get time through bios calls date is fixed { /* Calculate how many miliseconds have passed */ Bitu ticks=5*mem_readd(BIOS_TIMER); ticks = ((ticks / 59659u) << 16) + ((ticks % 59659u) << 16) / 59659u; Bitu seconds=(ticks/100); reg_ch=(Bit8u)(seconds/3600); reg_cl=(Bit8u)((seconds % 3600)/60); reg_dh=(Bit8u)(seconds % 60); reg_dl=(Bit8u)(ticks % 100); } break; case 0x2d: /* Set System Time */ LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Set System Time not supported"); //Check input parameters nonetheless if( reg_ch > 23 || reg_cl > 59 || reg_dh > 59 || reg_dl > 99 ) reg_al = 0xff; else reg_al = 0; break; case 0x2e: /* Set Verify flag */ dos.verify=(reg_al==1); break; case 0x2f: /* Get Disk Transfer Area */ SegSet16(es,RealSeg(dos.dta())); reg_bx=RealOff(dos.dta()); break; case 0x30: /* Get DOS Version */ if (reg_al==0) reg_bh=0xFF; /* Fake Microsoft DOS */ if (reg_al==1) reg_bh=0x10; /* DOS is in HMA */ reg_al=dos.version.major; reg_ah=dos.version.minor; /* Serialnumber */ reg_bl=0x00; reg_cx=0x0000; break; case 0x31: /* Terminate and stay resident */ //TODO First get normal files executing // Important: This service does not set the carry flag! DOS_ResizeMemory(dos.psp(),®_dx); DOS_Terminate(true,reg_al); dos.return_mode=RETURN_TSR; break; case 0x1f: /* Get drive parameter block for default drive */ case 0x32: /* Get drive parameter block for specific drive */ { /* Officially a dpb should be returned as well. The disk detection part is implemented */ Bitu drive=reg_dl;if(!drive || reg_ah==0x1f) drive=dos.current_drive;else drive--; if(Drives[drive]) { reg_al = 0x00; SegSet16(ds,dos.tables.dpb); reg_bx = drive;//Faking only the first entry (that is the driveletter) LOG(LOG_DOSMISC,LOG_ERROR)("Get drive parameter block."); } else { reg_al=0xff; } } break; case 0x33: /* Extended Break Checking */ switch (reg_al) { case 0:reg_dl=dos.breakcheck;break; /* Get the breakcheck flag */ case 1:dos.breakcheck=(reg_dl>0);break; /* Set the breakcheck flag */ case 2:{bool old=dos.breakcheck;dos.breakcheck=(reg_dl>0);reg_dl=old;}break; case 3: /* Get cpsw */ /* Fallthrough */ case 4: /* Set cpsw */ LOG(LOG_DOSMISC,LOG_ERROR)("Someone playing with cpsw %x",reg_ax); break; case 5:reg_dl=3;break;//TODO should be z /* Always boot from c: :) */ case 6: /* Get true version number */ reg_bl=dos.version.major; reg_bh=dos.version.minor; reg_dl=dos.version.revision; reg_dh=0x10; /* Dos in HMA */ break; default: E_Exit("DOS:Illegal 0x33 Call %2X",reg_al); } break; case 0x34: /* Get INDos Flag */ SegSet16(es,DOS_SDA_SEG); reg_bx=DOS_SDA_OFS + 0x01; break; case 0x35: /* Get interrupt vector */ reg_bx=real_readw(0,((Bit16u)reg_al)*4); SegSet16(es,real_readw(0,((Bit16u)reg_al)*4+2)); break; case 0x36: /* Get Free Disk Space */ { Bit16u bytes,clusters,free; Bit8u sectors; if (DOS_GetFreeDiskSpace(reg_dl,&bytes,§ors,&clusters,&free)) { reg_ax=sectors; reg_bx=free; reg_cx=bytes; reg_dx=clusters; } else { Bit8u drive=reg_dl; if (drive==0) drive=DOS_GetDefaultDrive(); else drive--; if (drive<2) { // floppy oddity, non-present drives don't fail with the // invalid drive error CALLBACK_SCF(true); } else { reg_ax=0xffff; // invalid drive specified } } } break; case 0x37: /* Get/Set Switch char Get/Set Availdev thing */ //TODO Give errors for these functions to see if anyone actually uses this shit- switch (reg_al) { case 0: reg_al=0;reg_dl=0x2f;break; /* always return '/' like dos 5.0+ */ case 1: reg_al=0;break; case 2: reg_al=0;reg_dl=0x2f;break; case 3: reg_al=0;break; }; LOG(LOG_MISC,LOG_ERROR)("DOS:0x37:Call for not supported switchchar"); break; case 0x38: /* Set Country Code */ if (reg_al==0) { /* Get country specidic information */ PhysPt dest = SegPhys(ds)+reg_dx; MEM_BlockWrite(dest,dos.tables.country,0x18); reg_ax = reg_bx = 0x01; CALLBACK_SCF(false); break; } else { /* Set country code */ LOG(LOG_MISC,LOG_ERROR)("DOS:Setting country code not supported"); } CALLBACK_SCF(true); break; case 0x39: /* MKDIR Create directory */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_MakeDir(name1)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x3a: /* RMDIR Remove directory */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_RemoveDir(name1)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); LOG(LOG_MISC,LOG_NORMAL)("Remove dir failed on %s with error %X",name1,dos.errorcode); } break; case 0x3b: /* CHDIR Set current directory */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_ChangeDir(name1)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x3c: /* CREATE Create of truncate file */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_CreateFile(name1,reg_cx,®_ax)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x3d: /* OPEN Open existing file */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_OpenFile(name1,reg_al,®_ax)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x3e: /* CLOSE Close file */ if (DOS_CloseFile(reg_bx)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x3f: /* READ Read from file or device */ { Bit16u toread=reg_cx; dos.echo=true; if (DOS_ReadFile(reg_bx,dos_copybuf,&toread)) { MEM_BlockWrite(SegPhys(ds)+reg_dx,dos_copybuf,toread); reg_ax=toread; CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } modify_cycles(reg_ax); dos.echo=false; break; } case 0x40: /* WRITE Write to file or device */ { Bit16u towrite=reg_cx; MEM_BlockRead(SegPhys(ds)+reg_dx,dos_copybuf,towrite); if (DOS_WriteFile(reg_bx,dos_copybuf,&towrite)) { reg_ax=towrite; CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } modify_cycles(reg_ax); break; }; case 0x41: /* UNLINK Delete file */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_UnlinkFile(name1)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x42: /* LSEEK Set current file position */ { Bit32u pos=(reg_cx<<16) + reg_dx; if (DOS_SeekFile(reg_bx,&pos,reg_al)) { reg_dx=(Bit16u)(pos >> 16); reg_ax=(Bit16u)(pos & 0xFFFF); CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; } case 0x43: /* Get/Set file attributes */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); switch (reg_al) { case 0x00: /* Get */ { Bit16u attr_val=reg_cx; if (DOS_GetFileAttr(name1,&attr_val)) { reg_cx=attr_val; reg_ax=attr_val; /* Undocumented */ CALLBACK_SCF(false); } else { CALLBACK_SCF(true); reg_ax=dos.errorcode; } break; }; case 0x01: /* Set */ LOG(LOG_MISC,LOG_ERROR)("DOS:Set File Attributes for %s not supported",name1); if (DOS_SetFileAttr(name1,reg_cx)) { reg_ax=0x202; /* ax destroyed */ CALLBACK_SCF(false); } else { CALLBACK_SCF(true); reg_ax=dos.errorcode; } break; default: LOG(LOG_MISC,LOG_ERROR)("DOS:0x43:Illegal subfunction %2X",reg_al); reg_ax=1; CALLBACK_SCF(true); break; } break; case 0x44: /* IOCTL Functions */ if (DOS_IOCTL()) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x45: /* DUP Duplicate file handle */ if (DOS_DuplicateEntry(reg_bx,®_ax)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x46: /* DUP2,FORCEDUP Force duplicate file handle */ if (DOS_ForceDuplicateEntry(reg_bx,reg_cx)) { reg_ax=reg_cx; //Not all sources agree on it. CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x47: /* CWD Get current directory */ if (DOS_GetCurrentDir(reg_dl,name1)) { MEM_BlockWrite(SegPhys(ds)+reg_si,name1,strlen(name1)+1); reg_ax=0x0100; CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x48: /* Allocate memory */ { Bit16u size=reg_bx;Bit16u seg; if (DOS_AllocateMemory(&seg,&size)) { reg_ax=seg; CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; reg_bx=size; CALLBACK_SCF(true); } break; } case 0x49: /* Free memory */ if (DOS_FreeMemory(SegValue(es))) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x4a: /* Resize memory block */ { Bit16u size=reg_bx; if (DOS_ResizeMemory(SegValue(es),&size)) { reg_ax=SegValue(es); CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; reg_bx=size; CALLBACK_SCF(true); } break; } case 0x4b: /* EXEC Load and/or execute program */ { MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); LOG(LOG_EXEC,LOG_ERROR)("Execute %s %d",name1,reg_al); if (!DOS_Execute(name1,SegPhys(es)+reg_bx,reg_al)) { reg_ax=dos.errorcode; CALLBACK_SCF(true); } } break; //TODO Check for use of execution state AL=5 case 0x00: reg_ax=0x4c00; /* Terminate Program */ case 0x4c: /* EXIT Terminate with return code */ { if (DOS_Terminate(false,reg_al)) { /* This can't ever return false normally */ } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; } case 0x4d: /* Get Return code */ reg_al=dos.return_code;/* Officially read from SDA and clear when read */ reg_ah=dos.return_mode; break; case 0x4e: /* FINDFIRST Find first matching file */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_FindFirst(name1,reg_cx)) { CALLBACK_SCF(false); reg_ax=0; /* Undocumented */ } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); }; break; case 0x4f: /* FINDNEXT Find next matching file */ if (DOS_FindNext()) { CALLBACK_SCF(false); /* reg_ax=0xffff;*/ /* Undocumented */ reg_ax=0; /* Undocumented:Qbix Willy beamish */ } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); }; break; case 0x50: /* Set current PSP */ dos.psp(reg_bx); break; case 0x51: /* Get current PSP */ reg_bx=dos.psp(); break; case 0x52: { /* Get list of lists */ RealPt addr=dos_infoblock.GetPointer(); SegSet16(es,RealSeg(addr)); reg_bx=RealOff(addr); LOG(LOG_DOSMISC,LOG_NORMAL)("Call is made for list of lists - let's hope for the best"); break; } //TODO Think hard how shit this is gonna be //And will any game ever use this :) case 0x53: /* Translate BIOS parameter block to drive parameter block */ E_Exit("Unhandled Dos 21 call %02X",reg_ah); break; case 0x54: /* Get verify flag */ reg_al=dos.verify?1:0; break; case 0x55: /* Create Child PSP*/ DOS_ChildPSP(reg_dx,reg_si); dos.psp(reg_dx); break; case 0x56: /* RENAME Rename file */ MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); MEM_StrCopy(SegPhys(es)+reg_di,name2,DOSNAMEBUF); if (DOS_Rename(name1,name2)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x57: /* Get/Set File's Date and Time */ if (reg_al==0x00) { if (DOS_GetFileDate(reg_bx,®_cx,®_dx)) { CALLBACK_SCF(false); } else { CALLBACK_SCF(true); } } else if (reg_al==0x01) { LOG(LOG_DOSMISC,LOG_ERROR)("DOS:57:Set File Date Time Faked"); CALLBACK_SCF(false); } else { LOG(LOG_DOSMISC,LOG_ERROR)("DOS:57:Unsupported subtion %X",reg_al); } break; case 0x58: /* Get/Set Memory allocation strategy */ switch (reg_al) { case 0: /* Get Strategy */ reg_ax=DOS_GetMemAllocStrategy(); break; case 1: /* Set Strategy */ if (DOS_SetMemAllocStrategy(reg_bx)) CALLBACK_SCF(false); else { reg_ax=1; CALLBACK_SCF(true); } break; case 2: /* Get UMB Link Status */ reg_al=dos_infoblock.GetUMBChainState()&1; CALLBACK_SCF(false); break; case 3: /* Set UMB Link Status */ if (DOS_LinkUMBsToMemChain(reg_bx)) CALLBACK_SCF(false); else { reg_ax=1; CALLBACK_SCF(true); } break; default: LOG(LOG_DOSMISC,LOG_ERROR)("DOS:58:Not Supported Set//Get memory allocation call %X",reg_al); } break; case 0x59: /* Get Extended error information */ reg_ax=dos.errorcode; reg_bh=0; //Unkown error class reg_bl=1; //Retry retry retry reg_ch=0; //Unkown error locus break; case 0x5a: /* Create temporary file */ { Bit16u handle; MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); if (DOS_CreateTempFile(name1,&handle)) { reg_ax=handle; MEM_BlockWrite(SegPhys(ds)+reg_dx,name1,strlen(name1)+1); CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } } break; case 0x5b: /* Create new file */ { MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF); Bit16u handle; if (DOS_OpenFile(name1,0,&handle)) { DOS_CloseFile(handle); DOS_SetError(DOSERR_FILE_ALREADY_EXISTS); reg_ax=dos.errorcode; CALLBACK_SCF(true); break; } if (DOS_CreateFile(name1,reg_cx,&handle)) { reg_ax=handle; CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; } case 0x5d: /* Network Functions */ if(reg_al == 0x06) { SegSet16(ds,DOS_SDA_SEG); reg_si = DOS_SDA_OFS; reg_cx = 0x80; // swap if in dos reg_dx = 0x1a; // swap always LOG(LOG_DOSMISC,LOG_ERROR)("Get SDA, Let's hope for the best!"); } break; case 0x5f: /* Network redirection */ reg_ax=0x0001; //Failing it CALLBACK_SCF(true); break; case 0x60: /* Canonicalize filename or path */ MEM_StrCopy(SegPhys(ds)+reg_si,name1,DOSNAMEBUF); if (DOS_Canonicalize(name1,name2)) { MEM_BlockWrite(SegPhys(es)+reg_di,name2,strlen(name2)+1); CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x62: /* Get Current PSP Address */ reg_bx=dos.psp(); break; case 0x63: /* DOUBLE BYTE CHARACTER SET */ if(reg_al == 0) { SegSet16(ds,RealSeg(dos.tables.dbcs)); reg_si=RealOff(dos.tables.dbcs); reg_al = 0; CALLBACK_SCF(false); //undocumented } else reg_al = 0xff; //Doesn't officially touch carry flag break; case 0x64: /* Set device driver lookahead flag */ LOG(LOG_DOSMISC,LOG_NORMAL)("set driver look ahead flag"); break; case 0x65: /* Get extented country information and a lot of other useless shit*/ { /* Todo maybe fully support this for now we set it standard for USA */ LOG(LOG_DOSMISC,LOG_ERROR)("DOS:65:Extended country information call %X",reg_ax); if((reg_al <= 0x07) && (reg_cx < 0x05)) { DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID); CALLBACK_SCF(true); break; } Bitu len = 0; /* For 0x21 and 0x22 */ PhysPt data=SegPhys(es)+reg_di; switch (reg_al) { case 0x01: mem_writeb(data + 0x00,reg_al); mem_writew(data + 0x01,0x26); mem_writew(data + 0x03,1); if(reg_cx > 0x06 ) mem_writew(data+0x05,dos.loaded_codepage); if(reg_cx > 0x08 ) { Bitu amount = (reg_cx>=0x29)?0x22:(reg_cx-7); MEM_BlockWrite(data + 0x07,dos.tables.country,amount); reg_cx=(reg_cx>=0x29)?0x29:reg_cx; } CALLBACK_SCF(false); break; case 0x05: // Get pointer to filename terminator table mem_writeb(data + 0x00, reg_al); mem_writed(data + 0x01, dos.tables.filenamechar); reg_cx = 5; CALLBACK_SCF(false); break; case 0x06: // Get pointer to collating sequence table mem_writeb(data + 0x00, reg_al); mem_writed(data + 0x01, dos.tables.collatingseq); reg_cx = 5; CALLBACK_SCF(false); break; case 0x02: // Get pointer to uppercase table case 0x03: // Get pointer to lowercase table case 0x04: // Get pointer to filename uppercase table case 0x07: // Get pointer to double byte char set table mem_writeb(data + 0x00, reg_al); mem_writed(data + 0x01, dos.tables.dbcs); //used to be 0 reg_cx = 5; CALLBACK_SCF(false); break; case 0x20: /* Capitalize Character */ { int in = reg_dl; int out = toupper(in); reg_dl = out; } CALLBACK_SCF(false); break; case 0x21: /* Capitalize String (cx=length) */ case 0x22: /* Capatilize ASCIZ string */ data = SegPhys(ds) + reg_dx; if(reg_al == 0x21) len = reg_cx; else len = mem_strlen(data); /* Is limited to 1024 */ if(len > DOS_COPYBUFSIZE - 1) E_Exit("DOS:0x65 Buffer overflow"); if(len) { MEM_BlockRead(data,dos_copybuf,len); dos_copybuf[len] = 0; //No upcase as String(0x21) might be multiple asciz strings for(Bitu count = 0; count < len;count++) dos_copybuf[count] = toupper(*reinterpret_cast(dos_copybuf+count)); MEM_BlockWrite(data,dos_copybuf,len); } CALLBACK_SCF(false); break; default: E_Exit("DOS:0x65:Unhandled country information call %2X",reg_al); }; break; } case 0x66: /* Get/Set global code page table */ if (reg_al==1) { LOG(LOG_DOSMISC,LOG_ERROR)("Getting global code page table"); reg_bx=reg_dx=dos.loaded_codepage; CALLBACK_SCF(false); break; } LOG(LOG_DOSMISC,LOG_NORMAL)("DOS:Setting code page table is not supported"); break; case 0x67: /* Set handle count */ /* Weird call to increase amount of file handles needs to allocate memory if >20 */ { DOS_PSP psp(dos.psp()); psp.SetNumFiles(reg_bx); CALLBACK_SCF(false); break; }; case 0x69: /* Get/Set disk serial number */ { switch(reg_al) { case 0x00: /* Get */ LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Get Disk serial number"); CALLBACK_SCF(true); break; case 0x01: LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Set Disk serial number"); default: E_Exit("DOS:Illegal Get Serial Number call %2X",reg_al); } break; } case 0x6c: /* Extended Open/Create */ MEM_StrCopy(SegPhys(ds)+reg_si,name1,DOSNAMEBUF); if (DOS_OpenFileExtended(name1,reg_bx,reg_cx,reg_dx,®_ax,®_cx)) { CALLBACK_SCF(false); } else { reg_ax=dos.errorcode; CALLBACK_SCF(true); } break; case 0x71: /* Unknown probably 4dos detection */ reg_ax=0x7100; CALLBACK_SCF(true); LOG(LOG_DOSMISC,LOG_NORMAL)("DOS:Windows long file name support call %2X",reg_al); break; case 0x68: /* FFLUSH Commit file */ CALLBACK_SCF(false); //mirek case 0xE0: case 0x18: /* NULL Function for CP/M compatibility or Extended rename FCB */ case 0x1d: /* NULL Function for CP/M compatibility or Extended rename FCB */ case 0x1e: /* NULL Function for CP/M compatibility or Extended rename FCB */ case 0x20: /* NULL Function for CP/M compatibility or Extended rename FCB */ case 0x6b: /* NULL Function */ case 0x61: /* UNUSED */ case 0xEF: /* Used in Ancient Art Of War CGA */ case 0x5c: /* FLOCK File region locking */ case 0x5e: /* More Network Functions */ default: LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Unhandled call %02X al=%02X. Set al to default of 0",reg_ah,reg_al); reg_al=0x00; /* default value */ break; }; return CBRET_NONE; }; static Bitu DOS_20Handler(void) { reg_ax=0x4c00; DOS_21Handler(); return CBRET_NONE; } static Bitu DOS_27Handler(void) { // Terminate & stay resident Bit16u para = (reg_dx/16)+((reg_dx % 16)>0); if (DOS_ResizeMemory(dos.psp(),¶)) DOS_Terminate(true,0); return CBRET_NONE; } static Bitu DOS_25Handler(void) { if(Drives[reg_al]==0){ reg_ax=0x8002; SETFLAGBIT(CF,true); }else{ SETFLAGBIT(CF,false); if((reg_cx != 1) ||(reg_dx != 1)) LOG(LOG_DOSMISC,LOG_NORMAL)("int 25 called but not as diskdetection drive %X",reg_al); reg_ax=0; } return CBRET_NONE; } static Bitu DOS_26Handler(void) { LOG(LOG_DOSMISC,LOG_NORMAL)("int 26 called: hope for the best!"); if(Drives[reg_al]==0){ reg_ax=0x8002; SETFLAGBIT(CF,true); }else{ SETFLAGBIT(CF,false); reg_ax=0; } return CBRET_NONE; } class DOS:public Module_base{ private: CALLBACK_HandlerObject callback[7]; public: DOS(Section* configuration):Module_base(configuration){ callback[0].Install(DOS_20Handler,CB_IRET,"DOS Int 20"); callback[0].Set_RealVec(0x20); callback[1].Install(DOS_21Handler,CB_IRET_STI,"DOS Int 21"); callback[1].Set_RealVec(0x21); callback[2].Install(DOS_25Handler,CB_RETF,"DOS Int 25"); callback[2].Set_RealVec(0x25); callback[3].Install(DOS_26Handler,CB_RETF,"DOS Int 26"); callback[3].Set_RealVec(0x26); callback[4].Install(DOS_27Handler,CB_IRET,"DOS Int 27"); callback[4].Set_RealVec(0x27); callback[5].Install(NULL,CB_IRET,"DOS Int 28"); callback[5].Set_RealVec(0x28); callback[6].Install(NULL,CB_INT29,"CON Output Int 29"); callback[6].Set_RealVec(0x29); // pseudocode for CB_INT29: // push ax // mov ah, 0x0e // int 0x10 // pop ax // iret DOS_SetupFiles(); /* Setup system File tables */ DOS_SetupDevices(); /* Setup dos devices */ DOS_SetupTables(); DOS_SetupMemory(); /* Setup first MCB */ DOS_SetupPrograms(); DOS_SetupMisc(); /* Some additional dos interrupts */ DOS_SetDefaultDrive(25); dos.version.major=5; dos.version.minor=0; /* Setup time and date */ time_t curtime;struct tm *loctime; curtime = time (NULL);loctime = localtime (&curtime); dos.date.day=(Bit8u)loctime->tm_mday; dos.date.month=(Bit8u)loctime->tm_mon+1; dos.date.year=(Bit16u)loctime->tm_year+1900; Bit32u ticks=(Bit32u)((loctime->tm_hour*3600+loctime->tm_min*60+loctime->tm_sec)*18.2); mem_writed(BIOS_TIMER,ticks); } ~DOS(){ for (Bit16u i=0;iAddDestroyFunction(&DOS_ShutDown,false); }