DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/dos.cpp
00001 /*
00002  *  Copyright (C) 2002-2015  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
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 
00020 #include <stdlib.h>
00021 #include <string.h>
00022 #include <ctype.h>
00023 #include "dosbox.h"
00024 #include "dos_inc.h"
00025 #include "bios.h"
00026 #include "mem.h"
00027 #include "paging.h"
00028 #include "callback.h"
00029 #include "regs.h"
00030 #include "drives.h"
00031 #include "dos_inc.h"
00032 #include "setup.h"
00033 #include "support.h"
00034 #include "parport.h"
00035 #include "serialport.h"
00036 #include "dos_network.h"
00037 
00038 int ascii_toupper(int c) {
00039     if (c >= 'a' && c <= 'z')
00040         return c + 'A' - 'a';
00041 
00042     return c;
00043 }
00044 
00045 bool shiftjis_lead_byte(int c) {
00046     if ((((unsigned char)c & 0xE0) == 0x80) ||
00047         (((unsigned char)c & 0xE0) == 0xE0))
00048         return true;
00049 
00050     return false;
00051 }
00052 
00053 char * shiftjis_upcase(char * str) {
00054     for (char* idx = str; *idx ; ) {
00055         if (shiftjis_lead_byte(*idx)) {
00056             /* Shift-JIS is NOT ASCII and should not be converted to uppercase like ASCII.
00057              * The trailing byte can be mistaken for ASCII */
00058             idx++;
00059             if (*idx != 0) idx++;
00060         }
00061         else {
00062             *idx = ascii_toupper(*reinterpret_cast<unsigned char*>(idx));
00063             idx++;
00064         }
00065     }
00066 
00067     return str;
00068 }
00069 
00070 unsigned char cpm_compat_mode = CPM_COMPAT_MSDOS5;
00071 
00072 bool dos_in_hma = true;
00073 bool DOS_BreakFlag = false;
00074 bool enable_dbcs_tables = true;
00075 bool enable_filenamechar = true;
00076 bool enable_share_exe_fake = true;
00077 int dos_initial_hma_free = 34*1024;
00078 int dos_sda_size = 0x560;
00079 
00080 extern bool int15_wait_force_unmask_irq;
00081 
00082 Bit32u dos_hma_allocator = 0; /* physical memory addr */
00083 
00084 Bitu XMS_EnableA20(bool enable);
00085 Bitu XMS_GetEnabledA20(void);
00086 bool XMS_IS_ACTIVE();
00087 bool XMS_HMA_EXISTS();
00088 
00089 bool DOS_IS_IN_HMA() {
00090         if (dos_in_hma && XMS_IS_ACTIVE() && XMS_HMA_EXISTS())
00091                 return true;
00092 
00093         return false;
00094 }
00095 
00096 Bit32u DOS_HMA_LIMIT() {
00097         if (dos.version.major < 5) return 0; /* MS-DOS 5.0+ only */
00098         if (!DOS_IS_IN_HMA()) return 0;
00099         return (0x110000 - 16); /* 1MB + 64KB - 16 bytes == (FFFF:FFFF + 1) == (0xFFFF0 + 0xFFFF + 1) == 0x10FFF0 */
00100 }
00101 
00102 Bit32u DOS_HMA_FREE_START() {
00103         if (dos.version.major < 5) return 0; /* MS-DOS 5.0+ only */
00104         if (!DOS_IS_IN_HMA()) return 0;
00105 
00106         if (dos_hma_allocator == 0) {
00107                 dos_hma_allocator = 0x110000u - 16u - (unsigned int)dos_initial_hma_free;
00108                 LOG(LOG_MISC,LOG_DEBUG)("Starting HMA allocation from physical address 0x%06x (FFFF:%04x)",
00109                         dos_hma_allocator,(dos_hma_allocator+0x10u)&0xFFFFu);
00110         }
00111 
00112         return dos_hma_allocator;
00113 }
00114 
00115 Bit32u DOS_HMA_GET_FREE_SPACE() {
00116         Bit32u start;
00117 
00118         if (dos.version.major < 5) return 0; /* MS-DOS 5.0+ only */
00119         if (!DOS_IS_IN_HMA()) return 0;
00120         start = DOS_HMA_FREE_START();
00121         if (start == 0) return 0;
00122         return (DOS_HMA_LIMIT() - start);
00123 }
00124 
00125 void DOS_HMA_CLAIMED(Bitu bytes) {
00126         Bit32u limit = DOS_HMA_LIMIT();
00127 
00128         if (limit == 0) E_Exit("HMA allocatiom bug: Claim function called when HMA allocation is not enabled");
00129         if (dos_hma_allocator == 0) E_Exit("HMA allocatiom bug: Claim function called without having determined start");
00130         dos_hma_allocator += bytes;
00131         if (dos_hma_allocator > limit) E_Exit("HMA allocation bug: Exceeded limit");
00132 }
00133 
00134 Bit16u DOS_INFOBLOCK_SEG=0x80;  // sysvars (list of lists)
00135 Bit16u DOS_CONDRV_SEG=0xa0;
00136 Bit16u DOS_CONSTRING_SEG=0xa8;
00137 Bit16u DOS_SDA_SEG=0xb2;                // dos swappable area
00138 Bit16u DOS_SDA_SEG_SIZE=0x560;  // WordPerfect 5.1 consideration (emendelson)
00139 Bit16u DOS_SDA_OFS=0;
00140 Bit16u DOS_CDS_SEG=0x108;
00141 Bit16u DOS_MEM_START=0x158;      // regression to r3437 fixes nascar 2 colors
00142 Bit16u minimum_mcb_segment=0x70;
00143 Bit16u minimum_mcb_free=0x70;
00144 Bit16u minimum_dos_initial_private_segment=0x70;
00145 
00146 Bit16u DOS_PRIVATE_SEGMENT=0;//0xc800;
00147 Bit16u DOS_PRIVATE_SEGMENT_END=0;//0xd000;
00148 
00149 Bitu DOS_PRIVATE_SEGMENT_Size=0x800;    // 32KB (0x800 pages), mainline DOSBox behavior
00150 
00151 bool enable_dummy_device_mcb = true;
00152 
00153 extern unsigned int MAXENV;// = 32768u;
00154 extern unsigned int ENV_KEEPFREE;// = 83;
00155 
00156 DOS_Block dos;
00157 DOS_InfoBlock dos_infoblock;
00158 
00159 extern bool dos_kernel_disabled;
00160 
00161 Bit16u DOS_Block::psp() {
00162         if (dos_kernel_disabled) {
00163                 LOG_MSG("BUG: DOS kernel is disabled (booting a guest OS), and yet somebody is still asking for DOS's current PSP segment\n");
00164                 return 0x0000;
00165         }
00166 
00167         return DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).GetPSP();
00168 }
00169 
00170 void DOS_Block::psp(Bit16u _seg) {
00171         if (dos_kernel_disabled) {
00172                 LOG_MSG("BUG: DOS kernel is disabled (booting a guest OS), and yet somebody is still attempting to change DOS's current PSP segment\n");
00173                 return;
00174         }
00175 
00176         DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).SetPSP(_seg);
00177 }
00178 
00179 RealPt DOS_Block::dta() {
00180         if (dos_kernel_disabled) {
00181                 LOG_MSG("BUG: DOS kernel is disabled (booting a guest OS), and yet somebody is still asking for DOS's DTA (disk transfer address)\n");
00182                 return 0;
00183         }
00184 
00185         return DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).GetDTA();
00186 }
00187 
00188 void DOS_Block::dta(RealPt _dta) {
00189         if (dos_kernel_disabled) {
00190                 LOG_MSG("BUG: DOS kernel is disabled (booting a guest OS), and yet somebody is still attempting to change DOS's DTA (disk transfer address)\n");
00191                 return;
00192         }
00193 
00194         DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).SetDTA(_dta);
00195 }
00196 
00197 #define DOS_COPYBUFSIZE 0x10000
00198 Bit8u dos_copybuf[DOS_COPYBUFSIZE];
00199 #ifdef WIN32
00200 Bit16u  NetworkHandleList[127];Bit8u dos_copybuf_second[DOS_COPYBUFSIZE];
00201 #endif
00202 
00203 void DOS_SetError(Bit16u code) {
00204         dos.errorcode=code;
00205 }
00206 
00207 const Bit8u DOS_DATE_months[] = {
00208         0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
00209 };
00210 
00211 static void DOS_AddDays(Bitu days) {
00212         dos.date.day += days;
00213         Bit8u monthlimit = DOS_DATE_months[dos.date.month];
00214 
00215         if(dos.date.day > monthlimit) {
00216                 if((dos.date.year %4 == 0) && (dos.date.month==2)) {
00217                         // leap year
00218                         if(dos.date.day > 29) {
00219                                 dos.date.month++;
00220                                 dos.date.day -= 29;
00221                         }
00222                 } else {
00223                         //not leap year
00224                         dos.date.month++;
00225                         dos.date.day -= monthlimit;
00226                 }
00227                 if(dos.date.month > 12) {
00228                         // year over
00229                         dos.date.month = 1;
00230                         dos.date.year++;
00231                 }
00232         }
00233 }
00234 
00235 #define DATA_TRANSFERS_TAKE_CYCLES 1
00236 #define DOS_OVERHEAD 1
00237 
00238 #ifndef DOSBOX_CPU_H
00239 #include "cpu.h"
00240 #endif
00241 
00242 // TODO: Make this configurable.
00243 //       Additionally, allow this to vary per-drive so that
00244 //       Drive D: can be as slow as a 2X IDE CD-ROM drive in PIO mode
00245 //       Drive C: can be as slow as a IDE drive in PIO mode and
00246 //       Drive A: can be as slow as a 3.5" 1.44MB floppy disk
00247 //
00248 // This fixes MS-DOS games that crash or malfunction if the disk I/O is too fast.
00249 // This also fixes "380 volt" and prevents the "city animation" from loading too fast for it's music timing (and getting stuck)
00250 int disk_data_rate = 2100000;    // 2.1MBytes/sec mid 1990s IDE PIO hard drive without SMARTDRV
00251 
00252 void diskio_delay(Bits value/*bytes*/) {
00253     if (disk_data_rate != 0) {
00254         double scalar = (double)value / disk_data_rate;
00255         double endtime = PIC_FullIndex() + (scalar * 1000);
00256 
00257         /* MS-DOS will most likely enable interrupts in the course of
00258          * performing disk I/O */
00259         CPU_STI();
00260 
00261         do {
00262             CALLBACK_Idle();
00263         } while (PIC_FullIndex() < endtime);
00264     }
00265 }
00266 
00267 static inline void overhead() {
00268         reg_ip += 2;
00269 }
00270 
00271 #define BCD2BIN(x)      ((((unsigned int)(x) >> 4u) * 10u) + ((x) & 0x0fu))
00272 #define BIN2BCD(x)      ((((x) / 10u) << 4u) + (x) % 10u)
00273 extern bool date_host_forced;
00274 
00275 static Bitu DOS_21Handler(void);
00276 Bitu DEBUG_EnableDebugger(void);
00277 void CALLBACK_RunRealInt_retcsip(Bit8u intnum,Bitu &cs,Bitu &ip);
00278 
00279 bool DOS_BreakINT23InProgress = false;
00280 
00281 void DOS_PrintCBreak() {
00282         /* print ^C <newline> */
00283         Bit16u n = 4;
00284         const char *nl = "^C\r\n";
00285         DOS_WriteFile(STDOUT,(Bit8u*)nl,&n);
00286 }
00287 
00288 bool DOS_BreakTest() {
00289         if (DOS_BreakFlag) {
00290                 bool terminate = true;
00291                 bool terminint23 = false;
00292                 Bitu segv,offv;
00293 
00294                 /* print ^C on the console */
00295                 DOS_PrintCBreak();
00296 
00297                 DOS_BreakFlag = false;
00298 
00299                 offv = mem_readw((0x23*4)+0);
00300                 segv = mem_readw((0x23*4)+2);
00301                 if (offv != 0 && segv != 0) { /* HACK: DOSBox's shell currently does not assign INT 23h */
00302                         /* NTS: DOS calls are allowed within INT 23h! */
00303                         Bitu save_sp = reg_sp;
00304 
00305                         /* set carry flag */
00306                         reg_flags |= 1;
00307 
00308                         /* invoke INT 23h */
00309                         /* NTS: Some DOS programs provide their own INT 23h which then calls INT 21h AH=0x4C
00310                          *      inside the handler! Set a flag so that if that happens, the termination
00311                          *      handler will throw us an exception to force our way back here after
00312                          *      termination completes!
00313                          *
00314                          *      This fixes: PC Mix compiler PCL.EXE
00315                          *
00316                          *      FIXME: This is an ugly hack! */
00317                         try {
00318                                 DOS_BreakINT23InProgress = true;
00319                                 CALLBACK_RunRealInt(0x23);
00320                                 DOS_BreakINT23InProgress = false;
00321                         }
00322                         catch (int x) {
00323                                 if (x == 0) {
00324                                         DOS_BreakINT23InProgress = false;
00325                                         terminint23 = true;
00326                                 }
00327                                 else {
00328                                         LOG_MSG("Unexpected code in INT 23h termination exception\n");
00329                                         abort();
00330                                 }
00331                         }
00332 
00333                         /* if the INT 23h handler did not already terminate itself... */
00334                         if (!terminint23) {
00335                                 /* if it returned with IRET, or with RETF and CF=0, don't terminate */
00336                                 if (reg_sp == save_sp || (reg_flags & 1) == 0) {
00337                                         terminate = false;
00338                                         LOG_MSG("Note: DOS handler does not wish to terminate\n");
00339                                 }
00340                                 else {
00341                                         /* program does not wish to continue. it used RETF. pop the remaining flags off */
00342                                         LOG_MSG("Note: DOS handler does wish to terminate\n");
00343                                 }
00344 
00345                                 if (reg_sp != save_sp) reg_sp += 2;
00346                         }
00347                 }
00348 
00349                 if (terminate) {
00350                         LOG_MSG("Note: DOS break terminating program\n");
00351                         DOS_Terminate(dos.psp(),false,0);
00352                         return false;
00353                 }
00354                 else if (terminint23) {
00355                         LOG_MSG("Note: DOS break handler terminated program for us.\n");
00356                         return false;
00357                 }
00358         }
00359 
00360         return true;
00361 }
00362 
00363 void DOS_BreakAction() {
00364         DOS_BreakFlag = true;
00365 }
00366 
00367 /* unmask IRQ 0 automatically on disk I/O functions.
00368  * there exist old DOS games and demos that rely on very selective IRQ masking,
00369  * but, their code also assumes that calling into DOS or the BIOS will unmask the IRQ.
00370  *
00371  * This fixes "Rebel by Arkham" which masks IRQ 0-7 (PIC port 21h) in a VERY stingy manner!
00372  *
00373  *    Pseudocode (early in demo init):
00374  *
00375  *             in     al,21h
00376  *             or     al,3Bh        ; mask IRQ 0, 1, 3, 4, and 5
00377  *             out    21h,al
00378  *
00379  *    Later:
00380  *
00381  *             mov    ah,3Dh        ; open file
00382  *             ...
00383  *             int    21h
00384  *             ...                  ; demo apparently assumes that INT 21h will unmask IRQ 0 when reading, because ....
00385  *             in     al,21h
00386  *             or     al,3Ah        ; mask IRQ 1, 3, 4, and 5
00387  *             out    21h,al
00388  *
00389  * The demo runs fine anyway, but if we do not unmask IRQ 0 at the INT 21h call, the timer never ticks and the
00390  * demo does not play any music (goldplay style, of course).
00391  *
00392  * This means several things. One is that a disk cache (which may provide the file without using INT 13h) could
00393  * mysteriously prevent the demo from playing music. Future OS changes, where IRQ unmasking during INT 21h could
00394  * not occur, would also prevent it from working. I don't know what the programmer was thinking, but side
00395  * effects like that are not to be relied on!
00396  *
00397  * On the other hand, perhaps masking the keyboard (IRQ 1) was intended as an anti-debugger trick? You can't break
00398  * into the demo if you can't trigger the debugger, after all! The demo can still poll the keyboard controller
00399  * for ESC or whatever.
00400  *
00401  * --J.C. */
00402 bool disk_io_unmask_irq0 = true;
00403 
00405 bool dos_program_running = false;
00406 
00407 #define DOSNAMEBUF 256
00408 static Bitu DOS_21Handler(void) {
00409     bool unmask_irq0 = false;
00410 
00411     if (((reg_ah != 0x50) && (reg_ah != 0x51) && (reg_ah != 0x62) && (reg_ah != 0x64)) && (reg_ah<0x6c)) {
00412         DOS_PSP psp(dos.psp());
00413         psp.SetStack(RealMake(SegValue(ss),reg_sp-18));
00414     }
00415 
00416     if (((reg_ah >= 0x01 && reg_ah <= 0x0C) || (reg_ah != 0 && reg_ah != 0x4C && reg_ah != 0x31 && dos.breakcheck)) && !DOS_BreakTest()) return CBRET_NONE;
00417 
00418     char name1[DOSNAMEBUF+2+DOS_NAMELENGTH_ASCII];
00419     char name2[DOSNAMEBUF+2+DOS_NAMELENGTH_ASCII];
00420     
00421     static Bitu time_start = 0; //For emulating temporary time changes.
00422 
00423     switch (reg_ah) {
00424         case 0x00:      /* Terminate Program */
00425             DOS_Terminate(mem_readw(SegPhys(ss)+reg_sp+2),false,0);
00426             if (DOS_BreakINT23InProgress) throw int(0); /* HACK: Ick */
00427             dos_program_running = false;
00428             break;
00429         case 0x01:      /* Read character from STDIN, with echo */
00430             {   
00431                 Bit8u c;Bit16u n=1;
00432                 dos.echo=true;
00433                 DOS_ReadFile(STDIN,&c,&n);
00434                 if (c == 3) {
00435                     DOS_BreakAction();
00436                     if (!DOS_BreakTest()) return CBRET_NONE;
00437                 }
00438                 reg_al=c;
00439                 dos.echo=false;
00440             }
00441             break;
00442         case 0x02:      /* Write character to STDOUT */
00443             {
00444                 Bit8u c=reg_dl;Bit16u n=1;
00445                 DOS_WriteFile(STDOUT,&c,&n);
00446                 //Not in the official specs, but happens nonetheless. (last written character)
00447                 reg_al = c;// reg_al=(c==9)?0x20:c; //Officially: tab to spaces
00448             }
00449             break;
00450         case 0x03:      /* Read character from STDAUX */
00451             {
00452                 Bit16u port = real_readw(0x40,0);
00453                 if(port!=0 && serialports[0]) {
00454                     Bit8u status;
00455                     // RTS/DTR on
00456                     IO_WriteB(port+4u,0x3u);
00457                     serialports[0]->Getchar(&reg_al, &status, true, 0xFFFFFFFF);
00458                 }
00459             }
00460             break;
00461         case 0x04:      /* Write Character to STDAUX */
00462             {
00463                 Bit16u port = real_readw(0x40,0);
00464                 if(port!=0 && serialports[0]) {
00465                     // RTS/DTR on
00466                     IO_WriteB(port+4u,0x3u);
00467                     serialports[0]->Putchar(reg_dl,true,true, 0xFFFFFFFF);
00468                     // RTS off
00469                     IO_WriteB(port+4u,0x1u);
00470                 }
00471             }
00472             break;
00473         case 0x05:      /* Write Character to PRINTER */
00474             {
00475                 for(unsigned int i = 0; i < 3; i++) {
00476                     // look up a parallel port
00477                     if(parallelPortObjects[i] != NULL) {
00478                         parallelPortObjects[i]->Putchar(reg_dl);
00479                         break;
00480                     }
00481                 }
00482                 break;
00483             }
00484         case 0x06:      /* Direct Console Output / Input */
00485             switch (reg_dl) {
00486                 case 0xFF:  /* Input */
00487                     {   
00488                         //Simulate DOS overhead for timing sensitive games
00489                         //MM1
00490                         overhead();
00491                         //TODO Make this better according to standards
00492                         if (!DOS_GetSTDINStatus()) {
00493                             reg_al=0;
00494                             CALLBACK_SZF(true);
00495                             break;
00496                         }
00497                         Bit8u c;Bit16u n=1;
00498                         DOS_ReadFile(STDIN,&c,&n);
00499                         reg_al=c;
00500                         CALLBACK_SZF(false);
00501                         break;
00502                     }
00503                 default:
00504                     {
00505                         Bit8u c = reg_dl;Bit16u n = 1;
00506                         DOS_WriteFile(STDOUT,&c,&n);
00507                         reg_al = reg_dl;
00508                     }
00509                     break;
00510             };
00511             break;
00512         case 0x07:      /* Character Input, without echo */
00513             {
00514                 Bit8u c;Bit16u n=1;
00515                 DOS_ReadFile (STDIN,&c,&n);
00516                 reg_al=c;
00517                 break;
00518             };
00519         case 0x08:      /* Direct Character Input, without echo (checks for breaks officially :)*/
00520             {
00521                 Bit8u c;Bit16u n=1;
00522                 DOS_ReadFile (STDIN,&c,&n);
00523                 if (c == 3) {
00524                     DOS_BreakAction();
00525                     if (!DOS_BreakTest()) return CBRET_NONE;
00526                 }
00527                 reg_al=c;
00528                 break;
00529             };
00530         case 0x09:      /* Write string to STDOUT */
00531             {   
00532                 Bit8u c;Bit16u n=1;
00533                 PhysPt buf=SegPhys(ds)+reg_dx;
00534                 while ((c=mem_readb(buf++))!='$') {
00535                     DOS_WriteFile(STDOUT,&c,&n);
00536                 }
00537             }
00538             break;
00539         case 0x0a:      /* Buffered Input */
00540             {
00541                 //TODO ADD Break checkin in STDIN but can't care that much for it
00542                 PhysPt data=SegPhys(ds)+reg_dx;
00543                 Bit8u free=mem_readb(data);
00544                 Bit8u read=0;Bit8u c;Bit16u n=1;
00545                 if (!free) break;
00546                 free--;
00547                 for(;;) {
00548                     if (!DOS_BreakTest()) return CBRET_NONE;
00549                     DOS_ReadFile(STDIN,&c,&n);
00550                     if (c == 8) {           // Backspace
00551                         if (read) { //Something to backspace.
00552                             // STDOUT treats backspace as non-destructive.
00553                             DOS_WriteFile(STDOUT,&c,&n);
00554                             c = ' '; DOS_WriteFile(STDOUT,&c,&n);
00555                             c = 8;   DOS_WriteFile(STDOUT,&c,&n);
00556                             --read;
00557                         }
00558                         continue;
00559                     }
00560                     if (c == 3) {   // CTRL+C
00561                         DOS_BreakAction();
00562                         if (!DOS_BreakTest()) return CBRET_NONE;
00563                     }
00564                     if (read == free && c != 13) {      // Keyboard buffer full
00565                         Bit8u bell = 7;
00566                         DOS_WriteFile(STDOUT, &bell, &n);
00567                         continue;
00568                     }
00569                     DOS_WriteFile(STDOUT,&c,&n);
00570                     mem_writeb(data+read+2,c);
00571                     if (c==13) 
00572                         break;
00573                     read++;
00574                 };
00575                 mem_writeb(data+1,read);
00576                 break;
00577             };
00578         case 0x0b:      /* Get STDIN Status */
00579             if (!DOS_GetSTDINStatus()) {reg_al=0x00;}
00580             else {reg_al=0xFF;}
00581             //Simulate some overhead for timing issues
00582             //Tankwar menu (needs maybe even more)
00583             overhead();
00584             break;
00585         case 0x0c:      /* Flush Buffer and read STDIN call */
00586             {
00587                 /* flush buffer if STDIN is CON */
00588                 Bit8u handle=RealHandle(STDIN);
00589                 if (handle!=0xFF && Files[handle] && Files[handle]->IsName("CON")) {
00590                     Bit8u c;Bit16u n;
00591                     while (DOS_GetSTDINStatus()) {
00592                         n=1;    DOS_ReadFile(STDIN,&c,&n);
00593                     }
00594                 }
00595                 switch (reg_al) {
00596                     case 0x1:
00597                     case 0x6:
00598                     case 0x7:
00599                     case 0x8:
00600                     case 0xa:
00601                         { 
00602                             Bit8u oldah=reg_ah;
00603                             reg_ah=reg_al;
00604                             DOS_21Handler();
00605                             reg_ah=oldah;
00606                         }
00607                         break;
00608                     default:
00609                         //              LOG_ERROR("DOS:0C:Illegal Flush STDIN Buffer call %d",reg_al);
00610                         reg_al=0;
00611                         break;
00612                 }
00613             }
00614             break;
00615             //TODO Find out the values for when reg_al!=0
00616             //TODO Hope this doesn't do anything special
00617         case 0x0d:      /* Disk Reset */
00618             //Sure let's reset a virtual disk
00619             break;  
00620         case 0x0e:      /* Select Default Drive */
00621             DOS_SetDefaultDrive(reg_dl);
00622             reg_al=DOS_DRIVES;
00623             break;
00624         case 0x0f:      /* Open File using FCB */
00625             if(DOS_FCBOpen(SegValue(ds),reg_dx)){
00626                 reg_al=0;
00627             }else{
00628                 reg_al=0xff;
00629             }
00630             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x0f FCB-fileopen used, result:al=%d",reg_al);
00631             break;
00632         case 0x10:      /* Close File using FCB */
00633             if(DOS_FCBClose(SegValue(ds),reg_dx)){
00634                 reg_al=0;
00635             }else{
00636                 reg_al=0xff;
00637             }
00638             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x10 FCB-fileclose used, result:al=%d",reg_al);
00639             break;
00640         case 0x11:      /* Find First Matching File using FCB */
00641             if(DOS_FCBFindFirst(SegValue(ds),reg_dx)) reg_al = 0x00;
00642             else reg_al = 0xFF;
00643             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x11 FCB-FindFirst used, result:al=%d",reg_al);
00644             break;
00645         case 0x12:      /* Find Next Matching File using FCB */
00646             if(DOS_FCBFindNext(SegValue(ds),reg_dx)) reg_al = 0x00;
00647             else reg_al = 0xFF;
00648             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x12 FCB-FindNext used, result:al=%d",reg_al);
00649             break;
00650         case 0x13:      /* Delete File using FCB */
00651             if (DOS_FCBDeleteFile(SegValue(ds),reg_dx)) reg_al = 0x00;
00652             else reg_al = 0xFF;
00653             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x16 FCB-Delete used, result:al=%d",reg_al);
00654             break;
00655         case 0x14:      /* Sequential read from FCB */
00656             reg_al = DOS_FCBRead(SegValue(ds),reg_dx,0);
00657             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x14 FCB-Read used, result:al=%d",reg_al);
00658             break;
00659         case 0x15:      /* Sequential write to FCB */
00660             reg_al=DOS_FCBWrite(SegValue(ds),reg_dx,0);
00661             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x15 FCB-Write used, result:al=%d",reg_al);
00662             break;
00663         case 0x16:      /* Create or truncate file using FCB */
00664             if (DOS_FCBCreate(SegValue(ds),reg_dx)) reg_al = 0x00;
00665             else reg_al = 0xFF;
00666             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x16 FCB-Create used, result:al=%d",reg_al);
00667             break;
00668         case 0x17:      /* Rename file using FCB */     
00669             if (DOS_FCBRenameFile(SegValue(ds),reg_dx)) reg_al = 0x00;
00670             else reg_al = 0xFF;
00671             break;
00672         case 0x1b:      /* Get allocation info for default drive */ 
00673             if (!DOS_GetAllocationInfo(0,&reg_cx,&reg_al,&reg_dx)) reg_al=0xff;
00674             break;
00675         case 0x1c:      /* Get allocation info for specific drive */
00676             if (!DOS_GetAllocationInfo(reg_dl,&reg_cx,&reg_al,&reg_dx)) reg_al=0xff;
00677             break;
00678         case 0x21:      /* Read random record from FCB */
00679             {
00680                 Bit16u toread=1;
00681                 reg_al = DOS_FCBRandomRead(SegValue(ds),reg_dx,&toread,true);
00682             }
00683             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x21 FCB-Random read used, result:al=%d",reg_al);
00684             break;
00685         case 0x22:      /* Write random record to FCB */
00686             {
00687                 Bit16u towrite=1;
00688                 reg_al=DOS_FCBRandomWrite(SegValue(ds),reg_dx,&towrite,true);
00689             }
00690             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x22 FCB-Random write used, result:al=%d",reg_al);
00691             break;
00692         case 0x23:      /* Get file size for FCB */
00693             if (DOS_FCBGetFileSize(SegValue(ds),reg_dx)) reg_al = 0x00;
00694             else reg_al = 0xFF;
00695             break;
00696         case 0x24:      /* Set Random Record number for FCB */
00697             DOS_FCBSetRandomRecord(SegValue(ds),reg_dx);
00698             break;
00699         case 0x27:      /* Random block read from FCB */
00700             reg_al = DOS_FCBRandomRead(SegValue(ds),reg_dx,&reg_cx,false);
00701             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x27 FCB-Random(block) read used, result:al=%d",reg_al);
00702             break;
00703         case 0x28:      /* Random Block write to FCB */
00704             reg_al=DOS_FCBRandomWrite(SegValue(ds),reg_dx,&reg_cx,false);
00705             LOG(LOG_FCB,LOG_NORMAL)("DOS:0x28 FCB-Random(block) write used, result:al=%d",reg_al);
00706             break;
00707         case 0x29:      /* Parse filename into FCB */
00708             {   
00709                 Bit8u difference;
00710                 char string[1024];
00711                 MEM_StrCopy(SegPhys(ds)+reg_si,string,1023); // 1024 toasts the stack
00712                 reg_al=FCB_Parsename(SegValue(es),reg_di,reg_al ,string, &difference);
00713                 reg_si+=difference;
00714             }
00715             LOG(LOG_FCB,LOG_NORMAL)("DOS:29:FCB Parse Filename, result:al=%d",reg_al);
00716             break;
00717         case 0x19:      /* Get current default drive */
00718             reg_al=DOS_GetDefaultDrive();
00719             break;
00720         case 0x1a:      /* Set Disk Transfer Area Address */
00721             dos.dta(RealMakeSeg(ds,reg_dx));
00722             break;
00723         case 0x25:      /* Set Interrupt Vector */
00724             RealSetVec(reg_al,RealMakeSeg(ds,reg_dx));
00725             break;
00726         case 0x26:      /* Create new PSP */
00727             /* TODO: DEBUG.EXE/DEBUG.COM as shipped with MS-DOS seems to reveal a bug where,
00728              *       when DEBUG.EXE calls this function and you're NOT loading a program to debug,
00729              *       the CP/M CALL FAR instruction's offset field will be off by 2. When does
00730              *       that happen, and how do we emulate that? */
00731             DOS_NewPSP(reg_dx,DOS_PSP(dos.psp()).GetSize());
00732             reg_al=0xf0;    /* al destroyed */      
00733             break;
00734         case 0x2a:      /* Get System Date */
00735             {
00736                 if(date_host_forced || IS_PC98_ARCH) {
00737                     // use BIOS to get system date
00738                     if (IS_PC98_ARCH) {
00739                         CPU_Push16(reg_ax);
00740                         CPU_Push16(reg_bx);
00741                         CPU_Push16(SegValue(es));
00742                         reg_sp -= 6;
00743 
00744                         reg_ah = 0;     // get time
00745                         reg_bx = reg_sp;
00746                         SegSet16(es,SegValue(ss));
00747                         CALLBACK_RunRealInt(0x1c);
00748 
00749                         Bitu memaddr = ((Bitu)SegValue(es) << 4u) + reg_bx;
00750 
00751                         reg_sp += 6;
00752                         SegSet16(es,CPU_Pop16());
00753                         reg_bx = CPU_Pop16();
00754                         reg_ax = CPU_Pop16();
00755 
00756                         reg_cx = 1900u + BCD2BIN(mem_readb(memaddr+0u));                  // year
00757                         if (reg_cx < 1980u) reg_cx += 100u;
00758                         reg_dh = BCD2BIN((unsigned int)mem_readb(memaddr+1) >> 4u);
00759                         reg_dl = BCD2BIN(mem_readb(memaddr+2));
00760                         reg_al = BCD2BIN(mem_readb(memaddr+1) & 0xFu);
00761                     }
00762                     else {
00763                         CPU_Push16(reg_ax);
00764                         reg_ah = 4;     // get RTC date
00765                         CALLBACK_RunRealInt(0x1a);
00766                         reg_ax = CPU_Pop16();
00767 
00768                         reg_ch = BCD2BIN(reg_ch);       // century
00769                         reg_cl = BCD2BIN(reg_cl);       // year
00770                         reg_cx = reg_ch * 100u + reg_cl; // compose century + year
00771                         reg_dh = BCD2BIN(reg_dh);       // month
00772                         reg_dl = BCD2BIN(reg_dl);       // day
00773 
00774                         // calculate day of week (we could of course read it from CMOS, but never mind)
00775                         unsigned int a = (14u - reg_dh) / 12u;
00776                         unsigned int y = reg_cl - a;
00777                         unsigned int m = reg_dh + 12u * a - 2u;
00778                         reg_al = (reg_dl + y + (y / 4u) - (y / 100u) + (y / 400u) + (31u * m) / 12u) % 7u;
00779                     }
00780                 } else {
00781                     reg_ax=0; // get time
00782                     CALLBACK_RunRealInt(0x1a);
00783                     if(reg_al) DOS_AddDays(reg_al);
00784                     int a = (14 - dos.date.month)/12;
00785                     int y = dos.date.year - a;
00786                     int m = dos.date.month + 12*a - 2;
00787                     reg_al=(dos.date.day+y+(y/4)-(y/100)+(y/400)+(31*m)/12) % 7;
00788                     reg_cx=dos.date.year;
00789                     reg_dh=dos.date.month;
00790                     reg_dl=dos.date.day;
00791                 }
00792             }
00793             break;
00794         case 0x2b:      /* Set System Date */
00795             if(date_host_forced) {
00796                 // unfortunately, BIOS does not return whether succeeded
00797                 // or not, so do a sanity check first
00798 
00799                 int maxday[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
00800 
00801                 if (reg_cx % 4 == 0 && (reg_cx % 100 != 0 || reg_cx % 400 == 0))
00802                     maxday[1]++;
00803 
00804                 if (reg_cx < 1980 || reg_cx > 9999 || reg_dh < 1 || reg_dh > 12 ||
00805                         reg_dl < 1 || reg_dl > maxday[reg_dh])
00806                 {
00807                     reg_al = 0xff;              // error!
00808                     break;                      // done
00809                 }
00810 
00811                 Bit16u cx = reg_cx;
00812 
00813                 CPU_Push16(reg_ax);
00814                 CPU_Push16(reg_cx);
00815                 CPU_Push16(reg_dx);
00816 
00817                 reg_al = 5;
00818                 reg_ch = BIN2BCD(cx / 100);     // century
00819                 reg_cl = BIN2BCD(cx % 100);     // year
00820                 reg_dh = BIN2BCD(reg_dh);       // month
00821                 reg_dl = BIN2BCD(reg_dl);       // day
00822 
00823                 CALLBACK_RunRealInt(0x1a);
00824 
00825                 reg_dx = CPU_Pop16();
00826                 reg_cx = CPU_Pop16();
00827                 reg_ax = CPU_Pop16();
00828 
00829                 reg_al = 0;                     // OK
00830                 break;
00831             }
00832             if (reg_cx<1980) { reg_al=0xff;break;}
00833             if ((reg_dh>12) || (reg_dh==0)) { reg_al=0xff;break;}
00834             if (reg_dl==0) { reg_al=0xff;break;}
00835             if (reg_dl>DOS_DATE_months[reg_dh]) {
00836                 if(!((reg_dh==2)&&(reg_cx%4 == 0)&&(reg_dl==29))) // february pass
00837                 { reg_al=0xff;break; }
00838             }
00839             dos.date.year=reg_cx;
00840             dos.date.month=reg_dh;
00841             dos.date.day=reg_dl;
00842             reg_al=0;
00843             break;
00844         case 0x2c: {    /* Get System Time */
00845             if(date_host_forced || IS_PC98_ARCH) {
00846                 // use BIOS to get RTC time
00847                 if (IS_PC98_ARCH) {
00848                     CPU_Push16(reg_ax);
00849                     CPU_Push16(reg_bx);
00850                     CPU_Push16(SegValue(es));
00851                     reg_sp -= 6;
00852 
00853                     reg_ah = 0;     // get time
00854                     reg_bx = reg_sp;
00855                     SegSet16(es,SegValue(ss));
00856                     CALLBACK_RunRealInt(0x1c);
00857 
00858                     Bitu memaddr = ((PhysPt)SegValue(es) << 4u) + reg_bx;
00859 
00860                     reg_sp += 6;
00861                     SegSet16(es,CPU_Pop16());
00862                     reg_bx = CPU_Pop16();
00863                     reg_ax = CPU_Pop16();
00864 
00865                     reg_ch = BCD2BIN(mem_readb(memaddr+3));     // hours
00866                     reg_cl = BCD2BIN(mem_readb(memaddr+4));     // minutes
00867                     reg_dh = BCD2BIN(mem_readb(memaddr+5));     // seconds
00868 
00869                     reg_dl = 0;
00870                 }
00871                 else {
00872                     CPU_Push16(reg_ax);
00873 
00874                     reg_ah = 2;     // get RTC time
00875                     CALLBACK_RunRealInt(0x1a);
00876 
00877                     reg_ax = CPU_Pop16();
00878 
00879                     reg_ch = BCD2BIN(reg_ch);       // hours
00880                     reg_cl = BCD2BIN(reg_cl);       // minutes
00881                     reg_dh = BCD2BIN(reg_dh);       // seconds
00882 
00883                     // calculate milliseconds (% 20 to prevent overflow, .55ms has period of 20)
00884                     // direcly read BIOS_TIMER, don't want to destroy regs by calling int 1a
00885                     reg_dl = (Bit8u)((mem_readd(BIOS_TIMER) % 20) * 55 % 100);
00886                 }
00887                 break;
00888             }
00889             reg_ax=0; // get time
00890             CALLBACK_RunRealInt(0x1a);
00891             if(reg_al) DOS_AddDays(reg_al);
00892             reg_ah=0x2c;
00893 
00894             Bitu ticks=((Bitu)reg_cx<<16)|reg_dx;
00895             if(time_start<=ticks) ticks-=time_start;
00896             Bitu time=(Bitu)((100.0/((double)PIT_TICK_RATE/65536.0)) * (double)ticks);
00897 
00898             reg_dl=(Bit8u)((Bitu)time % 100); // 1/100 seconds
00899             time/=100;
00900             reg_dh=(Bit8u)((Bitu)time % 60); // seconds
00901             time/=60;
00902             reg_cl=(Bit8u)((Bitu)time % 60); // minutes
00903             time/=60;
00904             reg_ch=(Bit8u)((Bitu)time % 24); // hours
00905 
00906             //Simulate DOS overhead for timing-sensitive games
00907             //Robomaze 2
00908             overhead();
00909             break;
00910         }
00911         case 0x2d:      /* Set System Time */
00912             if(date_host_forced) {
00913                 // unfortunately, BIOS does not return whether succeeded
00914                 // or not, so do a sanity check first
00915                 if (reg_ch > 23 || reg_cl > 59 || reg_dh > 59 || reg_dl > 99)
00916                 {
00917                     reg_al = 0xff;      // error!
00918                     break;              // done
00919                 }
00920 
00921                 // timer ticks every 55ms
00922                 Bit32u ticks = ((((reg_ch * 60u + reg_cl) * 60u + reg_dh) * 100u) + reg_dl) * 10u / 55u;
00923 
00924                 CPU_Push16(reg_ax);
00925                 CPU_Push16(reg_cx);
00926                 CPU_Push16(reg_dx);
00927 
00928                 // use BIOS to set RTC time
00929                 reg_ah = 3;     // set RTC time
00930                 reg_ch = BIN2BCD(reg_ch);       // hours
00931                 reg_cl = BIN2BCD(reg_cl);       // minutes
00932                 reg_dh = BIN2BCD(reg_dh);       // seconds
00933                 reg_dl = 0;                     // no DST
00934 
00935                 CALLBACK_RunRealInt(0x1a);
00936 
00937                 // use BIOS to update clock ticks to sync time
00938                 // could set directly, but setting is safer to do via dedicated call (at least in theory)
00939                 reg_ah = 1;     // set system time
00940                 reg_cx = (Bit16u)(ticks >> 16);
00941                 reg_dx = (Bit16u)(ticks & 0xffff);
00942 
00943                 CALLBACK_RunRealInt(0x1a);
00944 
00945                 reg_dx = CPU_Pop16();
00946                 reg_cx = CPU_Pop16();
00947                 reg_ax = CPU_Pop16();
00948 
00949                 reg_al = 0;                     // OK
00950                 break;
00951             }
00952             LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Set System Time not supported");
00953             //Check input parameters nonetheless
00954             if( reg_ch > 23 || reg_cl > 59 || reg_dh > 59 || reg_dl > 99 )
00955                 reg_al = 0xff; 
00956             else { //Allow time to be set to zero. Restore the orginal time for all other parameters. (QuickBasic)
00957                 if (reg_cx == 0 && reg_dx == 0) {time_start = mem_readd(BIOS_TIMER);LOG_MSG("Warning: game messes with DOS time!");}
00958                 else time_start = 0;
00959                 reg_al = 0;
00960             }
00961             break;
00962         case 0x2e:      /* Set Verify flag */
00963             dos.verify=(reg_al==1);
00964             break;
00965         case 0x2f:      /* Get Disk Transfer Area */
00966             SegSet16(es,RealSeg(dos.dta()));
00967             reg_bx=RealOff(dos.dta());
00968             break;
00969         case 0x30:      /* Get DOS Version */
00970             if (reg_al==0) reg_bh=0xFF;     /* Fake Microsoft DOS */
00971             if (reg_al==1 && DOS_IS_IN_HMA()) reg_bh=0x10;      /* DOS is in HMA? */
00972             reg_al=dos.version.major;
00973             reg_ah=dos.version.minor;
00974             /* Serialnumber */
00975             reg_bl=0x00;
00976             reg_cx=0x0000;
00977             break;
00978         case 0x31:      /* Terminate and stay resident */
00979             // Important: This service does not set the carry flag!
00980             DOS_ResizeMemory(dos.psp(),&reg_dx);
00981             DOS_Terminate(dos.psp(),true,reg_al);
00982             if (DOS_BreakINT23InProgress) throw int(0); /* HACK: Ick */
00983             dos_program_running = false;
00984             break;
00985         case 0x1f: /* Get drive parameter block for default drive */
00986         case 0x32: /* Get drive parameter block for specific drive */
00987             {   /* Officially a dpb should be returned as well. The disk detection part is implemented */
00988                 Bit8u drive=reg_dl;
00989                 if (!drive || reg_ah==0x1f) drive = DOS_GetDefaultDrive();
00990                 else drive--;
00991                 if (Drives[drive]) {
00992                     reg_al = 0x00;
00993                     SegSet16(ds,dos.tables.dpb);
00994                     reg_bx = drive;//Faking only the first entry (that is the driveletter)
00995                     LOG(LOG_DOSMISC,LOG_ERROR)("Get drive parameter block.");
00996                 } else {
00997                     reg_al=0xff;
00998                 }
00999             }
01000             break;
01001         case 0x33:      /* Extended Break Checking */
01002             switch (reg_al) {
01003                 case 0:reg_dl=dos.breakcheck;break;         /* Get the breakcheck flag */
01004                 case 1:dos.breakcheck=(reg_dl>0);break;     /* Set the breakcheck flag */
01005                 case 2:{bool old=dos.breakcheck;dos.breakcheck=(reg_dl>0);reg_dl=old;}break;
01006                 case 3: /* Get cpsw */
01007                        /* Fallthrough */
01008                 case 4: /* Set cpsw */
01009                        LOG(LOG_DOSMISC,LOG_ERROR)("Someone playing with cpsw %x",reg_ax);
01010                        break;
01011                 case 5:reg_dl=3;break;//TODO should be z                        /* Always boot from c: :) */
01012                 case 6:                                         /* Get true version number */
01013                        reg_bl=dos.version.major;
01014                        reg_bh=dos.version.minor;
01015                        reg_dl=dos.version.revision;
01016                        reg_dh=DOS_IS_IN_HMA()?0x10:0x00;                       /* Dos in HMA?? */
01017                        break;
01018                 case 7:
01019                        break;
01020                 default:
01021                        LOG(LOG_DOSMISC,LOG_ERROR)("Weird 0x33 call %2X",reg_al);
01022                        reg_al =0xff;
01023                        break;
01024             }
01025             break;
01026         case 0x34:      /* Get INDos Flag */
01027             SegSet16(es,DOS_SDA_SEG);
01028             reg_bx=DOS_SDA_OFS + 0x01;
01029             break;
01030         case 0x35:      /* Get interrupt vector */
01031             reg_bx=real_readw(0,((Bit16u)reg_al)*4);
01032             SegSet16(es,real_readw(0,((Bit16u)reg_al)*4+2));
01033             break;
01034         case 0x36:      /* Get Free Disk Space */
01035             {
01036                 Bit16u bytes,clusters,free;
01037                 Bit8u sectors;
01038                 if (DOS_GetFreeDiskSpace(reg_dl,&bytes,&sectors,&clusters,&free)) {
01039                     reg_ax=sectors;
01040                     reg_bx=free;
01041                     reg_cx=bytes;
01042                     reg_dx=clusters;
01043                 } else {
01044                     Bit8u drive=reg_dl;
01045                     if (drive==0) drive=DOS_GetDefaultDrive();
01046                     else drive--;
01047                     if (drive<2) {
01048                         // floppy drive, non-present drivesdisks issue floppy check through int24
01049                         // (critical error handler); needed for Mixed up Mother Goose (hook)
01050                         //                  CALLBACK_RunRealInt(0x24);
01051                     }
01052                     reg_ax=0xffff;  // invalid drive specified
01053                 }
01054             }
01055             break;
01056         case 0x37:      /* Get/Set Switch char Get/Set Availdev thing */
01057             //TODO  Give errors for these functions to see if anyone actually uses this shit-
01058             switch (reg_al) {
01059                 case 0:
01060                     reg_al=0;reg_dl=0x2f;break;  /* always return '/' like dos 5.0+ */
01061                 case 1:
01062                     reg_al=0;break;
01063                 case 2:
01064                     reg_al=0;reg_dl=0x2f;break;
01065                 case 3:
01066                     reg_al=0;break;
01067             };
01068             LOG(LOG_MISC,LOG_ERROR)("DOS:0x37:Call for not supported switchchar");
01069             break;
01070         case 0x38:                  /* Set Country Code */  
01071             if (reg_al==0) {        /* Get country specidic information */
01072                 PhysPt dest = SegPhys(ds)+reg_dx;
01073                 MEM_BlockWrite(dest,dos.tables.country,0x18);
01074                 reg_ax = reg_bx = 0x01;
01075                 CALLBACK_SCF(false);
01076                 break;
01077             } else {                /* Set country code */
01078                 LOG(LOG_MISC,LOG_ERROR)("DOS:Setting country code not supported");
01079             }
01080             CALLBACK_SCF(true);
01081             break;
01082         case 0x39:      /* MKDIR Create directory */
01083             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01084             if (DOS_MakeDir(name1)) {
01085                 reg_ax=0x05;    /* ax destroyed */
01086                 CALLBACK_SCF(false);
01087             } else {
01088                 reg_ax=dos.errorcode;
01089                 CALLBACK_SCF(true);
01090             }
01091             break;
01092         case 0x3a:      /* RMDIR Remove directory */
01093             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01094             if  (DOS_RemoveDir(name1)) {
01095                 reg_ax=0x05;    /* ax destroyed */
01096                 CALLBACK_SCF(false);
01097             } else {
01098                 reg_ax=dos.errorcode;
01099                 CALLBACK_SCF(true);
01100                 LOG(LOG_MISC,LOG_NORMAL)("Remove dir failed on %s with error %X",name1,dos.errorcode);
01101             }
01102             break;
01103         case 0x3b:      /* CHDIR Set current directory */
01104             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01105             if  (DOS_ChangeDir(name1)) {
01106                 reg_ax=0x00;    /* ax destroyed */
01107                 CALLBACK_SCF(false);
01108             } else {
01109                 reg_ax=dos.errorcode;
01110                 CALLBACK_SCF(true);
01111             }
01112             break;
01113         case 0x3c:      /* CREATE Create of truncate file */
01114             unmask_irq0 |= disk_io_unmask_irq0;
01115             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01116             if (DOS_CreateFile(name1,reg_cx,&reg_ax)) {
01117                 CALLBACK_SCF(false);
01118             } else {
01119                 reg_ax=dos.errorcode;
01120                 CALLBACK_SCF(true);
01121             }
01122             diskio_delay(2048);
01123             break;
01124         case 0x3d:      /* OPEN Open existing file */
01125             unmask_irq0 |= disk_io_unmask_irq0;
01126             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01127             if (DOS_OpenFile(name1,reg_al,&reg_ax)) {
01128                 CALLBACK_SCF(false);
01129             } else {
01130                 reg_ax=dos.errorcode;
01131                 CALLBACK_SCF(true);
01132             }
01133             diskio_delay(1024);
01134             break;
01135         case 0x3e:      /* CLOSE Close file */
01136             unmask_irq0 |= disk_io_unmask_irq0;
01137             if (DOS_CloseFile(reg_bx)) {
01138                 //          reg_al=0x01;    /* al destroyed. Refcount */
01139                 CALLBACK_SCF(false);
01140             } else {
01141                 reg_ax=dos.errorcode;
01142                 CALLBACK_SCF(true);
01143             }
01144             diskio_delay(512);
01145             break;
01146         case 0x3f:      /* READ Read from file or device */
01147             unmask_irq0 |= disk_io_unmask_irq0;
01148             /* TODO: If handle is STDIN and not binary do CTRL+C checking */
01149             { 
01150                 Bit16u toread=reg_cx;
01151 
01152                 /* if the offset and size exceed the end of the 64KB segment,
01153                  * truncate the read according to observed MS-DOS 5.0 behavior
01154                  * where the actual byte count read is 64KB minus (reg_dx % 16).
01155                  *
01156                  * This is needed for "Dark Purpose" to read it's DAT file
01157                  * correctly, which calls INT 21h AH=3Fh with DX=0004h and CX=FFFFh
01158                  * and will mis-render it's fonts, images, and color palettes
01159                  * if we do not do this.
01160                  *
01161                  * Ref: http://files.scene.org/get/mirrors/hornet/demos/1995/d/darkp.zip */
01162                 if (((uint32_t)toread+(uint32_t)reg_dx) > 0xFFFFUL && (reg_dx & 0xFU) != 0U) {
01163                     Bit16u nuread = (Bit16u)(0x10000UL - (reg_dx & 0xF)); /* FIXME: If MS-DOS 5.0 truncates it any farther I need to know! */
01164 
01165                     if (nuread > toread) nuread = toread;
01166                     LOG_MSG("INT 21h READ warning: DX=%04xh CX=%04xh exceeds 64KB, truncating to %04xh",reg_dx,toread,nuread);
01167                     toread = nuread;
01168                 }
01169 
01170                 dos.echo=true;
01171                 if (DOS_ReadFile(reg_bx,dos_copybuf,&toread)) {
01172                     MEM_BlockWrite(SegPhys(ds)+reg_dx,dos_copybuf,toread);
01173                     reg_ax=toread;
01174                     CALLBACK_SCF(false);
01175                 } else {
01176                     reg_ax=dos.errorcode;
01177                     CALLBACK_SCF(true);
01178                 }
01179                 diskio_delay(reg_ax);
01180                 dos.echo=false;
01181                 break;
01182             }
01183         case 0x40:                  /* WRITE Write to file or device */
01184             unmask_irq0 |= disk_io_unmask_irq0;
01185             {
01186                 Bit16u towrite=reg_cx;
01187 
01188                 /* if the offset and size exceed the end of the 64KB segment,
01189                  * truncate the write according to observed MS-DOS 5.0 READ behavior
01190                  * where the actual byte count written is 64KB minus (reg_dx % 16).
01191                  *
01192                  * This is copy-paste of AH=3Fh read handling because it's likely
01193                  * that MS-DOS probably does the same with write as well, though
01194                  * this has not yet been confirmed. --J.C. */
01195                 if (((uint32_t)towrite+(uint32_t)reg_dx) > 0xFFFFUL && (reg_dx & 0xFU) != 0U) {
01196                     Bit16u nuwrite = (Bit16u)(0x10000UL - (reg_dx & 0xF)); /* FIXME: If MS-DOS 5.0 truncates it any farther I need to know! */
01197 
01198                     if (nuwrite > towrite) nuwrite = towrite;
01199                     LOG_MSG("INT 21h WRITE warning: DX=%04xh CX=%04xh exceeds 64KB, truncating to %04xh",reg_dx,towrite,nuwrite);
01200                     towrite = nuwrite;
01201                 }
01202 
01203                 MEM_BlockRead(SegPhys(ds)+reg_dx,dos_copybuf,towrite);
01204                 if (DOS_WriteFile(reg_bx,dos_copybuf,&towrite)) {
01205                     reg_ax=towrite;
01206                     CALLBACK_SCF(false);
01207                 } else {
01208                     reg_ax=dos.errorcode;
01209                     CALLBACK_SCF(true);
01210                 }
01211                 diskio_delay(reg_ax);
01212                 break;
01213             };
01214         case 0x41:                  /* UNLINK Delete file */
01215             unmask_irq0 |= disk_io_unmask_irq0;
01216             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01217             if (DOS_UnlinkFile(name1)) {
01218                 CALLBACK_SCF(false);
01219             } else {
01220                 reg_ax=dos.errorcode;
01221                 CALLBACK_SCF(true);
01222             }
01223             diskio_delay(1024);
01224             break;
01225         case 0x42:                  /* LSEEK Set current file position */
01226             unmask_irq0 |= disk_io_unmask_irq0;
01227             {
01228                 Bit32u pos=((Bit32u)reg_cx << 16u) + reg_dx;
01229                 if (DOS_SeekFile(reg_bx,&pos,reg_al)) {
01230                     reg_dx=(Bit16u)((unsigned int)pos >> 16u);
01231                     reg_ax=(Bit16u)(pos & 0xFFFF);
01232                     CALLBACK_SCF(false);
01233                 } else {
01234                     reg_ax=dos.errorcode;
01235                     CALLBACK_SCF(true);
01236                 }
01237                 diskio_delay(32);
01238                 break;
01239             }
01240         case 0x43:                  /* Get/Set file attributes */
01241             unmask_irq0 |= disk_io_unmask_irq0;
01242             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01243             switch (reg_al) {
01244                 case 0x00:              /* Get */
01245                     {
01246                         Bit16u attr_val=reg_cx;
01247                         if (DOS_GetFileAttr(name1,&attr_val)) {
01248                             reg_cx=attr_val;
01249                             reg_ax=attr_val; /* Undocumented */   
01250                             CALLBACK_SCF(false);
01251                         } else {
01252                             CALLBACK_SCF(true);
01253                             reg_ax=dos.errorcode;
01254                         }
01255                         break;
01256                     };
01257                 case 0x01:              /* Set */
01258                     LOG(LOG_MISC,LOG_ERROR)("DOS:Set File Attributes for %s not supported",name1);
01259                     if (DOS_SetFileAttr(name1,reg_cx)) {
01260                         reg_ax=0x202;   /* ax destroyed */
01261                         CALLBACK_SCF(false);
01262                     } else {
01263                         CALLBACK_SCF(true);
01264                         reg_ax=dos.errorcode;
01265                     }
01266                     break;
01267                 default:
01268                     LOG(LOG_MISC,LOG_ERROR)("DOS:0x43:Illegal subfunction %2X",reg_al);
01269                     reg_ax=1;
01270                     CALLBACK_SCF(true);
01271                     break;
01272             }
01273             break;
01274         case 0x44:                  /* IOCTL Functions */
01275             if (DOS_IOCTL()) {
01276                 CALLBACK_SCF(false);
01277             } else {
01278                 reg_ax=dos.errorcode;
01279                 CALLBACK_SCF(true);
01280             }
01281             break;
01282         case 0x45:                  /* DUP Duplicate file handle */
01283             if (DOS_DuplicateEntry(reg_bx,&reg_ax)) {
01284                 CALLBACK_SCF(false);
01285             } else {
01286                 reg_ax=dos.errorcode;
01287                 CALLBACK_SCF(true);
01288             }
01289             break;
01290         case 0x46:                  /* DUP2,FORCEDUP Force duplicate file handle */
01291             if (DOS_ForceDuplicateEntry(reg_bx,reg_cx)) {
01292                 reg_ax=reg_cx; //Not all sources agree on it.
01293                 CALLBACK_SCF(false);
01294             } else {
01295                 reg_ax=dos.errorcode;
01296                 CALLBACK_SCF(true);
01297             }
01298             break;
01299         case 0x47:                  /* CWD Get current directory */
01300             if (DOS_GetCurrentDir(reg_dl,name1)) {
01301                 MEM_BlockWrite(SegPhys(ds)+reg_si,name1,(Bitu)(strlen(name1)+1));   
01302                 reg_ax=0x0100;
01303                 CALLBACK_SCF(false);
01304             } else {
01305                 reg_ax=dos.errorcode;
01306                 CALLBACK_SCF(true);
01307             }
01308             break;
01309         case 0x48:                  /* Allocate memory */
01310             {
01311                 Bit16u size=reg_bx;Bit16u seg;
01312                 if (DOS_AllocateMemory(&seg,&size)) {
01313                     reg_ax=seg;
01314                     CALLBACK_SCF(false);
01315                 } else {
01316                     reg_ax=dos.errorcode;
01317                     reg_bx=size;
01318                     CALLBACK_SCF(true);
01319                 }
01320                 break;
01321             }
01322         case 0x49:                  /* Free memory */
01323             if (DOS_FreeMemory(SegValue(es))) {
01324                 CALLBACK_SCF(false);
01325             } else {            
01326                 reg_ax=dos.errorcode;
01327                 CALLBACK_SCF(true);
01328             }
01329             break;
01330         case 0x4a:                  /* Resize memory block */
01331             {
01332                 Bit16u size=reg_bx;
01333                 if (DOS_ResizeMemory(SegValue(es),&size)) {
01334                     reg_ax=SegValue(es);
01335                     CALLBACK_SCF(false);
01336                 } else {            
01337                     reg_ax=dos.errorcode;
01338                     reg_bx=size;
01339                     CALLBACK_SCF(true);
01340                 }
01341                 break;
01342             }
01343         case 0x4b:                  /* EXEC Load and/or execute program */
01344             { 
01345                 MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01346                 LOG(LOG_EXEC,LOG_NORMAL)("Execute %s %d",name1,reg_al);
01347                 if (!DOS_Execute(name1,SegPhys(es)+reg_bx,reg_al)) {
01348                     reg_ax=dos.errorcode;
01349                     CALLBACK_SCF(true);
01350                 }
01351                 dos_program_running = true;
01352             }
01353             break;
01354             //TODO Check for use of execution state AL=5
01355         case 0x4c:                  /* EXIT Terminate with return code */
01356             DOS_Terminate(dos.psp(),false,reg_al);
01357             if (DOS_BreakINT23InProgress) throw int(0); /* HACK: Ick */
01358             dos_program_running = false;
01359             break;
01360         case 0x4d:                  /* Get Return code */
01361             reg_al=dos.return_code;/* Officially read from SDA and clear when read */
01362             reg_ah=dos.return_mode;
01363             break;
01364         case 0x4e:                  /* FINDFIRST Find first matching file */
01365             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01366             if (DOS_FindFirst(name1,reg_cx)) {
01367                 CALLBACK_SCF(false);    
01368                 reg_ax=0;           /* Undocumented */
01369             } else {
01370                 reg_ax=dos.errorcode;
01371                 CALLBACK_SCF(true);
01372             };
01373             break;       
01374         case 0x4f:                  /* FINDNEXT Find next matching file */
01375             if (DOS_FindNext()) {
01376                 CALLBACK_SCF(false);
01377                 /* reg_ax=0xffff;*/         /* Undocumented */
01378                 reg_ax=0;               /* Undocumented:Qbix Willy beamish */
01379             } else {
01380                 reg_ax=dos.errorcode;
01381                 CALLBACK_SCF(true);
01382             };
01383             break;      
01384         case 0x50:                  /* Set current PSP */
01385             dos.psp(reg_bx);
01386             break;
01387         case 0x51:                  /* Get current PSP */
01388             reg_bx=dos.psp();
01389             break;
01390         case 0x52: {                /* Get list of lists */
01391             RealPt addr=dos_infoblock.GetPointer();
01392             SegSet16(es,RealSeg(addr));
01393             reg_bx=RealOff(addr);
01394             LOG(LOG_DOSMISC,LOG_NORMAL)("Call is made for list of lists - let's hope for the best");
01395             break; }
01396             //TODO Think hard how shit this is gonna be
01397             //And will any game ever use this :)
01398         case 0x53:                  /* Translate BIOS parameter block to drive parameter block */
01399             E_Exit("Unhandled Dos 21 call %02X",reg_ah);
01400             break;
01401         case 0x54:                  /* Get verify flag */
01402             reg_al=dos.verify?1:0;
01403             break;
01404         case 0x55:                  /* Create Child PSP*/
01405             DOS_ChildPSP(reg_dx,reg_si);
01406             dos.psp(reg_dx);
01407             reg_al=0xf0;    /* al destroyed */
01408             break;
01409         case 0x56:                  /* RENAME Rename file */
01410             MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01411             MEM_StrCopy(SegPhys(es)+reg_di,name2,DOSNAMEBUF);
01412             if (DOS_Rename(name1,name2)) {
01413                 CALLBACK_SCF(false);            
01414             } else {
01415                 reg_ax=dos.errorcode;
01416                 CALLBACK_SCF(true);
01417             }
01418             break;      
01419         case 0x57:                  /* Get/Set File's Date and Time */
01420             if (reg_al==0x00) {
01421                 if (DOS_GetFileDate(reg_bx,&reg_cx,&reg_dx)) {
01422                     CALLBACK_SCF(false);
01423                 } else {
01424                     CALLBACK_SCF(true);
01425                 }
01426             } else if (reg_al==0x01) {
01427                 if (DOS_SetFileDate(reg_bx,reg_cx,reg_dx)) {
01428                     CALLBACK_SCF(false);
01429                 } else {
01430                     CALLBACK_SCF(true);
01431                 }
01432             } else {
01433                 LOG(LOG_DOSMISC,LOG_ERROR)("DOS:57:Unsupported subtion %X",reg_al);
01434             }
01435             break;
01436         case 0x58:                  /* Get/Set Memory allocation strategy */
01437             switch (reg_al) {
01438                 case 0:                 /* Get Strategy */
01439                     reg_ax=DOS_GetMemAllocStrategy();
01440                     break;
01441                 case 1:                 /* Set Strategy */
01442                     if (DOS_SetMemAllocStrategy(reg_bx)) CALLBACK_SCF(false);
01443                     else {
01444                         reg_ax=1;
01445                         CALLBACK_SCF(true);
01446                     }
01447                     break;
01448                 case 2:                 /* Get UMB Link Status */
01449                     reg_al=dos_infoblock.GetUMBChainState()&1;
01450                     CALLBACK_SCF(false);
01451                     break;
01452                 case 3:                 /* Set UMB Link Status */
01453                     if (DOS_LinkUMBsToMemChain(reg_bx)) CALLBACK_SCF(false);
01454                     else {
01455                         reg_ax=1;
01456                         CALLBACK_SCF(true);
01457                     }
01458                     break;
01459                 default:
01460                     LOG(LOG_DOSMISC,LOG_ERROR)("DOS:58:Not Supported Set//Get memory allocation call %X",reg_al);
01461                     reg_ax=1;
01462                     CALLBACK_SCF(true);
01463             }
01464             break;
01465         case 0x59:                  /* Get Extended error information */
01466             reg_ax=dos.errorcode;
01467             if (dos.errorcode==DOSERR_FILE_NOT_FOUND || dos.errorcode==DOSERR_PATH_NOT_FOUND) {
01468                 reg_bh=8;   //Not Found error class (Road Hog)
01469             } else {
01470                 reg_bh=0;   //Unspecified error class
01471             }
01472             reg_bl=1;   //Retry retry retry
01473             reg_ch=0;   //Unkown error locus
01474             break;
01475         case 0x5a:                  /* Create temporary file */
01476             {
01477                 Bit16u handle;
01478                 MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01479                 if (DOS_CreateTempFile(name1,&handle)) {
01480                     reg_ax=handle;
01481                     MEM_BlockWrite(SegPhys(ds)+reg_dx,name1,(Bitu)(strlen(name1)+1));
01482                     CALLBACK_SCF(false);
01483                 } else {
01484                     reg_ax=dos.errorcode;
01485                     CALLBACK_SCF(true);
01486                 }
01487             }
01488             break;
01489         case 0x5b:                  /* Create new file */
01490             {
01491                 MEM_StrCopy(SegPhys(ds)+reg_dx,name1,DOSNAMEBUF);
01492                 Bit16u handle;
01493                 if (DOS_OpenFile(name1,0,&handle)) {
01494                     DOS_CloseFile(handle);
01495                     DOS_SetError(DOSERR_FILE_ALREADY_EXISTS);
01496                     reg_ax=dos.errorcode;
01497                     CALLBACK_SCF(true);
01498                     break;
01499                 }
01500                 if (DOS_CreateFile(name1,reg_cx,&handle)) {
01501                     reg_ax=handle;
01502                     CALLBACK_SCF(false);
01503                 } else {
01504                     reg_ax=dos.errorcode;
01505                     CALLBACK_SCF(true);
01506                 }
01507                 break;
01508             }
01509         case 0x5c:  {       /* FLOCK File region locking */
01510             /* ert, 20100711: Locking extensions */
01511             Bit32u pos=((unsigned int)reg_cx << 16u) + reg_dx;
01512             Bit32u size=((unsigned int)reg_si << 16u) + reg_di;
01513             //LOG_MSG("LockFile: BX=%d, AL=%d, POS=%d, size=%d", reg_bx, reg_al, pos, size);
01514             if (DOS_LockFile(reg_bx,reg_al,pos, size)) {
01515                 reg_ax=0;
01516                 CALLBACK_SCF(false);
01517             } else {
01518                 reg_ax=dos.errorcode;
01519                 CALLBACK_SCF(true);
01520             }
01521             break; }
01522             /*
01523                DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
01524                reg_ax = dos.errorcode;
01525                CALLBACK_SCF(true);
01526                break;
01527                */
01528         case 0x5d:                  /* Network Functions */
01529             if(reg_al == 0x06) {
01530                 /* FIXME: I'm still not certain, @emendelson, why this matters so much
01531                  *        to WordPerfect 5.1 and 6.2 and why it causes problems otherwise.
01532                  *        DOSBox and DOSBox-X only use the first 0x1A bytes anyway. */
01533                 SegSet16(ds,DOS_SDA_SEG);
01534                 reg_si = DOS_SDA_OFS;
01535                 reg_cx = DOS_SDA_SEG_SIZE;  // swap if in dos
01536                 reg_dx = 0x1a;  // swap always (NTS: Size of DOS SDA structure in dos_inc)
01537                 LOG(LOG_DOSMISC,LOG_ERROR)("Get SDA, Let's hope for the best!");
01538             }
01539             break;
01540         case 0x5f:                  /* Network redirection */
01541 #if defined(WIN32) && !defined(HX_DOS)
01542             switch(reg_al)
01543             {
01544                 case    0x34:   //Set pipe state
01545                     if(Network_SetNamedPipeState(reg_bx,reg_cx,reg_ax))
01546                         CALLBACK_SCF(false);
01547                     else    CALLBACK_SCF(true);
01548                     break;
01549                 case    0x35:   //Peek pipe
01550                     {
01551                         Bit16u  uTmpSI=reg_si;
01552                         if(Network_PeekNamedPipe(reg_bx,
01553                                     dos_copybuf,reg_cx,
01554                                     reg_cx,reg_si,reg_dx,
01555                                     reg_di,reg_ax))
01556                         {
01557                             MEM_BlockWrite(SegPhys(ds)+uTmpSI,dos_copybuf,reg_cx);
01558                             CALLBACK_SCF(false);
01559                         }
01560                         else    CALLBACK_SCF(true);
01561                     }
01562                     break;
01563                 case    0x36:   //Transcate pipe
01564                     //Inbuffer:the buffer to be written to pipe
01565                     MEM_BlockRead(SegPhys(ds)+reg_si,dos_copybuf_second,reg_cx);
01566 
01567                     if(Network_TranscateNamedPipe(reg_bx,
01568                                 dos_copybuf_second,reg_cx,
01569                                 dos_copybuf,reg_dx,
01570                                 reg_cx,reg_ax))
01571                     {
01572                         //Outbuffer:the buffer to receive data from pipe
01573                         MEM_BlockWrite(SegPhys(es)+reg_di,dos_copybuf,reg_cx);
01574                         CALLBACK_SCF(false);
01575                     }
01576                     else    CALLBACK_SCF(true);
01577                     break;
01578                 default:
01579                     reg_ax=0x0001;          //Failing it
01580                     CALLBACK_SCF(true);
01581                     break;
01582             }
01583 #else
01584             reg_ax=0x0001;          //Failing it
01585             CALLBACK_SCF(true);
01586 #endif
01587             break; 
01588         case 0x60:                  /* Canonicalize filename or path */
01589             MEM_StrCopy(SegPhys(ds)+reg_si,name1,DOSNAMEBUF);
01590             if (DOS_Canonicalize(name1,name2)) {
01591                 MEM_BlockWrite(SegPhys(es)+reg_di,name2,(Bitu)(strlen(name2)+1));   
01592                 CALLBACK_SCF(false);
01593             } else {
01594                 reg_ax=dos.errorcode;
01595                 CALLBACK_SCF(true);
01596             }
01597             break;
01598         case 0x62:                  /* Get Current PSP Address */
01599             reg_bx=dos.psp();
01600             break;
01601         case 0x63:                  /* DOUBLE BYTE CHARACTER SET */
01602             if(reg_al == 0 && dos.tables.dbcs != 0) {
01603                 SegSet16(ds,RealSeg(dos.tables.dbcs));
01604                 reg_si=RealOff(dos.tables.dbcs);        
01605                 reg_al = 0;
01606                 CALLBACK_SCF(false); //undocumented
01607             } else reg_al = 0xff; //Doesn't officially touch carry flag
01608             break;
01609         case 0x64:                  /* Set device driver lookahead flag */
01610             LOG(LOG_DOSMISC,LOG_NORMAL)("set driver look ahead flag");
01611             break;
01612         case 0x65:                  /* Get extented country information and a lot of other useless shit*/
01613             { /* Todo maybe fully support this for now we set it standard for USA */ 
01614                 LOG(LOG_DOSMISC,LOG_ERROR)("DOS:65:Extended country information call %X",reg_ax);
01615                 if((reg_al <=  0x07) && (reg_cx < 0x05)) {
01616                     DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
01617                     CALLBACK_SCF(true);
01618                     break;
01619                 }
01620                 Bitu len = 0; /* For 0x21 and 0x22 */
01621                 PhysPt data=SegPhys(es)+reg_di;
01622                 switch (reg_al) {
01623                     case 0x01:
01624                         mem_writeb(data + 0x00,reg_al);
01625                         mem_writew(data + 0x01,0x26);
01626                         mem_writew(data + 0x03,1);
01627                         if(reg_cx > 0x06 ) mem_writew(data+0x05,dos.loaded_codepage);
01628                         if(reg_cx > 0x08 ) {
01629                             Bitu amount = (reg_cx>=0x29u)?0x22u:(reg_cx-7u);
01630                             MEM_BlockWrite(data + 0x07,dos.tables.country,amount);
01631                             reg_cx=(reg_cx>=0x29)?0x29:reg_cx;
01632                         }
01633                         CALLBACK_SCF(false);
01634                         break;
01635                     case 0x05: // Get pointer to filename terminator table
01636                         mem_writeb(data + 0x00, reg_al);
01637                         mem_writed(data + 0x01, dos.tables.filenamechar);
01638                         reg_cx = 5;
01639                         CALLBACK_SCF(false);
01640                         break;
01641                     case 0x02: // Get pointer to uppercase table
01642                         mem_writeb(data + 0x00, reg_al);
01643                         mem_writed(data + 0x01, dos.tables.upcase);
01644                         reg_cx = 5;
01645                         CALLBACK_SCF(false);
01646                         break;
01647                     case 0x06: // Get pointer to collating sequence table
01648                         mem_writeb(data + 0x00, reg_al);
01649                         mem_writed(data + 0x01, dos.tables.collatingseq);
01650                         reg_cx = 5;
01651                         CALLBACK_SCF(false);
01652                         break;
01653                     case 0x03: // Get pointer to lowercase table
01654                     case 0x04: // Get pointer to filename uppercase table
01655                     case 0x07: // Get pointer to double byte char set table
01656                         if (dos.tables.dbcs != 0) {
01657                             mem_writeb(data + 0x00, reg_al);
01658                             mem_writed(data + 0x01, dos.tables.dbcs); //used to be 0
01659                             reg_cx = 5;
01660                             CALLBACK_SCF(false);
01661                         }
01662                         break;
01663                     case 0x20: /* Capitalize Character */
01664                         {
01665                             int in  = reg_dl;
01666                             int out = toupper(in);
01667                             reg_dl  = (Bit8u)out;
01668                         }
01669                         CALLBACK_SCF(false);
01670                         break;
01671                     case 0x21: /* Capitalize String (cx=length) */
01672                     case 0x22: /* Capatilize ASCIZ string */
01673                         data = SegPhys(ds) + reg_dx;
01674                         if(reg_al == 0x21) len = reg_cx; 
01675                         else len = mem_strlen(data); /* Is limited to 1024 */
01676 
01677                         if(len > DOS_COPYBUFSIZE - 1) E_Exit("DOS:0x65 Buffer overflow");
01678                         if(len) {
01679                             MEM_BlockRead(data,dos_copybuf,len);
01680                             dos_copybuf[len] = 0;
01681                             //No upcase as String(0x21) might be multiple asciz strings
01682                             for (Bitu count = 0; count < len;count++)
01683                                 dos_copybuf[count] = (Bit8u)toupper(*reinterpret_cast<unsigned char*>(dos_copybuf+count));
01684                             MEM_BlockWrite(data,dos_copybuf,len);
01685                         }
01686                         CALLBACK_SCF(false);
01687                         break;
01688                     default:
01689                         E_Exit("DOS:0x65:Unhandled country information call %2X",reg_al);   
01690                 };
01691                 break;
01692             }
01693         case 0x66:                  /* Get/Set global code page table  */
01694             if (reg_al==1) {
01695                 LOG(LOG_DOSMISC,LOG_ERROR)("Getting global code page table");
01696                 reg_bx=reg_dx=dos.loaded_codepage;
01697                 CALLBACK_SCF(false);
01698                 break;
01699             }
01700             LOG(LOG_DOSMISC,LOG_NORMAL)("DOS:Setting code page table is not supported");
01701             break;
01702         case 0x67:                  /* Set handle count */
01703             /* Weird call to increase amount of file handles needs to allocate memory if >20 */
01704             {
01705                 DOS_PSP psp(dos.psp());
01706                 psp.SetNumFiles(reg_bx);
01707                 CALLBACK_SCF(false);
01708                 break;
01709             };
01710         case 0x68:                  /* FFLUSH Commit file */
01711             if(DOS_FlushFile(reg_bl)) {
01712                 CALLBACK_SCF(false);
01713             } else {
01714                 reg_ax = dos.errorcode;
01715                 CALLBACK_SCF(true);
01716             }
01717             break;
01718         case 0x69:                  /* Get/Set disk serial number */
01719             {
01720                 switch(reg_al)      {
01721                     case 0x00:              /* Get */
01722                         LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Get Disk serial number");
01723                         CALLBACK_SCF(true);
01724                         break;
01725                     case 0x01:
01726                         LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Set Disk serial number");
01727                     default:
01728                         E_Exit("DOS:Illegal Get Serial Number call %2X",reg_al);
01729                 }   
01730                 break;
01731             } 
01732         case 0x6c:                  /* Extended Open/Create */
01733             MEM_StrCopy(SegPhys(ds)+reg_si,name1,DOSNAMEBUF);
01734             if (DOS_OpenFileExtended(name1,reg_bx,reg_cx,reg_dx,&reg_ax,&reg_cx)) {
01735                 CALLBACK_SCF(false);
01736             } else {
01737                 reg_ax=dos.errorcode;
01738                 CALLBACK_SCF(true);
01739             }
01740             break;
01741 
01742         case 0x71:                  /* Unknown probably 4dos detection */
01743             reg_ax=0x7100;
01744             CALLBACK_SCF(true); //Check this! What needs this ? See default case
01745             LOG(LOG_DOSMISC,LOG_NORMAL)("DOS:Windows long file name support call %2X",reg_al);
01746             break;
01747 
01748         case 0xE0:
01749         case 0x18:                  /* NULL Function for CP/M compatibility or Extended rename FCB */
01750         case 0x1d:                  /* NULL Function for CP/M compatibility or Extended rename FCB */
01751         case 0x1e:                  /* NULL Function for CP/M compatibility or Extended rename FCB */
01752         case 0x20:                  /* NULL Function for CP/M compatibility or Extended rename FCB */
01753         case 0x6b:                  /* NULL Function */
01754         case 0x61:                  /* UNUSED */
01755         case 0xEF:                  /* Used in Ancient Art Of War CGA */
01756         case 0x5e:                  /* More Network Functions */
01757         default:
01758             if (reg_ah < 0x6d) LOG(LOG_DOSMISC,LOG_ERROR)("DOS:Unhandled call %02X al=%02X. Set al to default of 0",reg_ah,reg_al); //Less errors. above 0x6c the functions are simply always skipped, only al is zeroed, all other registers untouched
01759             reg_al=0x00; /* default value */
01760             break;
01761     };
01762 
01763     /* if INT 21h involves any BIOS calls that need the timer, emulate the fact that tbe
01764      * BIOS might unmask IRQ 0 as part of the job (especially INT 13h disk I/O).
01765      *
01766      * Some DOS games & demos mask interrupts at the PIC level in a stingy manner that
01767      * apparently assumes DOS/BIOS will unmask some when called.
01768      *
01769      * Examples:
01770      *   Rebel by Arkham (without this fix, timer interrupt will not fire during demo and therefore music will not play). */
01771     if (unmask_irq0)
01772         PIC_SetIRQMask(0,false); /* Enable system timer */
01773 
01774     return CBRET_NONE;
01775 }
01776 
01777 
01778 static Bitu BIOS_1BHandler(void) {
01779     mem_writeb(BIOS_CTRL_BREAK_FLAG,0x00);
01780 
01781     /* take note (set flag) and return */
01782     /* FIXME: Don't forget that on "BOOT" this handler should be unassigned, though having it assigned
01783      *        to the guest OS causes no harm. */
01784     LOG_MSG("Note: default 1Bh handler invoked\n");
01785     DOS_BreakFlag = true;
01786     return CBRET_NONE;
01787 }
01788 
01789 static Bitu DOS_20Handler(void) {
01790         reg_ah=0x00;
01791         DOS_21Handler();
01792         return CBRET_NONE;
01793 }
01794 
01795 static Bitu DOS_CPMHandler(void) {
01796         // Convert a CPM-style call to a normal DOS call
01797         Bit16u flags=CPU_Pop16();
01798         CPU_Pop16();
01799         Bit16u caller_seg=CPU_Pop16();
01800         Bit16u caller_off=CPU_Pop16();
01801         CPU_Push16(flags);
01802         CPU_Push16(caller_seg);
01803         CPU_Push16(caller_off);
01804         if (reg_cl>0x24) {
01805                 reg_al=0;
01806                 return CBRET_NONE;
01807         }
01808         reg_ah=reg_cl;
01809         return DOS_21Handler();
01810 }
01811 
01812 static Bitu DOS_27Handler(void) {
01813         // Terminate & stay resident
01814         Bit16u para = (reg_dx/16)+((reg_dx % 16)>0);
01815         Bit16u psp = dos.psp(); //mem_readw(SegPhys(ss)+reg_sp+2);
01816         if (DOS_ResizeMemory(psp,&para)) {
01817                 DOS_Terminate(psp,true,0);
01818                 if (DOS_BreakINT23InProgress) throw int(0); /* HACK: Ick */
01819         }
01820         return CBRET_NONE;
01821 }
01822 
01823 extern DOS_Device *DOS_CON;
01824 
01825 /* PC-98 INT DC CL=0x10 AH=0x00 DL=cjar */
01826 void PC98_INTDC_WriteChar(unsigned char b) {
01827     if (DOS_CON != NULL) {
01828         Bit16u sz = 1;
01829 
01830         DOS_CON->Write(&b,&sz);
01831     }
01832 }
01833 
01834 static Bitu INT29_HANDLER(void) {
01835     if (DOS_CON != NULL) {
01836         unsigned char b = reg_al;
01837         Bit16u sz = 1;
01838 
01839         DOS_CON->Write(&b,&sz);
01840     }
01841 
01842     return CBRET_NONE;
01843 }
01844 
01845 static Bitu DOS_25Handler(void) {
01846         if (Drives[reg_al] == 0){
01847                 reg_ax = 0x8002;
01848                 SETFLAGBIT(CF,true);
01849         } else {
01850                 SETFLAGBIT(CF,false);
01851                 if ((reg_cx != 1) ||(reg_dx != 1))
01852                         LOG(LOG_DOSMISC,LOG_NORMAL)("int 25 called but not as diskdetection drive %X",reg_al);
01853 
01854            reg_ax = 0;
01855         }
01856         SETFLAGBIT(IF,true);
01857     return CBRET_NONE;
01858 }
01859 static Bitu DOS_26Handler(void) {
01860         LOG(LOG_DOSMISC,LOG_NORMAL)("int 26 called: hope for the best!");
01861         if (Drives[reg_al] == 0){
01862                 reg_ax = 0x8002;
01863                 SETFLAGBIT(CF,true);
01864         } else {
01865                 SETFLAGBIT(CF,false);
01866                 reg_ax = 0;
01867         }
01868         SETFLAGBIT(IF,true);
01869     return CBRET_NONE;
01870 }
01871 
01872 bool iret_only_for_debug_interrupts = true;
01873 bool enable_collating_uppercase = true;
01874 bool keep_private_area_on_boot = false;
01875 bool private_always_from_umb = false;
01876 bool private_segment_in_umb = true;
01877 Bit16u DOS_IHSEG = 0;
01878 
01879 // NOTE about 0x70 and PC-98 emulation mode:
01880 //
01881 // I don't know exactly how things differ in NEC's PC-98 MS-DOS, but,
01882 // according to some strange code in Touhou Project that's responsible
01883 // for blanking the text layer, there's a "row count" variable at 0x70:0x12
01884 // that holds (number of rows - 1). Leaving that byte value at zero prevents
01885 // the game from clearing the screen (which also exposes the tile data and
01886 // overdraw of the graphics layer). A value of zero instead just causes the
01887 // first text character row to be filled in, not the whole visible text layer.
01888 //
01889 // Pseudocode of the routine:
01890 //
01891 // XOR AX,AX
01892 // MOV ES,AX
01893 // MOV AL,ES:[0712h]            ; AX = BYTE [0x70:0x12] zero extend (ex. 0x18 == 24)
01894 // INC AX                       ; AX++                              (ex. becomes 0x19 == 25)
01895 // MOV DX,AX
01896 // SHL DX,1
01897 // SHL DX,1                     ; DX *= 4
01898 // ADD DX,AX                    ; DX += AX     equiv. DX = AX * 5
01899 // MOV CL,4h
01900 // SHL DX,CL                    ; DX <<= 4     equiv. DX = AX * 0x50  or  DX = AX * 80
01901 // ...
01902 // MOV AX,0A200h
01903 // MOV ES,AX
01904 // MOV AX,(solid black overlay block attribute)
01905 // MOV CX,DX
01906 // REP STOSW
01907 //
01908 // When the routine is done, the graphics layer is obscured by text character cells that
01909 // represent all black (filled in) so that the game can later "punch out" the regions
01910 // of the graphics layer it wants you to see. TH02 relies on this as well to flash the
01911 // screen and open from the center to show the title screen. During gameplay, the text
01912 // layer is used to obscure sprite overdraw when a sprite is partially off-screen as well
01913 // as hidden tile data on the right hand half of the screen that the game read/write
01914 // copies through the GDC pattern/tile registers to make the background. When the text
01915 // layer is not present it's immediately apparent that the sprite renderer makes no attempt
01916 // to clip sprites within the screen, but instead relies on the text overlay to hide the
01917 // overdraw.
01918 //
01919 // this means that on PC-98 one of two things are true. either:
01920 //  - NEC's variation of MS-DOS loads the base kernel higher up (perhaps at 0x80:0x00?)
01921 //    and the BIOS data area lies from 0x40:00 to 0x7F:00
01922 //
01923 //    or
01924 //
01925 //  - NEC's variation loads at 0x70:0x00 (same as IBM PC MS-DOS) and Touhou Project
01926 //    is dead guilty of reaching directly into MS-DOS kernel memory to read
01927 //    internal variables it shouldn't be reading directly!
01928 //
01929 // Ick...
01930 
01931 void DOS_GetMemory_reset();
01932 void DOS_GetMemory_Choose();
01933 Bitu MEM_PageMask(void);
01934 
01935 #include <assert.h>
01936 
01937 extern bool dos_con_use_int16_to_detect_input;
01938 extern bool dbg_zero_on_dos_allocmem;
01939 extern bool log_dev_con;
01940 
01941 class DOS:public Module_base{
01942 private:
01943         CALLBACK_HandlerObject callback[9];
01944         RealPt int30,int31;
01945 
01946 public:
01947     void DOS_Write_HMA_CPM_jmp(void) {
01948         // HMA mirror of CP/M entry point.
01949         // this is needed for "F01D:FEF0" to be a valid jmp whether or not A20 is enabled
01950         if (dos_in_hma &&
01951             cpm_compat_mode != CPM_COMPAT_OFF &&
01952             cpm_compat_mode != CPM_COMPAT_DIRECT) {
01953             LOG(LOG_MISC,LOG_DEBUG)("Writing HMA mirror of CP/M entry point");
01954 
01955             Bitu was_a20 = XMS_GetEnabledA20();
01956 
01957             XMS_EnableA20(true);
01958 
01959             mem_writeb(0x1000C0,(Bit8u)0xea);           // jmpf
01960             mem_unalignedwrited(0x1000C0+1,callback[8].Get_RealPointer());
01961 
01962             if (!was_a20) XMS_EnableA20(false);
01963         }
01964     }
01965 
01966     Bitu DOS_Get_CPM_entry_direct(void) {
01967         return callback[8].Get_RealPointer();
01968     }
01969 
01970         DOS(Section* configuration):Module_base(configuration){
01971                 Section_prop * section=static_cast<Section_prop *>(configuration);
01972 
01973         ::disk_data_rate = section->Get_int("hard drive data rate limit");
01974         if (::disk_data_rate < 0) {
01975             extern bool pcibus_enable;
01976 
01977             if (pcibus_enable)
01978                 ::disk_data_rate = 8333333; /* Probably an average IDE data rate for mid 1990s PCI IDE controllers in PIO mode */
01979             else
01980                 ::disk_data_rate = 3500000; /* Probably an average IDE data rate for early 1990s ISA IDE controllers in PIO mode */
01981         }
01982 
01983         dos_in_hma = section->Get_bool("dos in hma");
01984         dos_sda_size = section->Get_int("dos sda size");
01985         log_dev_con = control->opt_log_con || section->Get_bool("log console");
01986                 enable_dbcs_tables = section->Get_bool("dbcs");
01987                 enable_share_exe_fake = section->Get_bool("share");
01988                 enable_filenamechar = section->Get_bool("filenamechar");
01989                 dos_initial_hma_free = section->Get_int("hma free space");
01990         minimum_mcb_free = section->Get_hex("minimum mcb free");
01991                 minimum_mcb_segment = section->Get_hex("minimum mcb segment");
01992                 private_segment_in_umb = section->Get_bool("private area in umb");
01993                 enable_collating_uppercase = section->Get_bool("collating and uppercase");
01994                 private_always_from_umb = section->Get_bool("kernel allocation in umb");
01995                 minimum_dos_initial_private_segment = section->Get_hex("minimum dos initial private segment");
01996                 dos_con_use_int16_to_detect_input = section->Get_bool("con device use int 16h to detect keyboard input");
01997                 iret_only_for_debug_interrupts = section->Get_bool("write plain iretf for debug interrupts");
01998                 dbg_zero_on_dos_allocmem = section->Get_bool("zero memory on int 21h memory allocation");
01999                 MAXENV = (unsigned int)section->Get_int("maximum environment block size on exec");
02000                 ENV_KEEPFREE = (unsigned int)section->Get_int("additional environment block size on exec");
02001                 enable_dummy_device_mcb = section->Get_bool("enable dummy device mcb");
02002                 int15_wait_force_unmask_irq = section->Get_bool("int15 wait force unmask irq");
02003         disk_io_unmask_irq0 = section->Get_bool("unmask timer on disk io");
02004 
02005         if (dos_initial_hma_free > 0x10000)
02006             dos_initial_hma_free = 0x10000;
02007 
02008         std::string cpmcompat = section->Get_string("cpm compatibility mode");
02009 
02010         if (cpmcompat == "")
02011             cpmcompat = "auto";
02012 
02013         if (cpmcompat == "msdos2")
02014             cpm_compat_mode = CPM_COMPAT_MSDOS2;
02015         else if (cpmcompat == "msdos5")
02016             cpm_compat_mode = CPM_COMPAT_MSDOS5;
02017         else if (cpmcompat == "direct")
02018             cpm_compat_mode = CPM_COMPAT_DIRECT;
02019         else if (cpmcompat == "auto")
02020             cpm_compat_mode = CPM_COMPAT_MSDOS5; /* MS-DOS 5.x is default */
02021         else
02022             cpm_compat_mode = CPM_COMPAT_OFF;
02023 
02024         /* FIXME: Boot up an MS-DOS system and look at what INT 21h on Microsoft's MS-DOS returns
02025          *        for SDA size and location, then use that here.
02026          *
02027          *        Why does this value matter so much to WordPerfect 5.1? */
02028         if (dos_sda_size == 0)
02029             DOS_SDA_SEG_SIZE = 0x560;
02030         else if (dos_sda_size < 0x1A)
02031             DOS_SDA_SEG_SIZE = 0x1A;
02032         else if (dos_sda_size > 32768)
02033             DOS_SDA_SEG_SIZE = 32768;
02034         else
02035             DOS_SDA_SEG_SIZE = (dos_sda_size + 0xF) & (~0xF); /* round up to paragraph */
02036 
02037         /* msdos 2.x and msdos 5.x modes, if HMA is involved, require us to take the first 256 bytes of HMA
02038          * in order for "F01D:FEF0" to work properly whether or not A20 is enabled. Our direct mode doesn't
02039          * jump through that address, and therefore doesn't need it. */
02040         if (dos_in_hma &&
02041             cpm_compat_mode != CPM_COMPAT_OFF &&
02042             cpm_compat_mode != CPM_COMPAT_DIRECT) {
02043             LOG(LOG_MISC,LOG_DEBUG)("DOS: CP/M compatibility method with DOS in HMA requires mirror of entry point in HMA.");
02044             if (dos_initial_hma_free > 0xFF00) {
02045                 dos_initial_hma_free = 0xFF00;
02046                 LOG(LOG_MISC,LOG_DEBUG)("DOS: CP/M compatibility method requires reduction of HMA free space to accomodate.");
02047             }
02048         }
02049 
02050                 if ((int)MAXENV < 0) MAXENV = 65535;
02051                 if ((int)ENV_KEEPFREE < 0) ENV_KEEPFREE = 1024;
02052 
02053                 LOG(LOG_MISC,LOG_DEBUG)("DOS: MAXENV=%u ENV_KEEPFREE=%u",MAXENV,ENV_KEEPFREE);
02054 
02055                 if (ENV_KEEPFREE < 83)
02056                         LOG_MSG("DOS: ENV_KEEPFREE is below 83 bytes. DOS programs that rely on undocumented data following the environment block may break.");
02057 
02058                 if (dbg_zero_on_dos_allocmem) {
02059                         LOG_MSG("Debug option enabled: INT 21h memory allocation will always clear memory block before returning\n");
02060                 }
02061 
02062                 if (minimum_mcb_segment > 0x8000) minimum_mcb_segment = 0x8000; /* FIXME: Clip against available memory */
02063 
02064         /* we make use of the DOS_GetMemory() function for the dynamic allocation */
02065         if (private_always_from_umb) {
02066             DOS_GetMemory_Choose(); /* the pool starts in UMB */
02067             if (minimum_mcb_segment == 0)
02068                 DOS_MEM_START = IS_PC98_ARCH ? 0x80 : 0x70; /* funny behavior in some games suggests the MS-DOS kernel loads a bit higher on PC-98 */
02069             else
02070                 DOS_MEM_START = minimum_mcb_segment;
02071 
02072             if (DOS_MEM_START < 0x40)
02073                 LOG_MSG("DANGER, DANGER! DOS_MEM_START has been set to within the interrupt vector table! Proceed at your own risk!");
02074             else if (DOS_MEM_START < 0x50)
02075                 LOG_MSG("WARNING: DOS_MEM_START has been assigned to the BIOS data area! Proceed at your own risk!");
02076             else if (DOS_MEM_START < 0x51)
02077                 LOG_MSG("WARNING: DOS_MEM_START has been assigned to segment 0x50, which some programs may use as the Print Screen flag");
02078             else if (DOS_MEM_START < 0x80 && IS_PC98_ARCH)
02079                 LOG_MSG("CAUTION: DOS_MEM_START is less than 0x80 which may cause problems with some DOS games or applications relying on PC-98 BIOS state");
02080             else if (DOS_MEM_START < 0x70)
02081                 LOG_MSG("CAUTION: DOS_MEM_START is less than 0x70 which may cause problems with some DOS games or applications");
02082         }
02083         else {
02084             if (minimum_dos_initial_private_segment == 0)
02085                 DOS_PRIVATE_SEGMENT = IS_PC98_ARCH ? 0x80 : 0x70; /* funny behavior in some games suggests the MS-DOS kernel loads a bit higher on PC-98 */
02086             else
02087                 DOS_PRIVATE_SEGMENT = minimum_dos_initial_private_segment;
02088 
02089             if (DOS_PRIVATE_SEGMENT < 0x50)
02090                 LOG_MSG("DANGER, DANGER! DOS_PRIVATE_SEGMENT has been set too low!");
02091             if (DOS_PRIVATE_SEGMENT < 0x80 && IS_PC98_ARCH)
02092                 LOG_MSG("DANGER, DANGER! DOS_PRIVATE_SEGMENT has been set too low for PC-98 emulation!");
02093 
02094             if (MEM_TotalPages() > 0x9C)
02095                 DOS_PRIVATE_SEGMENT_END = 0x9C00;
02096             else
02097                 DOS_PRIVATE_SEGMENT_END = (MEM_TotalPages() << (12 - 4)) - 1; /* NTS: Remember DOSBox's implementation reuses the last paragraph for UMB linkage */
02098         }
02099 
02100         LOG(LOG_MISC,LOG_DEBUG)("DOS kernel structures will be allocated from pool 0x%04x-0x%04x",
02101                 DOS_PRIVATE_SEGMENT,DOS_PRIVATE_SEGMENT_END-1);
02102 
02103         DOS_IHSEG = DOS_GetMemory(1,"DOS_IHSEG");
02104 
02105         /* DOS_INFOBLOCK_SEG contains the entire List of Lists, though the INT 21h call returns seg:offset with offset nonzero */
02106         DOS_INFOBLOCK_SEG = DOS_GetMemory(0xC0,"DOS_INFOBLOCK_SEG");    // was 0x80
02107 
02108         DOS_CONDRV_SEG = DOS_GetMemory(0x08,"DOS_CONDRV_SEG");          // was 0xA0
02109         DOS_CONSTRING_SEG = DOS_GetMemory(0x0A,"DOS_CONSTRING_SEG");    // was 0xA8
02110         DOS_SDA_SEG = DOS_GetMemory(DOS_SDA_SEG_SIZE>>4,"DOS_SDA_SEG");         // was 0xB2  (0xB2 + 0x56 = 0x108)
02111         DOS_SDA_OFS = 0;
02112         DOS_CDS_SEG = DOS_GetMemory(0x10,"DOS_CDA_SEG");                // was 0x108
02113 
02114                 LOG(LOG_MISC,LOG_DEBUG)("DOS kernel alloc:");
02115                 LOG(LOG_MISC,LOG_DEBUG)("   IHSEG:        seg 0x%04x",DOS_IHSEG);
02116                 LOG(LOG_MISC,LOG_DEBUG)("   infoblock:    seg 0x%04x",DOS_INFOBLOCK_SEG);
02117                 LOG(LOG_MISC,LOG_DEBUG)("   condrv:       seg 0x%04x",DOS_CONDRV_SEG);
02118                 LOG(LOG_MISC,LOG_DEBUG)("   constring:    seg 0x%04x",DOS_CONSTRING_SEG);
02119                 LOG(LOG_MISC,LOG_DEBUG)("   SDA:          seg 0x%04x:0x%04x %u bytes",DOS_SDA_SEG,DOS_SDA_OFS,DOS_SDA_SEG_SIZE);
02120                 LOG(LOG_MISC,LOG_DEBUG)("   CDS:          seg 0x%04x",DOS_CDS_SEG);
02121                 LOG(LOG_MISC,LOG_DEBUG)("[private segment @ this point 0x%04x-0x%04x mem=0x%04lx]",
02122                         DOS_PRIVATE_SEGMENT,DOS_PRIVATE_SEGMENT_END,
02123                         (unsigned long)(MEM_TotalPages() << (12 - 4)));
02124 
02125                 callback[0].Install(DOS_20Handler,CB_IRET,"DOS Int 20");
02126                 callback[0].Set_RealVec(0x20);
02127 
02128                 callback[1].Install(DOS_21Handler,CB_INT21,"DOS Int 21");
02129                 callback[1].Set_RealVec(0x21);
02130         //Pseudo code for int 21
02131         // sti
02132         // callback 
02133         // iret
02134         // retf  <- int 21 4c jumps here to mimic a retf Cyber
02135 
02136                 callback[2].Install(DOS_25Handler,CB_RETF,"DOS Int 25");
02137                 callback[2].Set_RealVec(0x25);
02138 
02139                 callback[3].Install(DOS_26Handler,CB_RETF,"DOS Int 26");
02140                 callback[3].Set_RealVec(0x26);
02141 
02142                 callback[4].Install(DOS_27Handler,CB_IRET,"DOS Int 27");
02143                 callback[4].Set_RealVec(0x27);
02144 
02145                 callback[5].Install(NULL,CB_IRET/*CB_INT28*/,"DOS idle");
02146                 callback[5].Set_RealVec(0x28);
02147 
02148         if (IS_PC98_ARCH) {
02149             // PC-98 also has INT 29h but the behavior of some games suggest that it is handled
02150             // the same as CON device output. Apparently the reason Touhou Project has been unable
02151             // to clear the screen is that it uses INT 29h to directly send ANSI codes rather than
02152             // standard I/O calls to write to the CON device.
02153             callback[6].Install(INT29_HANDLER,CB_IRET,"CON Output Int 29");
02154             callback[6].Set_RealVec(0x29);
02155         }
02156         else {
02157             // FIXME: Really? Considering the main CON device emulation has ANSI.SYS emulation
02158             //        you'd think that this would route it through the same.
02159             callback[6].Install(NULL,CB_INT29,"CON Output Int 29");
02160             callback[6].Set_RealVec(0x29);
02161             // pseudocode for CB_INT29:
02162             //  push ax
02163             //  mov ah, 0x0e
02164             //  int 0x10
02165             //  pop ax
02166             //  iret
02167         }
02168 
02169         if (!IS_PC98_ARCH) {
02170             /* DOS installs a handler for INT 1Bh */
02171             callback[7].Install(BIOS_1BHandler,CB_IRET,"BIOS 1Bh");
02172             callback[7].Set_RealVec(0x1B);
02173         }
02174 
02175                 callback[8].Install(DOS_CPMHandler,CB_CPM,"DOS/CPM Int 30-31");
02176                 int30=RealGetVec(0x30);
02177                 int31=RealGetVec(0x31);
02178                 mem_writeb(0x30*4,(Bit8u)0xea);         // jmpf
02179                 mem_unalignedwrited(0x30*4+1,callback[8].Get_RealPointer());
02180                 // pseudocode for CB_CPM:
02181                 //      pushf
02182                 //      ... the rest is like int 21
02183 
02184         /* NTS: HMA support requires XMS. EMS support may switch on A20 if VCPI emulation requires the odd megabyte */
02185         if ((!dos_in_hma || !section->Get_bool("xms")) && (MEM_A20_Enabled() || strcmp(section->Get_string("ems"),"false") != 0) &&
02186             cpm_compat_mode != CPM_COMPAT_OFF && cpm_compat_mode != CPM_COMPAT_DIRECT) {
02187             /* hold on, only if more than 1MB of RAM and memory access permits it */
02188             if (MEM_TotalPages() > 0x100 && MEM_PageMask() > 0xff/*more than 20-bit decoding*/) {
02189                 LOG(LOG_MISC,LOG_WARN)("DOS not in HMA or XMS is disabled. This may break programs using the CP/M compatibility call method if the A20 gate is switched on.");
02190             }
02191         }
02192 
02193                 DOS_FILES = (unsigned int)section->Get_int("files");
02194                 DOS_SetupFiles();                                                               /* Setup system File tables */
02195                 DOS_SetupDevices();                                                     /* Setup dos devices */
02196                 DOS_SetupTables();
02197 
02198                 /* move the private segment elsewhere to avoid conflict with the MCB structure.
02199                  * either set to 0 to cause the decision making to choose an upper memory address,
02200                  * or allocate an additional private area and start the MCB just after that */
02201                 if (!private_always_from_umb) {
02202                         DOS_MEM_START = DOS_GetMemory(0,"DOS_MEM_START");               // was 0x158 (pass 0 to alloc nothing, get the pointer)
02203 
02204                         DOS_GetMemory_reset();
02205                         DOS_PRIVATE_SEGMENT = 0;
02206                         DOS_PRIVATE_SEGMENT_END = 0;
02207                         if (!private_segment_in_umb) {
02208                                 /* If private segment is not being placed in UMB, then it must follow the DOS kernel. */
02209                                 unsigned int seg;
02210                                 unsigned int segend;
02211 
02212                                 seg = DOS_MEM_START;
02213                                 DOS_MEM_START += DOS_PRIVATE_SEGMENT_Size;
02214                                 segend = DOS_MEM_START;
02215 
02216                                 if (segend >= (MEM_TotalPages() << (12 - 4)))
02217                                         E_Exit("Insufficient room for private area");
02218 
02219                                 DOS_PRIVATE_SEGMENT = seg;
02220                                 DOS_PRIVATE_SEGMENT_END = segend;
02221                                 DOS_MEM_START = DOS_PRIVATE_SEGMENT_END;
02222                                 DOS_GetMemory_reset();
02223                                 LOG_MSG("Private area, not stored in UMB on request, occupies 0x%04x-0x%04x [dynamic]\n",
02224                                         DOS_PRIVATE_SEGMENT,DOS_PRIVATE_SEGMENT_END-1);
02225                         }
02226                 }
02227 
02228                 if (minimum_mcb_segment != 0) {
02229                         if (DOS_MEM_START < minimum_mcb_segment)
02230                                 DOS_MEM_START = minimum_mcb_segment;
02231                 }
02232 
02233                 LOG(LOG_MISC,LOG_DEBUG)("   mem start:    seg 0x%04x",DOS_MEM_START);
02234 
02235                 /* carry on setup */
02236                 DOS_SetupMemory();                                                              /* Setup first MCB */
02237 
02238         /* NTS: There is a mysterious memory corruption issue with some DOS games
02239          *      and applications when they are loaded at or around segment 0x800.
02240          *      This should be looked into. In the meantime, setting the MCB
02241          *      start segment before or after 0x800 helps to resolve these issues.
02242          *      It also puts DOSBox-X at parity with main DOSBox SVN behavior. */
02243         if (minimum_mcb_free == 0)
02244             minimum_mcb_free = 0x100;
02245         else if (minimum_mcb_free < minimum_mcb_segment)
02246             minimum_mcb_free = minimum_mcb_segment;
02247 
02248         LOG(LOG_MISC,LOG_DEBUG)("   min free:     seg 0x%04x",minimum_mcb_free);
02249 
02250         if (DOS_MEM_START < minimum_mcb_free) {
02251             Bit16u sg=0,tmp;
02252 
02253             dos.psp(8); // DOS ownership
02254 
02255             tmp = 1; // start small
02256             if (DOS_AllocateMemory(&sg,&tmp)) {
02257                 if (sg < minimum_mcb_free) {
02258                     LOG(LOG_MISC,LOG_DEBUG)("   min free pad: seg 0x%04x",sg);
02259                 }
02260                 else {
02261                     DOS_FreeMemory(sg);
02262                     sg = 0;
02263                 }
02264             }
02265             else {
02266                 sg=0;
02267             }
02268 
02269             if (sg != 0 && sg < minimum_mcb_free) {
02270                 Bit16u tmp = minimum_mcb_free - sg;
02271                 if (!DOS_ResizeMemory(sg,&tmp)) {
02272                     LOG(LOG_MISC,LOG_DEBUG)("    WARNING: cannot resize min free pad");
02273                 }
02274             }
02275         }
02276 
02277                 DOS_SetupPrograms();
02278                 DOS_SetupMisc();                                                        /* Some additional dos interrupts */
02279                 DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).SetDrive(25); /* Else the next call gives a warning. */
02280                 DOS_SetDefaultDrive(25);
02281 
02282                 keep_private_area_on_boot = section->Get_bool("keep private area on boot");
02283         
02284                 dos.version.major=5;
02285                 dos.version.minor=0;
02286 
02287                 std::string ver = section->Get_string("ver");
02288                 if (!ver.empty()) {
02289                         const char *s = ver.c_str();
02290 
02291                         if (isdigit(*s)) {
02292                                 dos.version.minor=0;
02293                                 dos.version.major=(int)strtoul(s,(char**)(&s),10);
02294                                 if (*s == '.') {
02295                                         s++;
02296                                         if (isdigit(*s)) {
02297                                                 dos.version.minor=(int)strtoul(s,(char**)(&s),10);
02298                                         }
02299                                 }
02300 
02301                                 /* warn about unusual version numbers */
02302                                 if (dos.version.major >= 10 && dos.version.major <= 30) {
02303                                         LOG_MSG("WARNING, DOS version %u.%u: the major version is set to a "
02304                                                 "range that may cause some DOS programs to think they are "
02305                                                 "running from within an OS/2 DOS box.",
02306                                                 dos.version.major, dos.version.minor);
02307                                 }
02308                                 else if (dos.version.major == 0 || dos.version.major > 8 || dos.version.minor > 90)
02309                                         LOG_MSG("WARNING: DOS version %u.%u is unusual, may confuse DOS programs",
02310                                                 dos.version.major, dos.version.minor);
02311                         }
02312                 }
02313         }
02314         ~DOS(){
02315                 /* NTS: We do NOT free the drives! The OS may use them later! */
02316                 void DOS_ShutdownFiles();
02317                 DOS_ShutdownFiles();
02318                 void DOS_ShutdownDevices(void);
02319                 DOS_ShutdownDevices();
02320                 RealSetVec(0x30,int30);
02321                 RealSetVec(0x31,int31);
02322         }
02323 };
02324 
02325 static DOS* test = NULL;
02326 
02327 void DOS_Write_HMA_CPM_jmp(void) {
02328     assert(test != NULL);
02329     test->DOS_Write_HMA_CPM_jmp();
02330 }
02331 
02332 Bitu DOS_Get_CPM_entry_direct(void) {
02333     assert(test != NULL);
02334     return test->DOS_Get_CPM_entry_direct();
02335 }
02336 
02337 void DOS_ShutdownFiles() {
02338         if (Files != NULL) {
02339                 for (Bitu i=0;i<DOS_FILES;i++) {
02340                         if (Files[i] != NULL) {
02341                                 delete Files[i];
02342                                 Files[i] = NULL;
02343                         }
02344                 }
02345                 delete[] Files;
02346                 Files = NULL;
02347         }
02348 }
02349 
02350 void DOS_ShutdownDrives() {
02351         for (Bit16u i=0;i<DOS_DRIVES;i++) {
02352                 delete Drives[i];
02353                 Drives[i] = NULL;
02354         }
02355 }
02356 
02357 void update_pc98_function_row(bool enable);
02358 void DOS_UnsetupMemory();
02359 void DOS_Casemap_Free();
02360 
02361 void DOS_DoShutDown() {
02362         if (test != NULL) {
02363                 delete test;
02364                 test = NULL;
02365         }
02366 
02367     if (IS_PC98_ARCH) update_pc98_function_row(false);
02368 
02369     DOS_UnsetupMemory();
02370     DOS_Casemap_Free();
02371 }
02372 
02373 void DOS_ShutDown(Section* /*sec*/) {
02374         DOS_DoShutDown();
02375 }
02376 
02377 void DOS_GetMemory_reinit();
02378 
02379 void DOS_OnReset(Section* /*sec*/) {
02380         DOS_DoShutDown();
02381     DOS_GetMemory_reinit();
02382 }
02383 
02384 void DOS_Startup(Section* sec) {
02385     (void)sec;//UNUSED
02386 
02387         if (test == NULL) {
02388         DOS_GetMemLog.clear();
02389         DOS_GetMemory_reinit();
02390         LOG(LOG_MISC,LOG_DEBUG)("Allocating DOS kernel");
02391                 test = new DOS(control->GetSection("dos"));
02392         }
02393 }
02394 
02395 void DOS_Init() {
02396         LOG(LOG_MISC,LOG_DEBUG)("Initializing DOS kernel (DOS_Init)");
02397     LOG(LOG_MISC,LOG_DEBUG)("sizeof(union bootSector) = %u",(unsigned int)sizeof(union bootSector));
02398     LOG(LOG_MISC,LOG_DEBUG)("sizeof(struct bootstrap) = %u",(unsigned int)sizeof(struct bootstrap));
02399     LOG(LOG_MISC,LOG_DEBUG)("sizeof(direntry) = %u",(unsigned int)sizeof(direntry));
02400 
02401     /* this code makes assumptions! */
02402     assert(sizeof(direntry) == 32);
02403     assert((SECTOR_SIZE_MAX % sizeof(direntry)) == 0);
02404     assert((MAX_DIRENTS_PER_SECTOR * sizeof(direntry)) == SECTOR_SIZE_MAX);
02405 
02406         AddExitFunction(AddExitFunctionFuncPair(DOS_ShutDown),false);
02407         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(DOS_OnReset));
02408         AddVMEventFunction(VM_EVENT_DOS_EXIT_KERNEL,AddVMEventFunctionFuncPair(DOS_ShutDown));
02409         AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_KERNEL,AddVMEventFunctionFuncPair(DOS_ShutDown));
02410         AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(DOS_OnReset));
02411 }
02412