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