/* sys_dos.c @description@ Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dosisms.h" #define MINIMUM_WIN_MEMORY 0x800000 #define MINIMUM_WIN_MEMORY_LEVELPAK (MINIMUM_WIN_MEMORY + 0x100000) int end_of_memory; qboolean lockmem, lockunlockmem, unlockmem; static int win95; #define STDOUT 1 #define KEYBUF_SIZE 256 static unsigned char keybuf[KEYBUF_SIZE]; static int keybuf_head = 0; static int keybuf_tail = 0; static quakeparms_t quakeparms; int sys_checksum; static double curtime = 0.0; static double lastcurtime = 0.0; static double oldtime = 0.0; static qboolean isDedicated; static int minmem; float fptest_temp; extern char start_of_memory __asm__ ("start"); //============================================================================= // this is totally dependent on cwsdpmi putting the stack right after tge // global data // This does evil things in a Win95 DOS box!!! #if 0 extern byte end; #define CHECKBYTE 0xed void Sys_InitStackCheck (void) { int i; for (i = 0; i < 128 * 1024; i++) (&end)[i] = CHECKBYTE; } void Sys_StackCheck (void) { int i; for (i = 0; i < 128 * 1024; i++) if ((&end)[i] != CHECKBYTE) break; Con_Printf ("%i undisturbed stack bytes\n", i); if (end != CHECKBYTE) Sys_Error ("System stack overflow!"); } #endif //============================================================================= byte scantokey[128] = { // 0 1 2 3 4 5 6 7 // 8 9 A B C D E F 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', K_BACKSPACE, 9, // 0 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 13, K_CTRL, 'a', 's', // 1 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v', // 2 'b', 'n', 'm', ',', '.', '/', K_SHIFT, '*', K_ALT, ' ', 0, K_F1, K_F2, K_F3, K_F4, K_F5, // 3 K_F6, K_F7, K_F8, K_F9, K_F10, 0, 0, K_HOME, K_UPARROW, K_PGUP, '-', K_LEFTARROW, '5', K_RIGHTARROW, '+', K_END, // 4 K_DOWNARROW, K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, K_F12, 0, 0, 0, 0, 0, 0, 0, // 5 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 7 }; byte shiftscantokey[128] = { // 0 1 2 3 4 5 6 7 // 8 9 A B C D E F 0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', K_BACKSPACE, 9, // 0 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 13, K_CTRL, 'A', 'S', // 1 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', K_SHIFT, '|', 'Z', 'X', 'C', 'V', // 2 'B', 'N', 'M', '<', '>', '?', K_SHIFT, '*', K_ALT, ' ', 0, K_F1, K_F2, K_F3, K_F4, K_F5, // 3 K_F6, K_F7, K_F8, K_F9, K_F10, 0, 0, K_HOME, K_UPARROW, K_PGUP, '_', K_LEFTARROW, '%', K_RIGHTARROW, '+', K_END, // 4 K_DOWNARROW, K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, K_F12, 0, 0, 0, 0, 0, 0, 0, // 5 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 7 }; void TrapKey (void) { // static int ctrl=0; keybuf[keybuf_head] = dos_inportb (0x60); dos_outportb (0x20, 0x20); /* if (scantokey[keybuf[keybuf_head]&0x7f] == K_CTRL) ctrl=keybuf[keybuf_head]&0x80; if (ctrl && scantokey[keybuf[keybuf_head]&0x7f] == 'c') Sys_Error("ctrl-c hit\n"); */ keybuf_head = (keybuf_head + 1) & (KEYBUF_SIZE - 1); } #define SC_UPARROW 0x48 #define SC_DOWNARROW 0x50 #define SC_LEFTARROW 0x4b #define SC_RIGHTARROW 0x4d #define SC_LEFTSHIFT 0x2a #define SC_RIGHTSHIFT 0x36 #define SC_RIGHTARROW 0x4d void MaskExceptions (void); void Sys_InitFloatTime (void); void Sys_PushFPCW_SetHigh (void); void Sys_PopFPCW (void); #define LEAVE_FOR_CACHE (512*1024) // FIXME: tune #define LOCKED_FOR_MALLOC (128*1024) // FIXME: tune void Sys_DetectWin95 (void) { __dpmi_regs r; r.x.ax = 0x160a; /* Get Windows Version */ __dpmi_int (0x2f, &r); if (r.x.ax || r.h.bh < 4) { /* Not windows or earlier than Win95 */ win95 = 0; lockmem = true; lockunlockmem = false; unlockmem = true; } else { win95 = 1; lockunlockmem = COM_CheckParm ("-winlockunlock"); if (lockunlockmem) lockmem = true; else lockmem = COM_CheckParm ("-winlock"); unlockmem = lockmem && !lockunlockmem; } } void * dos_getmaxlockedmem (int *size) { __dpmi_free_mem_info meminfo; __dpmi_meminfo info; int working_size; void *working_memory; int last_locked; int extra, i, j, allocsize; static char *msg = "Locking data..."; int m, n; byte *x; // first lock all the current executing image so the locked count will // be accurate. It doesn't hurt to lock the memory multiple times last_locked = __djgpp_selector_limit + 1; info.size = last_locked - 4096; info.address = __djgpp_base_address + 4096; if (lockmem) { if (__dpmi_lock_linear_region (&info)) { Sys_Error ("Lock of current memory at 0x%lx for %ldKb failed!\n", info.address, info.size / 1024); } } __dpmi_get_free_memory_information (&meminfo); if (!win95) { /* Not windows or earlier than Win95 */ working_size = meminfo.maximum_locked_page_allocation_in_pages * 4096; } else { working_size = meminfo.largest_available_free_block_in_bytes - LEAVE_FOR_CACHE; } working_size &= ~0xffff; /* Round down to 64K */ working_size += 0x10000; do { working_size -= 0x10000; /* Decrease 64K and try again */ working_memory = sbrk (working_size); } while (working_memory == (void *) -1); extra = 0xfffc - ((unsigned) sbrk (0) & 0xffff); if (extra > 0) { sbrk (extra); working_size += extra; } // now grab the memory info.address = last_locked + __djgpp_base_address; if (!win95) { info.size = __djgpp_selector_limit + 1 - last_locked; while (info.size > 0 && __dpmi_lock_linear_region (&info)) { info.size -= 0x1000; working_size -= 0x1000; sbrk (-0x1000); } } else { /* Win95 section */ j = COM_CheckParm ("-winmem"); if (standard_quake) minmem = MINIMUM_WIN_MEMORY; else minmem = MINIMUM_WIN_MEMORY_LEVELPAK; if (j) { allocsize = ((int) (Q_atoi (com_argv[j + 1]))) * 0x100000 + LOCKED_FOR_MALLOC; if (allocsize < (minmem + LOCKED_FOR_MALLOC)) allocsize = minmem + LOCKED_FOR_MALLOC; } else { allocsize = minmem + LOCKED_FOR_MALLOC; } if (!lockmem) { // we won't lock, just sbrk the memory info.size = allocsize; goto UpdateSbrk; } // lock the memory down write (STDOUT, msg, strlen (msg)); for (j = allocsize; j > (minmem + LOCKED_FOR_MALLOC); j -= 0x100000) { info.size = j; if (!__dpmi_lock_linear_region (&info)) goto Locked; write (STDOUT, ".", 1); } // finally, try with the absolute minimum amount for (i = 0; i < 10; i++) { info.size = minmem + LOCKED_FOR_MALLOC; if (!__dpmi_lock_linear_region (&info)) goto Locked; } Sys_Error ("Can't lock memory; %d Mb lockable RAM required. " "Try shrinking smartdrv.", info.size / 0x100000); Locked: UpdateSbrk: info.address += info.size; info.address -= __djgpp_base_address + 4; // ending point, malloc // align working_size = info.address - (int) working_memory; sbrk (info.address - (int) sbrk (0)); // negative adjustment } if (lockunlockmem) { __dpmi_unlock_linear_region (&info); printf ("Locked and unlocked %d Mb data\n", working_size / 0x100000); } else if (lockmem) { printf ("Locked %d Mb data\n", working_size / 0x100000); } else { printf ("Allocated %d Mb data\n", working_size / 0x100000); } // touch all the memory to make sure it's there. The 16-page skip is to // keep Win 95 from thinking we're trying to page ourselves in (we are // doing that, of course, but there's no reason we shouldn't) x = (byte *) working_memory; for (n = 0; n < 4; n++) { for (m = 0; m < (working_size - 16 * 0x1000); m += 4) { sys_checksum += *(int *) &x[m]; sys_checksum += *(int *) &x[m + 16 * 0x1000]; } } // give some of what we locked back for malloc before returning. Done // by cheating and passing a negative value to sbrk working_size -= LOCKED_FOR_MALLOC; sbrk (-(LOCKED_FOR_MALLOC)); *size = working_size; return working_memory; } /* ============ Sys_FileTime returns -1 if not present ============ */ int Sys_FileTime (char *path) { struct stat buf; if (stat (path, &buf) == -1) return -1; return buf.st_mtime; } void Sys_mkdir (char *path) { mkdir (path, 0777); } void Sys_Sleep (void) { } char * Sys_ConsoleInput (void) { static char text[256]; static int len = 0; char ch; if (!isDedicated) return NULL; if (!kbhit ()) return NULL; ch = getche (); switch (ch) { case '\r': putch ('\n'); if (len) { text[len] = 0; len = 0; return text; } break; case '\b': putch (' '); if (len) { len--; putch ('\b'); } break; default: text[len] = ch; len = (len + 1) & 0xff; break; } return NULL; } void Sys_Init (void) { MaskExceptions (); Sys_SetFPCW (); dos_outportb (0x43, 0x34); // set system timer to mode 2 dos_outportb (0x40, 0); // for the Sys_DoubleTime() function dos_outportb (0x40, 0); Sys_InitFloatTime (); _go32_interrupt_stack_size = 4 * 1024;; _go32_rmcb_stack_size = 4 * 1024; } void Sys_Shutdown (void) { if (!isDedicated) dos_restoreintr (9); if (unlockmem) { dos_unlockmem (&start_of_memory, end_of_memory - (int) &start_of_memory); dos_unlockmem (quakeparms.membase, quakeparms.memsize); } } #define SC_RSHIFT 0x36 #define SC_LSHIFT 0x2a void IN_SendKeyEvents (void) { int k, next; int outkey; // get key events while (keybuf_head != keybuf_tail) { k = keybuf[keybuf_tail++]; keybuf_tail &= (KEYBUF_SIZE - 1); if (k == 0xe0) continue; // special / pause keys next = keybuf[(keybuf_tail - 2) & (KEYBUF_SIZE - 1)]; if (next == 0xe1) continue; // pause key bullshit if (k == 0xc5 && next == 0x9d) { Key_Event (K_PAUSE, true); continue; } // extended keyboard shift key bullshit if ((k & 0x7f) == SC_LSHIFT || (k & 0x7f) == SC_RSHIFT) { if (keybuf[(keybuf_tail - 2) & (KEYBUF_SIZE - 1)] == 0xe0) continue; k &= 0x80; k |= SC_RSHIFT; } if (k == 0xc5 && keybuf[(keybuf_tail - 2) & (KEYBUF_SIZE - 1)] == 0x9d) continue; // more pause bullshit outkey = scantokey[k & 0x7f]; if (k & 0x80) Key_Event (outkey, false); else Key_Event (outkey, true); } } // ======================================================================= // General routines // ======================================================================= /* ================ Sys_Printf ================ */ void Sys_Printf (char *fmt, ...) { va_list argptr; char text[1024]; va_start (argptr, fmt); vsnprintf (text, sizeof (text), fmt, argptr); va_end (argptr); if (cls.state == ca_dedicated) fprintf (stderr, "%s", text); } void Sys_AtExit (void) { // shutdown only once (so Sys_Error can call this function to shutdown, then // print the error message, then call exit without exit calling this function // again) Sys_Shutdown (); } void Sys_Quit (void) { byte screen[80 * 25 * 2]; byte *d; char ver[6]; int i; // load the sell screen before shuting everything down if (registered->int_val) d = COM_LoadHunkFile ("end2.bin"); else d = COM_LoadHunkFile ("end1.bin"); if (d) memcpy (screen, d, sizeof (screen)); // write the version number directly to the end screen snprintf (ver, sizeof (ver), " v%4.2f", VERSION); for (i = 0; i < 6; i++) screen[0 * 80 * 2 + 72 * 2 + i * 2] = ver[i]; Host_Shutdown (); // do the text mode sell screen if (d) { memcpy ((void *) real2ptr (0xb8000), screen, 80 * 25 * 2); // set text pos regs.x.ax = 0x0200; regs.h.bh = 0; regs.h.dl = 0; regs.h.dh = 22; dos_int86 (0x10); } else printf ("couldn't load endscreen.\n"); exit (0); } void Sys_Error (char *error, ...) { va_list argptr; char string[1024]; va_start (argptr, error); vsnprintf (string, sizeof (string), error, argptr); va_end (argptr); Host_Shutdown (); fprintf (stderr, "Error: %s\n", string); // Sys_AtExit is called by exit to shutdown the system exit (0); } int Sys_FileOpenRead (char *path, int *handle) { int h; struct stat fileinfo; h = open (path, O_RDONLY | O_BINARY, 0666); *handle = h; if (h == -1) return -1; if (fstat (h, &fileinfo) == -1) Sys_Error ("Error fstating %s", path); return fileinfo.st_size; } int Sys_FileOpenWrite (char *path) { int handle; umask (0); handle = open (path, O_RDWR | O_BINARY | O_CREAT | O_TRUNC, 0666); if (handle == -1) Sys_Error ("Error opening %s: %s", path, strerror (errno)); return handle; } void Sys_FileClose (int handle) { close (handle); } void Sys_FileSeek (int handle, int position) { lseek (handle, position, SEEK_SET); } int Sys_FileRead (int handle, void *dest, int count) { return read (handle, dest, count); } int Sys_FileWrite (int handle, void *data, int count) { return write (handle, data, count); } /* ================ Sys_MakeCodeWriteable ================ */ void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length) { // it's always writeable } /* ================ Sys_DoubleTime ================ */ double Sys_DoubleTime (void) { int r; unsigned t, tick; double ft, time; static int sametimecount; Sys_PushFPCW_SetHigh (); //{static float t = 0; t=t+0.05; return t;} // DEBUG t = *(unsigned short *) real2ptr (0x46c) * 65536; dos_outportb (0x43, 0); // latch time r = dos_inportb (0x40); r |= dos_inportb (0x40) << 8; r = (r - 1) & 0xffff; tick = *(unsigned short *) real2ptr (0x46c) * 65536; if ((tick != t) && (r & 0x8000)) t = tick; ft = (double) (t + (65536 - r)) / 1193200.0; time = ft - oldtime; oldtime = ft; if (time < 0) { if (time > -3000.0) time = 0.0; else time += 3600.0; } curtime += time; if (curtime == lastcurtime) { sametimecount++; if (sametimecount > 100000) { curtime += 1.0; sametimecount = 0; } } else { sametimecount = 0; } lastcurtime = curtime; Sys_PopFPCW (); return curtime; } /* ================ Sys_InitFloatTime ================ */ void Sys_InitFloatTime (void) { int j; Sys_DoubleTime (); oldtime = curtime; j = COM_CheckParm ("-starttime"); if (j) { curtime = (double) (Q_atof (com_argv[j + 1])); } else { curtime = 0.0; } lastcurtime = curtime; } /* ================ Sys_GetMemory ================ */ void Sys_GetMemory (void) { int j, tsize; j = COM_CheckParm ("-mem"); if (j) { quakeparms.memsize = (int) (Q_atof (com_argv[j + 1]) * 1024 * 1024); quakeparms.membase = malloc (quakeparms.memsize); } else { quakeparms.membase = dos_getmaxlockedmem (&quakeparms.memsize); } fprintf (stderr, "malloc'd: %d\n", quakeparms.memsize); if (COM_CheckParm ("-heapsize")) { tsize = Q_atoi (com_argv[COM_CheckParm ("-heapsize") + 1]) * 1024; if (tsize < quakeparms.memsize) quakeparms.memsize = tsize; } } /* ================ Sys_PageInProgram walks the text, data, and bss to make sure it's all paged in so that the actual physical memory detected by Sys_GetMemory is correct. ================ */ void Sys_PageInProgram (void) { int i, j; end_of_memory = (int) sbrk (0); if (lockmem) { if (dos_lockmem ((void *) &start_of_memory, end_of_memory - (int) &start_of_memory)) Sys_Error ("Couldn't lock text and data"); } if (lockunlockmem) { dos_unlockmem ((void *) &start_of_memory, end_of_memory - (int) &start_of_memory); printf ("Locked and unlocked %d Mb image\n", (end_of_memory - (int) &start_of_memory) / 0x100000); } else if (lockmem) { printf ("Locked %d Mb image\n", (end_of_memory - (int) &start_of_memory) / 0x100000); } else { printf ("Loaded %d Mb image\n", (end_of_memory - (int) &start_of_memory) / 0x100000); } // touch the entire image, doing the 16-page skip so Win95 doesn't think we're // trying to page ourselves in for (j = 0; j < 4; j++) { for (i = (int) &start_of_memory; i < (end_of_memory - 16 * 0x1000); i += 4) { sys_checksum += *(int *) i; sys_checksum += *(int *) (i + 16 * 0x1000); } } } /* ================ Sys_NoFPUExceptionHandler ================ */ void Sys_NoFPUExceptionHandler (int whatever) { printf ("\nError: Quake requires a floating-point processor\n"); exit (0); } /* ================ Sys_DefaultExceptionHandler ================ */ void Sys_DefaultExceptionHandler (int whatever) { } /* ================ main ================ */ int main (int c, char **v) { double time, oldtime, newtime; extern void (*dos_error_func) (char *, ...); static char cwd[1024]; printf ("Quake v%4.2f\n", VERSION); // make sure there's an FPU signal (SIGNOFP, Sys_NoFPUExceptionHandler); signal (SIGABRT, Sys_DefaultExceptionHandler); signal (SIGALRM, Sys_DefaultExceptionHandler); signal (SIGKILL, Sys_DefaultExceptionHandler); signal (SIGQUIT, Sys_DefaultExceptionHandler); signal (SIGINT, Sys_DefaultExceptionHandler); if (fptest_temp >= 0.0) fptest_temp += 0.1; COM_InitArgv (c, v); quakeparms.argc = com_argc; quakeparms.argv = com_argv; dos_error_func = Sys_Error; Sys_DetectWin95 (); Sys_PageInProgram (); Sys_GetMemory (); atexit (Sys_AtExit); // in case we crash getwd (cwd); if (cwd[Q_strlen (cwd) - 1] == '/') cwd[Q_strlen (cwd) - 1] = 0; quakeparms.basedir = cwd; // "f:/quake"; isDedicated = (COM_CheckParm ("-dedicated") != 0); Sys_Init (); if (!isDedicated) dos_registerintr (9, TrapKey); //Sys_InitStackCheck (); Host_Init (&quakeparms); //Sys_StackCheck (); //Con_Printf ("Top of stack: 0x%x\n", &time); oldtime = Sys_DoubleTime (); while (1) { newtime = Sys_DoubleTime (); time = newtime - oldtime; if (cls.state == ca_dedicated && (time < sys_ticrate->value)) continue; Host_Frame (time); //Sys_StackCheck (); oldtime = newtime; } }