/* msdos.c * * ``pinfocom'' -- a portable Infocom Inc. data file interpreter. * Copyright (C) 1987-1992 InfoTaskForce * * 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; see the file COPYING. If not, write to the * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * $Header: RCS/msdos.c,v 3.0 1992/10/21 16:56:19 pds Stab $ */ #include #include #include #include #include #include #include #include #include "infocom.h" #ifndef free extern void free(); #endif #ifdef NEED_ERRNO extern int errno; #endif /* * Global Variables */ /* * Variable: scr_usage * * Description: * This variable should contain a usage string for any extra * command-line options available through this terminal * interface. */ const char *scr_usage = "[-C]"; /* * Variable: scr_long_usage * * Description: * This variable should contain a more verbose usage string * detailing the command-line options available through this * terminal interface, one option per line. */ const char *scr_long_usage = "\t-C\tdon't use color"; /* * Variable: scr_opt_list * * Description: * This variable should contain a getopt(3)-style option list for * any command-line options available through this terminal * interface. */ const char *scr_opt_list = "C"; /* * Local Variables */ #define ENV_FLAGS "PI_FLAGS" /* * Options to use standard I/O, allowing redirection to/from files/devices. * Using stream output means we can't set scroll windows. Using stream input * means we use DOS's command line editor for input and can't properly erase * --More-- prompts. */ /* #define STREAM_OUT */ /* #define STREAM_IN */ static FILE *scr_fp=NULL; static int scr_columns=80, scr_indent=0, stat_size=0; static int scr_lines=24, scr_lineco=0, scr_lowleft=24; static int scr_width=80, scr_height=24; static Bool in_status=0, allow_color=1, use_color=1; #ifdef STREAM_OUT #define outstr(s) fputs(s, stdout) #define outchar(c) putchar(c) #else #define outstr(s) cputs(s) #define outchar(c) putch(c) #endif #define newline() outchar('\r'),outchar('\n') #if defined(STREAM_OUT) || !defined(__TURBOC__) /* * The following functions produce ANSI codes to produce generic video * effects. */ void term_clreol() { outstr("\033[k"); } void term_clrscr() { outstr("\033[2J"); } void term_home() { outstr("\033[H"); } void term_lowleft() { printf("\033[%d;0H", scr_lowleft); } #define term_statwin() /* * In the future, color should be a command line option, and perhaps * specific color settings. * * Some ANSI SGR parameters: * 0 reset * 1 bold * 5 blink * 7 reverse * 3x foreground color * 4x background color * (0-7: black/red/green/yellow/blue/magenta/cyan/white) */ void term_text_color() { /* white on blue, or reset (to white on black) */ outstr(use_color ? "\033[0;37;44m" : "\033[0m"); } void term_score_color() { /* red on white, or reverse video (black on white) */ outstr(use_color ? "\033[31;47m" : "\033[7m"); } #define term_wait_color() term_score_color() void term_status_color() { /* white on green, or on black */ outstr(use_color ? "\033[1;42m" : "\033[1m"); } void term_mesg_color() { /* bold red, or just bold (white) */ outstr(use_color ? "\033[1;31m" : "\033[1m"); } void term_input_color() { /* yellow on blue */ if (use_color) outstr("\033[1;33;44m"); } void term_termoff() { if (use_color) outstr("\033[0m"); } #define term_termon() #define term_setscrl(t, b) #else /* use convenient Turbo C library functions */ #define term_clreol() clreol() #define term_clrscr() clrscr() void gotoxy A2(int, col, int, row) /* * Note: * If we used the Turbo C library function, the row and column would be * window relative, but we want absolute. */ { union REGS regs; /* Set Cursor Position */ regs.x.ax = 0x0200; regs.x.bx = 0; regs.h.dh = --row; regs.h.dl = --col; int86(0x10,®s,®s); } #define term_home() gotoxy(1,1) #define term_lowleft() gotoxy(1,scr_lowleft) void term_text_color() { textcolor(LIGHTGRAY); textbackground(use_color ? BLUE : BLACK); } void term_score_color() { textcolor(use_color ? RED : BLACK); textbackground(LIGHTGRAY); } #define term_wait_color() term_score_color() void term_status_color() { textcolor(WHITE); textbackground(use_color ? GREEN : BLACK); } void term_mesg_color() { textcolor(use_color ? LIGHTRED : WHITE); } void term_input_color() { if (use_color) textcolor(YELLOW); } void term_termoff() { textcolor(LIGHTGRAY); textbackground(BLACK); } #define term_termon() void term_setscrl A2(int, top, int, bottom) { window(1, ++top, scr_width, ++bottom); } #endif /* * Function: get_scr_info() * * Arguments: * rows pointer to number of rows variable * cols pointer to number of columns variable * color_mode pointer to boolean in-color variable * * Description: * This function does the nasty platform-specific BIOS calls to * determine the screen mode and dimensions. int86() is common * to MS-DOS compilers including MSC and Turbo C. */ void get_scr_info A3(int *, rows, int *, cols, Bool *, color_mode) { union REGS regs; /* load default row count, in case no EGA or better */ regs.h.dl = 24; /* Get Font Information, current font - returns rows in DL */ regs.x.bx = 0; regs.x.ax = 0x1130; int86(0x10,®s,®s); /* Get Video Mode - returns columns in AH, mode in AL */ regs.h.ah = 0x0f; int86(0x10,®s,®s); /* if mode is BW 40x25, BW 80x25, or mono 80x25 */ *color_mode = !(regs.h.al == 0 || regs.h.al == 2 || regs.h.al == 7); /* store row/column counts */ *rows = regs.h.dl + 1; *cols = regs.h.ah; } /* * Function: scr_cmdarg() * * Arguments: * argc number of original arguments * argvp pointer to array of strings containing args * * Returns: * New number of arguments. * * Description: * This function is called before any command line parsing is * done. If the interface needs to insert any interpreter * options into the command-line list then they should be added * here; otherwise the function should do nothing. They should be * added between (*argvp)[0] and (*argvp)[1]. Note that * (*argvp)[0] must be the command name and (*argvp)[argc] must * be a null pointer. */ int scr_cmdarg A2(int, argc, char ***, argvp) { char *envp; /* * If we find an ENV_FLAGS environment variable then add the flags * into the command line. */ if ((envp = getenv(ENV_FLAGS)) != NULL) { char **newv; char *cp; int i, newc=0; /* * Count the number of args in the cmd line */ cp = envp; do { while ((*cp != '\0') && isspace(*cp)) ++cp; if (*cp != '\0') ++newc; while ((*cp != '\0') && !isspace(*cp)) ++cp; } while (*cp != '\0'); /* * Allocate enough space for the old and new args */ newv = (char **)xmalloc(sizeof(char *) * (argc+newc+1)); argc += newc; /* * Copy the arguments into their proper places. */ newv[0] = (*argvp)[0]; for (i=1, cp=envp; i<=newc; ++i) { while ((*cp != '\0') && isspace(*cp)) ++cp; newv[i] = cp; while ((*cp != '\0') && !isspace(*cp)) ++cp; *(cp++) = '\0'; } for (; i= scr_lines)) { scr_wait("--More--"); scr_lineco = 0; } for (i=scr_indent; i>0; --i) putc(' ', fp); for (p = sp; p < ep; ++p) putc(*p, fp); putc('\n', fp); } if (*ep == '\0') break; sp = ep + 1; } return (sp); } #ifdef STREAM_OUT #define scr_putcon(flags, buf, max) str_putbuf(stdout, flags, buf, max) #else static const char * scr_putcon A3( int, flags, const char *, buf, int, max ) { const char *sp; const char *ep; for (sp = buf; ;) { ep = chop_buf(sp, max); if ((*ep != '\0') || !(flags & PB_PROMPT)) { const char *p; int i; if (!(flags & PB_NO_PAGING) && (scr_lineco++ >= scr_lines)) { scr_wait("--More--"); scr_lineco = 0; } for (i=scr_indent; i>0; --i) outchar(' '); for (p = sp; p < ep; ++p) outchar(*p); newline(); } if (*ep == '\0') break; sp = ep + 1; } return (sp); } #endif /* * Function: scr_putline() * * Arguments: * buffer Line to be printed. * * Description: * This function is passed a nul-terminated string and it should * display the string on the terminal. It will *not* contain a * newline character. * * This function should perform whatever wrapping, paging, etc. * is necessary, print the string, and generate a final linefeed. * * If the TI supports proportional-width fonts, * F2_IS_SET(B_FIXED_FONT) should be checked as appropriate. * * If the TI supports scripting, F2_IS_SET(B_SCRIPTING) should be * checked as appropriate. */ void scr_putline A1(const char *, buffer) { scr_putcon(in_status || !gflags.paged, buffer, scr_columns); if (F2_IS_SET(B_SCRIPTING)) scr_putbuf(scr_fp, 1, buffer, scr_width); } /* * Function: scr_putscore() * * Description: * This function prints the ti_location and ti_status strings * if it can and if status line printing is enabled. */ void scr_putscore() { if (gflags.pr_status) { extern char *ti_location; extern char *ti_status; int i; /* * Go to the "home" position (top left corner) then clear to * the EOL, then go into reverse video mode. */ term_home(); term_score_color(); term_clreol(); outstr(ti_location); i = scr_columns+scr_indent - strlen(ti_location) - strlen(ti_status); for (; i != 0; --i) outchar(' '); outstr(ti_status); /* * Turn off reverse video, then jump back down to the * lower left corner of the screen. */ term_text_color(); term_lowleft(); } else if (stat_size) { /* * Leave an empty line where the status line would be. */ term_home(); term_score_color(); term_clreol(); term_text_color(); term_lowleft(); } } /* * Function: scr_putsound() * * Arguments: * number sound number to play * action action to perform * volume volume to play sound at * argc number of valid arguments * * Description: * This function plays the sound specified if it can; if not it * prints a line to that effect. * * If the `argc' value is 1, then the we play `number' of beeps * (usually the ^G character). * * If `argc' >1, the `action' argument is used as follows: * * 2: play sound file * 3: stop playing sound file * 4: free sound resources * * If `argc' >2, the `volume' argument is between 1 and 8 and is * a volume to play the sound at. */ void scr_putsound A4(int, number, int, action, int, volume, int, argc) { if (argc == 1) { while (number--) outchar('\a'); } else if (argc == 3 && action == 2) { char buf[81]; sprintf(buf, "Sound not supported! (number $%02x, volume %d)", number, volume); scr_putmesg(buf, 0); } } /* * Function: scr_putmesg() * * Arguments: * buffer message string to be printed. * is_err 1 if message is an error message, 0 if it's not. * * Description: * This function prints out a message from the interpreter, not * from the game itself. Often these are errors (IS_ERR==1) * but not necessarily. */ void scr_putmesg A2(const char *, buffer, Bool, is_err) { static char *buf = ""; int blen; blen = strlen(buffer); if (strlen(buf) < blen + 12) { if (strlen(buf) == 0) buf = xmalloc(blen + 13); else buf = xrealloc(buf, blen + 13); } sprintf(buf, "%s%s", is_err ? "ERROR: " : "", buffer); term_mesg_color(); scr_putline(""); scr_putline(buf); scr_putline(""); term_text_color(); } /* * Function: scr_getstr() * * Description: * This is an internal function which performs the meat of * reading a string, throwing away the excess, etc. It returns * the number of characters read. * * Notes: * This function is *not* a part of the terminal interface; it's * just an interal helper function. */ static int scr_getstr A4( const char *, prompt, int, length, char *, buffer, Bool, is_filenm ) { #ifndef STREAM_IN outstr(prompt); term_input_color(); buffer[0] = length > 255 ? 253 : length - 2; cgets(buffer); length = buffer[1]; strcpy(buffer, &buffer[2]); term_text_color(); newline(); #else int c; outstr(prompt); term_input_color(); buffer[0] = '\0'; buffer[length-2] = '\0'; fgets(buffer, length, stdin); /* * If there's more beyond the max length of our buffer, read * it in and throw it away... */ if ((buffer[length-2] != '\0') && (buffer[length-2] != '\n') && ((c = getchar()) != EOF) && (c != '\n')) { term_mesg_color(); fputs("[Input line too long. Flushing: `", stdout); do { outchar(c); } while (((c = getchar()) != EOF) && (c != '\n')); scr_putline("']"); } else { /* * Punt the last \n... */ length = strlen(buffer) - 1; buffer[length] = '\0'; } term_text_color(); #endif return (length); } /* * Function: scr_getline() * * Arguments: * prompt - prompt to be printed * length - total size of BUFFER * buffer - buffer to return nul-terminated response in * * Returns: * # of chars stored in BUFFER * * Description: * Reads a line of input and returns it. Handles all "special * operations" such as readline history support, shell escapes, * etc. invisibly to the caller. Note that the returned BUFFER * will be at most LENGTH-1 chars long because the last char will * always be the nul character. * * If the command begins with ESC_CHAR then it's an interpreter * escape command; call ti_escape() with the rest of the line, * then ask for another command. * * Notes: * May print the STATUS buffer more than once if necessary (i.e., * a shell escape messed up the screen, a history listing was * generated, etc.). */ int scr_getline A3( const char *, prompt, int, length, char *, buffer ) { const char *pp; int len; /* * Loop until we've read a line. Note that shell commands don't * count, so if we get one then read another line. */ for (;;) { /* * Print all the prompt except the last line */ pp = scr_putcon(PB_PROMPT, prompt, scr_columns); scr_putscore(); scr_lineco = 0; if ((len = scr_getstr(pp, length, buffer, 0)) == -1) continue; if (!len) break; /* * If it's an interpreter escape, call the function then try * again. */ if (buffer[0] == ESC_CHAR[0]) { ti_escape(&buffer[1]); /* * Adjust scroll region in case the status line was toggled. */ if (!stat_size) { term_setscrl(gflags.pr_status, scr_lowleft-1); term_lowleft(); } scr_putscore(); continue; } break; } if (F2_IS_SET(B_SCRIPTING)) { pp = scr_putbuf(scr_fp, PB_NO_PAGING|PB_PROMPT, prompt, scr_width); fputs(pp, scr_fp); scr_putbuf(scr_fp, PB_NO_PAGING, buffer, scr_width); } return (len); } /* * Function: scr_window() * * Arguments: * size - 0 to delete, non-0 means create with SIZE. * * Description: * Causes a status window to be created if supported by the * terminal interface; note this function won't be called unless * F1_SETB(B_STATUS_WIN) is invoked in scr_begin(). */ void scr_window A1(int, size) { #ifndef STREAM_OUT int i; if (!size) { if (!stat_size) return; scr_lines += stat_size; stat_size = 0; term_setscrl(gflags.pr_status, scr_lowleft-1); scr_set_win(0); return; } stat_size = size; scr_lines -= stat_size; term_setscrl(stat_size+1, scr_lowleft-1); /* * Clean out the window */ scr_set_win(1); for (i=stat_size; i != 0; --i) { term_clreol(); newline(); } scr_set_win(0); #endif } /* * Function: scr_set_win() * * Arguments: * win - 0==select text window, 1==select status window * * Description: * Selects a different window. This function won't be called * unless call F1_SETB(B_STATUS_WIN) in scr_begin(). * * Notes: */ void scr_set_win A1(int, win) { #ifndef STREAM_OUT /* * Select the status window; just move the cursor there */ if (win) { in_status = 1; term_home(); newline(); term_status_color(); } else { in_status = 0; term_lowleft(); term_text_color(); } #endif } /* * Function: scr_open_sf() * * Arguments: * length - total size of BUFFER * buffer - buffer to return nul-terminated filename in * type - SF_SAVE opening the file to save into * SF_RESTORE opening the file to restore from * SF_SCRIPT opening a file for scripting * * Returns: * FILE* - reference to the opened file, or * NULL - errno==0: operation cancelled, else error opening file * * Description: * Obtains the name of the file to be opened for writing (if * TYPE==SF_SAVE or SF_SCRIPT) or reading (if TYPE==SF_RESTORE), * opens the file with fopen(), and returns the FILE*. * * The name of the file should be stored in BUFFER. Upon initial * calling BUFFER contains a possible default filename. * * if TYPE==2 then don't ask the user for a name, just use * BUFFER. This means we got the -r option to restore the file. * Also note that unless gflags.game_state!=NOT_INIT, scr_begin() * has not been called (we're restoring before printing info). * * If the fopen() fails just return NULL: if errno!=0 then an * error will be printed. * * Notes: * History is turned off here (why would anyone want it?) */ FILE * scr_open_sf A3( int, length, char *, buffer, int, type ) { #define FN_PROMPT "Enter file name (or Q to quit)" FILE *fp; char prompt[MAXPATHLEN+sizeof(FN_PROMPT)+6]; char *cp, *p; int len; if (length == 0) { errno = 0; return (fopen(buffer, type==SF_RESTORE ? "rb" : type==SF_SAVE ? "wb" : "w")); } cp = xmalloc(length); retry: scr_putscore(); scr_lineco=0; sprintf(prompt, "%s [%s]: ", FN_PROMPT, buffer); len = scr_getstr(prompt, length, cp, 1); /* * Remove any excess whitespace from the end of the string. * If the input is really empty, then use the initial buffer. * If not copy over the string into the buffer. */ for (p = &cp[len-1]; len && isspace(*p); --len, --p) {} p[1] = '\0'; fp = NULL; errno = 0; if (((len == 1) && (cp[0] == 'q')) || (len && iscntrl(cp[0]))) goto done; if (len) strcpy(buffer, cp); len = strlen(buffer); /* * Open the file for reading. If we're saving and it already * exists then ask if the user wants to overwrite it. If not then * ask for another name. */ if (len) { fp = fopen(buffer, "rb"); if (type != SF_RESTORE) { if (fp != NULL) { char over_p[MAXPATHLEN+40]; fclose(fp); sprintf(over_p, "File `%s' exists: overwrite (y/n/q) [y]? ", buffer); if (scr_getstr(over_p, length, cp, 1)) if (cp[0] == 'q') { fp = NULL; goto done; } else if (cp[0] != 'y') goto retry; } fp = fopen(buffer, type==SF_SAVE ? "wb" : "w"); } } done: free(cp); if (type == SF_SCRIPT) scr_fp = fp; return (fp); } /* * Function: scr_close_sf() * * Arguments: * filenm - name of file just processed * fp - FILE* to open saved file * type - SF_SAVE closing a saved game file * SF_RESTORE closing a restored game file * SF_SCRIPT closing a scripting file * * Description: * This function will be called immediately after a successful * save or restore of a game file, so that if the interface needs * to perform any actions related to the saved game it may. It * will also be called when the interpreter notices that * scripting has been turned off. It should at least close the * file. * * This function will only be called if the save/restore of the * game succeeded; if it fails the file will be closed by the * interpreter. */ void scr_close_sf A3( const char *, filenm, FILE *, fp, int, type ) { fclose(fp); }