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