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