From: Stefan Jokisch Date: Wed, 7 Dec 2011 15:46:49 +0000 (+0000) Subject: Released Frotz232Src.zip X-Git-Url: https://scope-eye.net/git/?a=commitdiff_plain;h=d342d5faea30d5a63289d6421a8123a9e3c09cc3;p=liskon_frotz.git Released Frotz232Src.zip --- d342d5faea30d5a63289d6421a8123a9e3c09cc3 diff --git a/bcfrotz.h b/bcfrotz.h new file mode 100644 index 0000000..70c99a1 --- /dev/null +++ b/bcfrotz.h @@ -0,0 +1,85 @@ +/* + * "BCfrotz.h" + * + * Borland C interface, declarations + * + */ + +#define byte0(v) ((byte *)&v)[0] +#define byte1(v) ((byte *)&v)[1] +#define byte2(v) ((byte *)&v)[2] +#define byte3(v) ((byte *)&v)[3] +#define word0(v) ((word *)&v)[0] +#define word1(v) ((word *)&v)[1] + +#ifndef HISTORY_MIN_ENTRY +#define HISTORY_MIN_ENTRY 1 +#endif + +#define SPECIAL_KEY_MIN 256 +#define SPECIAL_KEY_HOME 256 +#define SPECIAL_KEY_END 257 +#define SPECIAL_KEY_WORD_LEFT 258 +#define SPECIAL_KEY_WORD_RIGHT 259 +#define SPECIAL_KEY_DELETE 260 +#define SPECIAL_KEY_INSERT 261 +#define SPECIAL_KEY_PAGE_UP 262 +#define SPECIAL_KEY_PAGE_DOWN 263 +#define SPECIAL_KEY_TAB 264 +#define SPECIAL_KEY_MAX 264 + +#define _MONO_ 0 +#define _TEXT_ 1 +#define _CGA_ 2 +#define _MCGA_ 3 +#define _EGA_ 4 +#define _AMIGA_ 5 + +typedef unsigned char byte; +typedef unsigned short word; + +extern display; + +extern cursor_x; +extern cursor_y; + +extern char latin1_to_ibm[]; +extern char latin1_to_ascii[]; + +extern byte text_bg; +extern byte text_fg; + +extern byte scrn_attr; + +extern user_background; +extern user_foreground; +extern user_emphasis; +extern user_reverse_bg; +extern user_reverse_fg; +extern user_screen_height; +extern user_screen_width; +extern user_tandy_bit; +extern user_bold_typing; +extern user_random_seed; +extern user_font; + +extern char stripped_story_name[]; +extern char *prog_name; + +extern current_bg; +extern current_fg; +extern current_style; +extern current_font; + +extern scaler; + +/* BCinit */ int dectoi (const char *); +/* BCinit */ int hextoi (const char *); +/* BCmouse */ bool detect_mouse (void); +/* BCmouse */ int read_mouse (void); +/* BCpic */ bool init_pictures (void); +/* BCpic */ void reset_pictures (void); +/* BCsmpl */ bool init_sound (void); +/* BCsmpl */ void reset_sound (void); +/* BCtext */ void switch_scrn_attr (bool); +/* BCtext */ void load_fonts (const char *); diff --git a/bcinit.c b/bcinit.c new file mode 100644 index 0000000..3fba0d9 --- /dev/null +++ b/bcinit.c @@ -0,0 +1,755 @@ +/* + * file "BCinit.c" + * + * Borland C front end, initialisation + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define INFORMATION "\ +\n\ +FROTZ V2.32 - interpreter for all Infocom games. Complies with standard\n\ +1.0 of Graham Nelson's specification. Written by Stefan Jokisch in 1995-7\n\ +\n\ +Syntax: frotz [options] story-file\n\ +\n\ + -a watch attribute setting \t -l # left margin\n\ + -A watch attribute testing \t -o watch object movement\n\ + -b # background colour \t -O watch object locating\n\ + -B # reverse background colour\t -p alter piracy opcode\n\ + -c # context lines \t -r # right margin\n\ + -d # display mode (see below) \t -s # random number seed value\n\ + -e # emphasis colour [mode 1] \t -S # transscript width\n\ + -f # foreground colour \t -t set Tandy bit\n\ + -F # reverse foreground colour\t -T bold typing [modes 2+4+5]\n\ + -g # font [mode 5] (see below)\t -u # slots for multiple undo\n\ + -h # screen height \t -w # screen width\n\ + -i ignore runtime errors \t -x expand abbreviations g/x/z\n\ +\n\ +Fonts are 0 (fixed), 1 (sans serif), 2 (comic), 3 (times), 4 (serif).\n\ +\n\ +Display modes are 0 (mono), 1 (text), 2 (CGA), 3 (MCGA), 4 (EGA), 5 (Amiga)." + +extern unsigned cdecl _heaplen = 0x800 + 4 * BUFSIZ; +extern unsigned cdecl _stklen = 0x800; + +extern const char *optarg; +extern int optind; + +int cdecl getopt (int, char *[], const char *); + +static const char *progname = NULL; + +extern char script_name[]; +extern char command_name[]; +extern char save_name[]; +extern char auxilary_name[]; + +char stripped_story_name[10]; + +int display = -1; + +int user_background = -1; +int user_foreground = -1; +int user_emphasis = -1; +int user_bold_typing = -1; +int user_reverse_bg = -1; +int user_reverse_fg = -1; +int user_screen_height = -1; +int user_screen_width = -1; +int user_tandy_bit = -1; +int user_random_seed = -1; +int user_font = 1; + +static byte old_video_mode = 0; + +static void interrupt (*oldvect) () = NULL; + +/* + * dectoi + * + * Convert a string containing a decimal number to integer. The string may + * be NULL, but it must not be empty. + * + */ + +int dectoi (const char *s) +{ + int n = 0; + + if (s != NULL) + + do { + + n = 10 * n + (*s & 15); + + } while (*++s > ' '); + + return n; + +}/* dectoi */ + +/* + * hextoi + * + * Convert a string containing a hex number to integer. The string may be + * NULL, but it must not be empty. + * + */ + +int hextoi (const char *s) +{ + int n = 0; + + if (s != NULL) + + do { + + n = 16 * n + (*s & 15); + + if (*s > '9') + n += 9; + + } while (*++s > ' '); + + return n; + +}/* hextoi */ + +/* + * cleanup + * + * Shut down the IO interface: free memory, close files, restore + * interrupt pointers and return to the previous video mode. + * + */ + +static void cleanup (void) +{ + + reset_sound (); + reset_pictures (); + + asm mov ah,0 + asm mov al,old_video_mode + asm int 0x10 + + setvect (0x1b, oldvect); + +}/* cleanup */ + +/* + * fast_exit + * + * Handler routine to be called when the crtl-break key is pressed. + * + */ + +static void interrupt fast_exit () +{ + + cleanup (); exit (EXIT_FAILURE); + +}/* fast_exit */ + +/* + * os_fatal + * + * Display error message and exit program. + * + */ + +void os_fatal (const char *s) +{ + + if (h_interpreter_number) + os_reset_screen (); + + /* Display error message */ + + fputs ("\nFatal error: ", stderr); + fputs (s, stderr); + fputs ("\n", stderr); + + /* Abort program */ + + exit (EXIT_FAILURE); + +}/* os_fatal */ + +/* + * parse_options + * + * Parse program options and set global flags accordingly. + * + */ + +static void parse_options (int argc, char **argv) +{ + int c; + + do { + + int num = 0; + + c = getopt (argc, argv, "aAb:B:c:d:e:f:F:g:h:il:oOpr:s:S:tTu:w:x"); + + if (optarg != NULL) + num = dectoi (optarg); + + if (c == 'a') + option_attribute_assignment = 1; + if (c == 'A') + option_attribute_testing = 1; + if (c == 'b') + user_background = num; + if (c == 'B') + user_reverse_bg = num; + if (c == 'c') + option_context_lines = num; + if (c == 'd') + display = optarg[0] | 32; + if (c == 'e') + user_emphasis = num; + if (c == 'T') + user_bold_typing = 1; + if (c == 'f') + user_foreground = num; + if (c == 'F') + user_reverse_fg = num; + if (c == 'g') + user_font = num; + if (c == 'h') + user_screen_height = num; + if (c == 'i') + option_ignore_errors = 1; + if (c == 'l') + option_left_margin = num; + if (c == 'o') + option_object_movement = 1; + if (c == 'O') + option_object_locating = 1; + if (c == 'p') + option_piracy = 1; + if (c == 'r') + option_right_margin = num; + if (c == 's') + user_random_seed = num; + if (c == 'S') + option_script_cols = num; + if (c == 't') + user_tandy_bit = 1; + if (c == 'u') + option_undo_slots = num; + if (c == 'w') + user_screen_width = num; + if (c == 'x') + option_expand_abbreviations = 1; + + } while (c != EOF); + +}/* parse_options */ + +/* + * os_process_arguments + * + * Handle command line switches. Some variables may be set to activate + * special features of Frotz: + * + * option_attribute_assignment + * option_attribute_testing + * option_context_lines + * option_object_locating + * option_object_movement + * option_left_margin + * option_right_margin + * option_ignore_errors + * option_piracy + * option_undo_slots + * option_expand_abbreviations + * option_script_cols + * + * The global pointer "story_name" is set to the story file name. + * + */ + +void os_process_arguments (int argc, char *argv[]) +{ + const char *p; + int i; + + /* Parse command line options */ + + parse_options (argc, argv); + + if (optind != argc - 1) { + puts (INFORMATION); + exit (EXIT_FAILURE); + } + + /* Set the story file name */ + + story_name = argv[optind]; + + /* Strip path and extension off the story file name */ + + p = story_name; + + for (i = 0; story_name[i] != 0; i++) + if (story_name[i] == '\\' || story_name[i] == ':') + p = story_name + i + 1; + + for (i = 0; p[i] != 0 && p[i] != '.' && i < 8; i++) + stripped_story_name[i] = p[i]; + + stripped_story_name[i] = 0; + + /* Create nice default file names */ + + strcpy (script_name, stripped_story_name); + strcpy (command_name, stripped_story_name); + strcpy (save_name, stripped_story_name); + strcpy (auxilary_name, stripped_story_name); + + strcat (script_name, ".scr"); + strcat (command_name, ".rec"); + strcat (save_name, ".sav"); + strcat (auxilary_name, ".aux"); + + /* Save the executable file name */ + + progname = argv[0]; + +}/* os_process_arguments */ + +/* + * standard_palette + * + * Set palette registers to EGA default values and call VGA BIOS to + * use DAC registers #0 to #63. + * + */ + +static void standard_palette (void) +{ + + static byte palette[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x00 /* last one is the overscan register */ + }; + + if (display == _AMIGA_) { + asm mov ax,0x1002 + asm lea dx,palette + asm push ds + asm pop es + asm int 0x10 + asm mov ax,0x1013 + asm mov bx,0x0001 + asm int 0x10 + } + +}/* standard_palette */ + +/* + * special_palette + * + * Set palette register #i to value i and call VGA BIOS to use DAC + * registers #64 to #127. + * + */ + +static void special_palette (void) +{ + + static byte palette[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00 /* last one is the overscan register */ + }; + + if (display == _AMIGA_) { + asm mov ax,0x1002 + asm mov dx,offset palette + asm push ds + asm pop es + asm int 0x10 + asm mov ax,0x1013 + asm mov bx,0x0101 + asm int 0x10 + } + +}/* special_palette */ + +/* + * os_init_screen + * + * Initialise the IO interface. Prepare the screen and other devices + * (mouse, sound board). Set various OS depending story file header + * entries: + * + * h_config (aka flags 1) + * h_flags (aka flags 2) + * h_screen_cols (aka screen width in characters) + * h_screen_rows (aka screen height in lines) + * h_screen_width + * h_screen_height + * h_font_height (defaults to 1) + * h_font_width (defaults to 1) + * h_default_foreground + * h_default_background + * h_interpreter_number + * h_interpreter_version + * h_user_name (optional; not used by any game) + * + * Finally, set reserve_mem to the amount of memory (in bytes) that + * should not be used for multiple undo and reserved for later use. + * + */ + +void os_init_screen (void) +{ + static byte zcolour[] = { + BLACK_COLOUR, + BLUE_COLOUR, + GREEN_COLOUR, + CYAN_COLOUR, + RED_COLOUR, + MAGENTA_COLOUR, + BROWN + 16, + LIGHTGRAY + 16, + GREY_COLOUR, + LIGHTBLUE + 16, + LIGHTGREEN + 16, + LIGHTCYAN + 16, + LIGHTRED + 16, + LIGHTMAGENTA + 16, + YELLOW_COLOUR, + WHITE_COLOUR + }; + + static struct { /* information on modes 0 to 5 */ + byte vmode; + word width; + word height; + byte font_width; + byte font_height; + byte fg; + byte bg; + } info[] = { + { 0x07, 80, 25, 1, 1, LIGHTGRAY + 16, BLACK_COLOUR }, /* MONO */ + { 0x03, 80, 25, 1, 1, LIGHTGRAY + 16, BLUE_COLOUR }, /* TEXT */ + { 0x06, 640, 200, 8, 8, WHITE_COLOUR, BLACK_COLOUR }, /* CGA */ + { 0x13, 320, 200, 5, 8, WHITE_COLOUR, GREY_COLOUR }, /* MCGA */ + { 0x0e, 640, 200, 8, 8, WHITE_COLOUR, BLUE_COLOUR }, /* EGA */ + { 0x12, 640, 400, 8, 16, WHITE_COLOUR, BLACK_COLOUR } /* AMIGA */ + }; + + static struct { /* information on modes A to E */ + word vesamode; + word width; + word height; + } subinfo[] = { + { 0x001, 40, 25 }, + { 0x109, 132, 25 }, + { 0x10b, 132, 50 }, + { 0x108, 80, 60 }, + { 0x10c, 132, 60 } + }; + + int subdisplay; + + /* Get the current video mode. This video mode will be selected + when the program terminates. It's also useful to auto-detect + monochrome boards. */ + + asm mov ah,15 + asm int 0x10 + asm mov old_video_mode,al + + /* If the display mode has not already been set by the user then see + if this is a monochrome board. If so, set the display mode to 0. + Otherwise check the graphics flag of the story. Select a graphic + mode if it is set or if this is a V6 game. Select text mode if it + is not. */ + + if (display == -1) + + if (old_video_mode == 7) + display = '0'; + else if (h_version == V6 || (h_flags & GRAPHICS_FLAG)) + display = '5'; + else + display = '1'; + + /* Activate the desired display mode. All VESA text modes are very + similar to the standard text mode; in fact, only here we need to + know which VESA mode is used. */ + + if (display >= '0' && display <= '5') { + subdisplay = -1; + display -= '0'; + _AL = info[display].vmode; + _AH = 0; + } else if (display == 'a') { + subdisplay = 0; + display = 1; + _AL = 0x01; + _AH = 0; + } else if (display >= 'b' && display <= 'e') { + subdisplay = display - 'a'; + display = 1; + _BX = subinfo[subdisplay].vesamode; + _AX = 0x4f02; + } + + geninterrupt (0x10); + + /* Make various preparations */ + + if (display <= _TEXT_) { + + /* Enable bright background colours */ + + asm mov ax,0x1003 + asm mov bl,0 + asm int 0x10 + + /* Turn off hardware cursor */ + + asm mov ah,1 + asm mov cx,0xffff + asm int 0x10 + + } else { + + load_fonts (progname); + + if (display == _AMIGA_) { + + scaler = 2; + + /* Use resolution 640 x 400 instead of 640 x 480. BIOS doesn't + help us here since this is not a standard resolution. */ + + outportb (0x03c2, 0x63); + + outport (0x03d4, 0x0e11); + outport (0x03d4, 0xbf06); + outport (0x03d4, 0x1f07); + outport (0x03d4, 0x9c10); + outport (0x03d4, 0x8f12); + outport (0x03d4, 0x9615); + outport (0x03d4, 0xb916); + + } + + } + +#if !defined(__SMALL__) && !defined (__TINY__) && !defined (__MEDIUM__) + + /* Set the amount of memory to reserve for later use. It takes + some memory to open command, script and game files. If Frotz + is compiled in a small memory model then memory for opening + files is allocated on the "near heap" while other allocations + are made on the "far heap", i.e. we need not reserve memory + in this case. */ + + reserve_mem = 4 * BUFSIZ; + +#endif + + /* Amiga emulation under V6 needs special preparation. */ + + if (display == _AMIGA_ && h_version == V6) { + + user_reverse_fg = -1; + user_reverse_bg = -1; + zcolour[LIGHTGRAY] = LIGHTGREY_COLOUR; + zcolour[DARKGRAY] = DARKGREY_COLOUR; + + special_palette (); + + } + + /* Set various bits in the configuration byte. These bits tell + the game which features are supported by the interpreter. */ + + if (h_version == V3 && user_tandy_bit != -1) + h_config |= CONFIG_TANDY; + if (h_version == V3) + h_config |= CONFIG_SPLITSCREEN; + if (h_version == V3 && (display == _MCGA_ || (display == _AMIGA_ && user_font != 0))) + h_config |= CONFIG_PROPORTIONAL; + if (h_version >= V4 && display != _MCGA_ && (user_bold_typing != -1 || display <= _TEXT_)) + h_config |= CONFIG_BOLDFACE; + if (h_version >= V4) + h_config |= CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMEDINPUT; + if (h_version >= V5 && display != _MONO_ && display != _CGA_) + h_config |= CONFIG_COLOUR; + if (h_version >= V5 && display >= _CGA_ && init_pictures ()) + h_config |= CONFIG_PICTURES; + + /* Handle various game flags. These flags are set if the game wants + to use certain features. The flags must be cleared if the feature + is not available. */ + + if (h_flags & GRAPHICS_FLAG) + if (display <= _TEXT_) + h_flags &= ~GRAPHICS_FLAG; + if (h_version == V3 && (h_flags & OLD_SOUND_FLAG)) + if (!init_sound ()) + h_flags &= ~OLD_SOUND_FLAG; + if (h_flags & SOUND_FLAG) + if (!init_sound ()) + h_flags &= ~SOUND_FLAG; + if (h_version >= V5 && (h_flags & UNDO_FLAG)) + if (!option_undo_slots) + h_flags &= ~UNDO_FLAG; + if (h_flags & MOUSE_FLAG) + if (subdisplay != -1 || !detect_mouse ()) + h_flags &= ~MOUSE_FLAG; + if (h_flags & COLOUR_FLAG) + if (display == _MONO_ || display == _CGA_) + h_flags &= ~COLOUR_FLAG; + h_flags &= ~MENU_FLAG; + + /* Set the screen dimensions, font size and default colour */ + + h_screen_width = info[display].width; + h_screen_height = info[display].height; + h_font_height = info[display].font_height; + h_font_width = info[display].font_width; + h_default_foreground = info[display].fg; + h_default_background = info[display].bg; + + if (subdisplay != -1) { + h_screen_width = subinfo[subdisplay].width; + h_screen_height = subinfo[subdisplay].height; + } + + if (user_screen_width != -1) + h_screen_width = user_screen_width; + if (user_screen_height != -1) + h_screen_height = user_screen_height; + + h_screen_cols = h_screen_width / h_font_width; + h_screen_rows = h_screen_height / h_font_height; + + if (user_foreground != -1) + h_default_foreground = zcolour[user_foreground]; + if (user_background != -1) + h_default_background = zcolour[user_background]; + + /* Set the interpreter number (a constant telling the game which + operating system it runs on) and the interpreter version. The + interpreter number has effect on V6 games and "Beyond Zork". */ + + h_interpreter_number = INTERP_MSDOS; + h_interpreter_version = 'F'; + + if (display == _AMIGA_) + h_interpreter_number = INTERP_AMIGA; + + /* Install the fast_exit routine to handle the ctrl-break key */ + + oldvect = getvect (0x1b); setvect (0x1b, fast_exit); + +}/* os_init_screen */ + +/* + * os_reset_screen + * + * Reset the screen before the program stops. + * + */ + +void os_reset_screen (void) +{ + + os_set_font (TEXT_FONT); + os_set_text_style (0); + os_display_string ((zchar *) "[Hit any key to exit.]"); + os_read_key (0, TRUE); + + cleanup (); + +}/* os_reset_screen */ + +/* + * os_restart_game + * + * This routine allows the interface to interfere with the process of + * restarting a game at various stages: + * + * RESTART_BEGIN - restart has just begun + * RESTART_WPROP_SET - window properties have been initialised + * RESTART_END - restart is complete + * + */ + +void os_restart_game (int stage) +{ + int x, y; + + if (story_id == BEYOND_ZORK) + + if (stage == RESTART_BEGIN) + + if ((display == _MCGA_ || display == _AMIGA_) && os_picture_data (1, &x, &y)) { + + special_palette (); + + asm mov ax,0x1010 + asm mov bx,64 + asm mov dh,0 + asm mov ch,0 + asm mov cl,0 + asm int 0x10 + asm mov ax,0x1010 + asm mov bx,79 + asm mov dh,0xff + asm mov ch,0xff + asm mov cl,0xff + asm int 0x10 + + os_draw_picture (1, 1, 1); + os_read_key (0, FALSE); + + standard_palette (); + + } + +}/* os_restart_game */ + +/* + * os_random_seed + * + * Return an appropriate random seed value in the range from 0 to + * 32767, possibly by using the current system time. + * + */ + +int os_random_seed (void) +{ + + if (user_random_seed == -1) { + + /* Use the time of day as seed value */ + + asm mov ah,0 + asm int 0x1a + + return _DX & 0x7fff; + + } else return user_random_seed; + +}/* os_random_seed */ diff --git a/bcinput.c b/bcinput.c new file mode 100644 index 0000000..04c6e0f --- /dev/null +++ b/bcinput.c @@ -0,0 +1,989 @@ +/* + * file "BCinput.c" + * + * Borland C front end, input functions + * + */ + +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#ifndef HISTORY_BUFSIZE +#define HISTORY_BUFSIZE 500 +#endif + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); +extern void read_string (int, zchar *); + +extern int completion (const zchar *, zchar *); + +static long limit = 0; + +static struct { + zchar buffer[HISTORY_BUFSIZE]; + int latest; + int current; + int prefix_len; +} history; + +static struct { + zchar *buffer; + int pos; + int length; + int max_length; + int width; + int max_width; +} input; + +static bool overwrite = FALSE; + +/* + * swap_colours + * + * This is a small helper function for switch_cursor. It swaps the + * current background and foreground colours. + * + */ + +static void swap_colours (void) +{ + + _AL = text_fg; + _AH = text_bg; + text_fg = _AH; + text_bg = _AL; + +}/* swap_colours */ + +/* + * switch_cursor + * + * Turn cursor on/off. If there is mouse support then turn the mouse + * pointer on/off as well. The cursor should not be moved and the + * contents of the screen should not be changed while the cursor is + * visible (because of the primitive cursor emulation we use here). + * + */ + +static void switch_cursor (bool cursor) +{ + + if (display <= _TEXT_) { + + /* Use hardware cursor in text mode */ + + if (display == _MONO_) + _CX = overwrite ? 0x080f : 0x0a0b; + else + _CX = overwrite ? 0x0408 : 0x0506; + + if (!cursor) + _CX = 0xffff; + + asm mov ah,2 + asm mov bh,0 + asm mov dh,byte ptr cursor_y + asm mov dl,byte ptr cursor_x + asm int 0x10 + asm mov ah,1 + asm int 0x10 + + } else { + + int saved_x = cursor_x; + + if (cursor) + swap_colours (); + + if (input.pos < input.length) + os_display_char (input.buffer[input.pos]); + else + os_display_char (' '); + + if (cursor) + swap_colours (); + + cursor_x = saved_x; + + } + +}/* switch_cursor */ + +/* + * get_current_time + * + * Return the current system time in 1/10 seconds. + * + */ + +static long get_current_time (void) +{ + long time; + + /* Get the current time of day measured in + 65536 / 1,193,180 = 0.054925493 + seconds. Multiply this value with + 959 / 1746 = 0.54925544 + to get the current time in 0.1 seconds. */ + + asm mov ah,0 + asm int 0x1a + asm mov word ptr time,dx + asm mov word ptr time + 2,cx + + return time * 959 / 1746; + +}/* get_current_time */ + +/* + * set_timer + * + * Set a time limit of timeout/10 seconds if timeout is not zero; + * otherwise clear the time limit. + * + */ + +static void set_timer (int timeout) +{ + + limit = (timeout != 0) ? get_current_time () + timeout : 0; + +}/* set_timer */ + +/* + * time_limit_hit + * + * Return true if a previously set time limit has been exceeded. + * + */ + +static bool out_of_time (void) +{ + + if (limit != 0) { + + long now = get_current_time (); + + if (now < 1L * 3600 * 10 && limit > 23L * 3600 * 10) + now += 24L * 3600 * 10; + + return now >= limit; + + } else return FALSE; + +}/* out_of_time */ + +/* + * get_key + * + * Read a keypress or a mouse click. Returns... + * + * ZC_TIME_OUT = time limit exceeded, + * ZC_BACKSPACE = the backspace key, + * ZC_RETURN = the return key, + * ZC_HKEY_MIN...ZC_HKEY_MAX = a hot key, + * ZC_ESCAPE = the escape key, + * ZC_ASCII_MIN...ZC_ASCII_MAX = ASCII character, + * ZC_ARROW_MIN...ZC_ARROW_MAX = an arrow key, + * ZC_FKEY_MIN...ZC_FKEY_MAX = a function key, + * ZC_NUMPAD_MIN...ZC_NUMPAD_MAX = a number pad key, + * ZC_SINGLE_CLICK = single mouse click, + * ZC_DOUBLE_CLICK = double mouse click, + * ZC_LATIN1_MIN+1...ZC_LATIN1_MAX = ISO Latin-1 character, + * SPECIAL_KEY_MIN...SPECIAL_KEY_MAX = a special editing key. + * + */ + +static int get_key (bool cursor) +{ + static byte arrow_key_map[] = { + 0x48, 0x50, 0x4b, 0x4d + }; + static byte special_key_map[] = { + 0x47, 0x4f, 0x73, 0x74, 0x53, 0x52, 0x49, 0x51, 0x0f + }; + static byte hot_key_map[] = { + 0x13, 0x19, 0x1f, 0x16, 0x31, 0x2d, 0x20, 0x23 + }; + + int key; + + /* Loop until a key was pressed */ + + if (cursor) + switch_cursor (TRUE); + + if (h_flags & MOUSE_FLAG) { + asm mov ax,1 + asm int 0x33 + } + + do { + + if (_bios_keybrd (_KEYBRD_READY)) { + + word code = _bios_keybrd (_KEYBRD_READ); + + if (byte0 (code) != 0 && byte0 (code) != 9) { + + for (key = ZC_NUMPAD_MIN; key <= ZC_NUMPAD_MAX; key++) + if (byte0 (code) == key - ZC_NUMPAD_MIN + '0' && byte1 (code) >= 0x10) + goto exit_loop; + + for (key = ZC_LATIN1_MIN + 1; key <= ZC_LATIN1_MAX; key++) + if (byte0 (code) == latin1_to_ibm[key - ZC_LATIN1_MIN]) + goto exit_loop; + + key = byte0 (code); + + if (key == ZC_BACKSPACE) + goto exit_loop; + if (key == ZC_RETURN) + goto exit_loop; + if (key == ZC_ESCAPE) + goto exit_loop; + if (key >= ZC_ASCII_MIN && key <= ZC_ASCII_MAX) + goto exit_loop; + + } else { + + for (key = ZC_ARROW_MIN; key <= ZC_ARROW_MAX; key++) + if (byte1 (code) == arrow_key_map[key - ZC_ARROW_MIN]) + goto exit_loop; + + for (key = ZC_FKEY_MIN; key <= ZC_FKEY_MAX - 2; key++) + if (byte1 (code) == key - ZC_FKEY_MIN + 0x3b) + goto exit_loop; + + for (key = ZC_HKEY_MIN; key <= ZC_HKEY_MAX; key++) + if (byte1 (code) == hot_key_map[key - ZC_HKEY_MIN]) + goto exit_loop; + + for (key = SPECIAL_KEY_MIN; key <= SPECIAL_KEY_MAX; key++) + if (byte1 (code) == special_key_map[key - SPECIAL_KEY_MIN]) + goto exit_loop; + + } + + } else { + + int clicks = read_mouse (); + + if (clicks == 1) + { key = ZC_SINGLE_CLICK; goto exit_loop; } + if (clicks == 2) + { key = ZC_DOUBLE_CLICK; goto exit_loop; } + + } + + key = ZC_TIME_OUT; + + } while (!out_of_time ()); + +exit_loop: + + if (h_flags & MOUSE_FLAG) { + asm mov ax,2 + asm int 0x33 + } + + if (cursor) + switch_cursor (FALSE); + + return key; + +}/* get_key */ + +/* + * cursor_left + * + * Move the cursor one character to the left. + * + */ + +static void cursor_left (void) +{ + + if (input.pos > 0) + cursor_x -= os_char_width (input.buffer[--input.pos]); + +}/* cursor_left */ + +/* + * cursor_right + * + * Move the cursor one character to the right. + * + */ + +static void cursor_right (void) +{ + + if (input.pos < input.length) + cursor_x += os_char_width (input.buffer[input.pos++]); + +}/* cursor_right */ + +/* + * first_char + * + * Move the cursor to the beginning of the input line. + * + */ + +static void first_char (void) +{ + + while (input.pos > 0) + cursor_left (); + +}/* first_char */ + +/* + * last_char + * + * Move the cursor to the end of the input line. + * + */ + +static void last_char (void) +{ + + while (input.pos < input.length) + cursor_right (); + +}/* last_char */ + +/* + * prev_word + * + * Move the cursor to the start of the previous word. + * + */ + +static void prev_word (void) +{ + + do { + + cursor_left (); + + if (input.pos == 0) + return; + + } while (input.buffer[input.pos] == ' ' || input.buffer[input.pos - 1] != ' '); + +}/* prev_word */ + +/* + * next_word + * + * Move the cursor to the start of the next word. + * + */ + +static void next_word (void) +{ + + do { + + cursor_right (); + + if (input.pos == input.length) + return; + + } while (input.buffer[input.pos] == ' ' || input.buffer[input.pos - 1] != ' '); + +}/* next_word */ + +/* + * input_move + * + * Helper function to move parts of the input buffer: + * + * newc != 0, oldc == 0: INSERT + * newc != 0, oldc != 0: OVERWRITE + * newc == 0, oldc != 0: DELETE + * newc == 0, oldc == 0: NO OPERATION + * + */ + +#define H(x) (x ? 1 : 0) + +static void input_move (zchar newc, zchar oldc) +{ + int newwidth = (newc != 0) ? os_char_width (newc) : 0; + int oldwidth = (oldc != 0) ? os_char_width (oldc) : 0; + + zchar *p = input.buffer + input.pos; + + int saved_x = cursor_x; + + int updated_width = input.width + newwidth - oldwidth; + int updated_length = input.length + H (newc) - H (oldc); + + if (updated_width > input.max_width) + return; + if (updated_length > input.max_length) + return; + + input.width = updated_width; + input.length = updated_length; + + if (oldc != 0 && newc == 0) + memmove (p, p + 1, updated_length - input.pos + 1); + if (newc != 0 && oldc == 0) + memmove (p + 1, p, updated_length - input.pos); + + if (newc != 0) + *p = newc; + + os_display_string (p); + + switch_scrn_attr (TRUE); + + if (oldwidth > newwidth) + + os_erase_area ( + cursor_y + 1, + cursor_x + 1, + cursor_y + h_font_height, + cursor_x + oldwidth - newwidth); + + switch_scrn_attr (FALSE); + + cursor_x = saved_x; + + if (newc != 0) + cursor_right (); + +}/* input_move */ + +#undef H(x) + +/* + * delete_char + * + * Delete the character below the cursor. + * + */ + +static void delete_char (void) +{ + + input_move (0, input.buffer[input.pos]); + +}/* delete_char */ + +/* + * delete_left + * + * Delete the character to the left of the cursor. + * + */ + +static void delete_left (void) +{ + + if (input.pos > 0) { + cursor_left (); + delete_char (); + } + +}/* delete_left */ + +/* + * truncate_line + * + * Truncate the input line to n characters. + * + */ + +static void truncate_line (int n) +{ + + last_char (); + + while (input.length > n) + delete_left (); + +}/* truncate_line */ + +/* + * insert_char + * + * Insert a character into the input buffer. + * + */ + +static void insert_char (zchar newc) +{ + zchar oldc = 0; + + if (overwrite) + oldc = input.buffer[input.pos]; + + input_move (newc, oldc); + +}/* insert_char */ + +/* + * insert_string + * + * Add a string of characters to the input line. + * + */ + +static void insert_string (const zchar *s) +{ + + while (*s != 0) { + + if (input.length + 1 > input.max_length) + break; + if (input.width + os_char_width (*s) > input.max_width) + break; + + insert_char (*s++); + + } + +}/* insert_string */ + +/* + * tabulator_key + * + * Complete the word at the end of the input line, if possible. + * + */ + +static void tabulator_key (void) +{ + int status; + + if (input.pos == input.length) { + + zchar extension[10]; + + status = completion (input.buffer, extension); + insert_string (extension); + + } else status = 2; + + /* Beep if the completion was impossible or ambiguous */ + + if (status != 0) + os_beep (status); + +}/* tabulator_key */ + +/* + * store_input + * + * Copy the current input line to the history buffer. + * + */ + +static void store_input (void) +{ + + if (input.length >= HISTORY_MIN_ENTRY) { + + const zchar *ptr = input.buffer; + + do { + + if (history.latest++ == HISTORY_BUFSIZE - 1) + history.latest = 0; + + history.buffer[history.latest] = *ptr; + + } while (*ptr++ != 0); + + } + +}/* store_input */ + +/* + * fetch_entry + * + * Copy the current history entry to the input buffer and check if it + * matches the prefix in the input buffer. + * + */ + +static bool fetch_entry (zchar *buf, int entry) +{ + int i = 0; + + zchar c; + + do { + + if (entry++ == HISTORY_BUFSIZE - 1) + entry = 0; + + c = history.buffer[entry]; + + if (i < history.prefix_len && input.buffer[i] != c) + return FALSE; + + buf[i++] = c; + + } while (c != 0); + + return (i > history.prefix_len) && (i > 1); + +}/* fetch_entry */ + +/* + * get_prev_entry + * + * Copy the previous history entry to the input buffer. + * + */ + +static void get_prev_entry (void) +{ + zchar buf[INPUT_BUFFER_SIZE]; + + int i = history.current; + + do { + + do { + + if (i-- == 0) + i = HISTORY_BUFSIZE - 1; + + if (i == history.latest) + return; + + } while (history.buffer[i] != 0); + + } while (!fetch_entry (buf, i)); + + truncate_line (history.prefix_len); + + insert_string (buf + history.prefix_len); + + history.current = i; + +}/* get_prev_entry */ + +/* + * get_next_entry + * + * Copy the next history entry to the input buffer. + * + */ + +static void get_next_entry (void) +{ + zchar buf[INPUT_BUFFER_SIZE]; + + int i = history.current; + + truncate_line (history.prefix_len); + + do { + + do { + + if (i == history.latest) + return; + + if (i++ == HISTORY_BUFSIZE - 1) + i = 0; + + } while (history.buffer[i] != 0); + + if (i == history.latest) + goto no_further; + + } while (!fetch_entry (buf, i)); + + insert_string (buf + history.prefix_len); + +no_further: + + history.current = i; + +}/* get_next_entry */ + +/* + * os_read_line + * + * Read a line of input from the keyboard into a buffer. The buffer + * may already be primed with some text. In this case, the "initial" + * text is already displayed on the screen. After the input action + * is complete, the function returns with the terminating key value. + * The length of the input should not exceed "max" characters plus + * an extra 0 terminator. + * + * Terminating keys are the return key (13) and all function keys + * (see the Specification of the Z-machine) which are accepted by + * the is_terminator function. Mouse clicks behave like function + * keys except that the mouse position is stored in global variables + * "mouse_x" and "mouse_y" (top left coordinates are (1,1)). + * + * Furthermore, Frotz introduces some special terminating keys: + * + * ZC_HKEY_PLAYBACK (Alt-P) + * ZC_HKEY_RECORD (Alt-R) + * ZC_HKEY_SEED (Alt-S) + * ZC_HKEY_UNDO (Alt-U) + * ZC_HKEY_RESTART (Alt-N, "new game") + * ZC_HKEY_QUIT (Alt-X, "exit game") + * ZC_HKEY_DEBUGGING (Alt-D) + * ZC_HKEY_HELP (Alt-H) + * + * If the timeout argument is not zero, the input gets interrupted + * after timeout/10 seconds (and the return value is 0). + * + * The complete input line including the cursor must fit in "width" + * screen units. + * + * The function may be called once again to continue after timeouts, + * misplaced mouse clicks or hot keys. In this case the "continued" + * flag will be set. This information can be useful if the interface + * implements input line history. + * + * The screen is not scrolled after the return key was pressed. The + * cursor is at the end of the input line when the function returns. + * + * Since Inform 2.2 the helper function "completion" can be called + * to implement word completion (similar to tcsh under Unix). + * + */ + +#define new_history_search() \ + { history.prefix_len = input.pos; history.current = history.latest; } + +zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued) +{ + int key = continued ? 9999 : 0; + + /* Initialise input variables */ + + input.buffer = buf; + input.pos = strlen ((char *) buf); + input.length = strlen ((char *) buf); + input.max_length = max; + input.width = os_string_width (buf); + input.max_width = width - os_char_width (' '); + + /* Calculate time limit */ + + set_timer (timeout); + + /* Loop until a terminator is found */ + + do { + + if (key != 9999) + new_history_search (); + + /* Get next key from mouse or keyboard */ + + key = get_key (TRUE); + + if (key < ZC_ASCII_MIN || key > ZC_ASCII_MAX && key < ZC_LATIN1_MIN || key > ZC_LATIN1_MAX) { + + /* Ignore time-outs if the cursor is not at end of the line */ + + if (key == ZC_TIME_OUT && input.pos < input.length) + key = 9999; + + /* Backspace, return and escape keys */ + + if (key == ZC_BACKSPACE) + delete_left (); + if (key == ZC_RETURN) + store_input (); + if (key == ZC_ESCAPE) + truncate_line (0); + + /* Editing keys */ + + if (cwin == 0) { + + if (key == ZC_ARROW_UP) + get_prev_entry (); + if (key == ZC_ARROW_DOWN) + get_next_entry (); + if (key == ZC_ARROW_LEFT) + cursor_left (); + if (key == ZC_ARROW_RIGHT) + cursor_right (); + + if (key >= ZC_ARROW_MIN && key <= ZC_ARROW_MAX) + key = 9999; + + if (key == SPECIAL_KEY_HOME) + first_char (); + if (key == SPECIAL_KEY_END) + last_char (); + if (key == SPECIAL_KEY_WORD_LEFT) + prev_word (); + if (key == SPECIAL_KEY_WORD_RIGHT) + next_word (); + if (key == SPECIAL_KEY_DELETE) + delete_char (); + if (key == SPECIAL_KEY_INSERT) + overwrite = !overwrite; + if (key == SPECIAL_KEY_TAB) + tabulator_key (); + + } + + if (key == SPECIAL_KEY_PAGE_UP) + key = ZC_ARROW_UP; + if (key == SPECIAL_KEY_PAGE_DOWN) + key = ZC_ARROW_DOWN; + + } else insert_char (key); + + } while (key > 0xff || !is_terminator (key)); + + last_char (); + + overwrite = FALSE; + + /* Return terminating key */ + + return key; + +}/* os_read_line */ + +#undef new_history_search() + +/* + * os_read_key + * + * Read a single character from the keyboard (or a mouse click) and + * return it. Input aborts after timeout/10 seconds. + * + */ + +zchar os_read_key (int timeout, bool cursor) +{ + int key; + + set_timer (timeout); + + do { + + key = get_key (cursor); + + } while (key > 0xff); + + return key; + +}/* os_read_key */ + +/* + * os_read_file_name + * + * Return the name of a file. Flag can be one of: + * + * FILE_SAVE - Save game file + * FILE_RESTORE - Restore game file + * FILE_SCRIPT - Transscript file + * FILE_RECORD - Command file for recording + * FILE_PLAYBACK - Command file for playback + * FILE_SAVE_AUX - Save auxilary ("preferred settings") file + * FILE_LOAD_AUX - Load auxilary ("preferred settings") file + * + * The length of the file name is limited by MAX_FILE_NAME. Ideally + * an interpreter should open a file requester to ask for the file + * name. If it is unable to do that then this function should call + * print_string and read_string to ask for a file name. + * + */ + +int os_read_file_name (char *file_name, const char *default_name, int flag) +{ + char *extension; + FILE *fp; + bool terminal; + bool result; + + bool saved_replay = istream_replay; + bool saved_record = ostream_record; + + /* Turn off playback and recording temporarily */ + + istream_replay = FALSE; + ostream_record = FALSE; + + /* Select appropriate extension */ + + extension = ".aux"; + + if (flag == FILE_SAVE || flag == FILE_RESTORE) + extension = ".sav"; + if (flag == FILE_SCRIPT) + extension = ".scr"; + if (flag == FILE_RECORD || flag == FILE_PLAYBACK) + extension = ".rec"; + + /* Input file name (reserve four bytes for a file name extension) */ + + print_string ("Enter file name (\""); + print_string (extension); + print_string ("\" will be added).\nDefault is \""); + print_string (default_name); + print_string ("\": "); + + read_string (MAX_FILE_NAME - 4, (zchar *) file_name); + + /* Use the default name if nothing was typed */ + + if (file_name[0] == 0) + strcpy (file_name, default_name); + if (strchr (file_name, '.') == NULL) + strcat (file_name, extension); + + /* Make sure it is safe to use this file name */ + + result = TRUE; + + /* OK if the file is opened for reading */ + + if (flag != FILE_SAVE && flag != FILE_SAVE_AUX && flag != FILE_RECORD) + goto finished; + + /* OK if the file does not exist */ + + if ((fp = fopen (file_name, "rb")) == NULL) + goto finished; + + /* OK if this is a pseudo-file (like PRN, CON, NUL) */ + + terminal = fp->flags & _F_TERM; + + fclose (fp); + + if (terminal) + goto finished; + + /* OK if user wants to overwrite */ + + result = read_yes_or_no ("Overwrite existing file"); + +finished: + + /* Restore state of playback and recording */ + + istream_replay = saved_replay; + ostream_record = saved_record; + + return result; + +}/* os_read_file_name */ diff --git a/bcmouse.c b/bcmouse.c new file mode 100644 index 0000000..ea8444b --- /dev/null +++ b/bcmouse.c @@ -0,0 +1,75 @@ +/* + * file "BCmouse.c" + * + * Borland C front end, mouse support + * + */ + +#include +#include "frotz.h" +#include "BCfrotz.h" + +/* + * detect_mouse + * + * Return true if a mouse driver is present. + * + */ + +bool detect_mouse (void) +{ + + asm xor ax,ax + asm int 0x33 + + return _AX; + +}/* detect_mouse */ + +/* + * read_mouse + * + * Report any mouse clicks. Return 2 for a double click, 1 for a single + * click or 0 if there was no mouse activity at all. + * + */ + +int read_mouse (void) +{ + int click; + + /* Read the current mouse status */ + + for (click = 0; click < 2; click++) { + + if (click == 1) + delay (222); + + asm mov ax,6 + asm xor bx,bx + asm int 0x33 + + if (_BX == 0) + break; + + mouse_x = _CX; + mouse_y = _DX; + + if (display <= _TEXT_) { + mouse_x /= 8; + mouse_y /= 8; + } + + if (display == _MCGA_) + mouse_x /= 2; + + mouse_x++; + mouse_y++; + + } + + /* Return single or double click */ + + return click; + +}/* read_mouse */ diff --git a/bcpic.c b/bcpic.c new file mode 100644 index 0000000..59115a9 --- /dev/null +++ b/bcpic.c @@ -0,0 +1,677 @@ +/* + * "BCpic.c" + * + * Borland C front end, picture functions + * + */ + +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define PIC_NUMBER 0 +#define PIC_WIDTH 2 +#define PIC_HEIGHT 4 +#define PIC_FLAGS 6 +#define PIC_DATA 8 +#define PIC_COLOUR 11 + +#define READ_BYTE(v,p,o) v = *(byte far *)(p+o) +#define READ_WORD(v,p,o) v = *(word far *)(p+o) + +extern byte far *get_scrnptr (int); + +static struct { + byte fileno; + byte flags; + word unused1; + word images; + word link; + byte entry_size; + byte padding; + word checksum; + word unused2; + word version; +} gheader; + +int scaler = 1; + +static word pic_width = 0; +static word pic_height = 0; +static word pic_flags = 0; +static long pic_data = 0; +static long pic_colour = 0; + +static byte far *table_val = NULL; +static word far *table_ref = NULL; + +static FILE *file = NULL; +static byte far *info = NULL; + +/* + * open_graphics_file + * + * Open a graphics file. EGA pictures may be stored in two separate + * graphics files. + * + */ + +static bool open_graphics_file (int number) +{ + char fname[MAX_FILE_NAME + 1]; + char extension[4 + 1]; + + /* Build graphics file name */ + + extension[0] = '.'; + extension[1] = "cmem"[display - 2]; + extension[2] = 'g'; + extension[3] = '0' + number; + extension[4] = 0; + + strcpy (fname, stripped_story_name); + strcat (fname, extension); + + /* Open file, load header, allocate memory, load picture directory */ + + if ((file = fopen (fname, "rb")) == NULL) + goto failure1; + if (fread (&gheader, sizeof (gheader), 1, file) != 1) + goto failure2; + if ((info = farmalloc (gheader.images * gheader.entry_size)) == NULL) + goto failure2; + if (fread (info, gheader.entry_size, gheader.images, file) != gheader.images) + goto failure3; + return TRUE; + +failure3: + farfree (info); info = NULL; +failure2: + fclose (file); file = NULL; +failure1: + return FALSE; + +}/* open_graphics_file */ + +/* + * close_graphics_file + * + * Free resources allocated for pictures. + * + */ + +static void close_graphics_file (void) +{ + + if (file != NULL) + { fclose (file); file = NULL; } + if (info != NULL) + { farfree (info); info = NULL; } + +}/* close_graphics_file */ + +/* + * init_pictures + * + * Prepare to draw pictures. Return true if pictures are available. + * + */ + +bool init_pictures (void) +{ + + /* Allocate memory for decompression */ + + table_val = (byte far *) farmalloc (3 * 3840); + table_ref = (word far *) (table_val + 3840); + + if (table_val == NULL) + return FALSE; + + /* Open the [first of two] graphics file[s] */ + + return open_graphics_file (1); + +}/* init_pictures */ + +/* + * reset_pictures + * + * Free resources allocated for decompression of pictures. + * + */ + +void reset_pictures (void) +{ + + if (table_val != NULL) + { farfree (table_val); table_val = NULL; } + if (file != NULL) + { fclose (file); file = NULL; } + if (info != NULL) + { farfree (info); info = NULL; } + +}/* reset_pictures */ + +/* + * load_picture_info + * + * Helper function for os_picture_data. Load all information about + * the given picture from the graphics file and store it in global + * variables. + * + */ + +static bool load_picture_info (int picture) +{ + byte far *ptr; + byte fileno; + + fileno = gheader.fileno; + + do { + + int i; + + /* Abort if there is a problem with the graphics file */ + + if (file == NULL) + return FALSE; + + /* Scan the directory of the current graphics file */ + + ptr = info; + + for (i = 0; i < gheader.images; i++) { + + if (picture == * (int far *) ptr) { + + READ_WORD (pic_width, ptr, PIC_WIDTH); + READ_WORD (pic_height, ptr, PIC_HEIGHT); + READ_WORD (pic_flags, ptr, PIC_FLAGS); + + pic_height *= scaler; + pic_width *= scaler; + + READ_BYTE (byte0 (pic_data), ptr, PIC_DATA + 2); + READ_BYTE (byte1 (pic_data), ptr, PIC_DATA + 1); + READ_BYTE (byte2 (pic_data), ptr, PIC_DATA); + + if (gheader.entry_size > PIC_COLOUR + 2) { + + READ_BYTE (byte0 (pic_colour), ptr, PIC_COLOUR + 2); + READ_BYTE (byte1 (pic_colour), ptr, PIC_COLOUR + 1); + READ_BYTE (byte2 (pic_colour), ptr, PIC_COLOUR); + + } else pic_colour = 0; + + return TRUE; + + } + + ptr += gheader.entry_size; + + } + + /* Close current graphics file */ + + close_graphics_file (); + + /* Open next graphics file */ + + open_graphics_file ((gheader.link != 0) ? gheader.fileno + 1 : 1); + + } while (fileno != gheader.fileno); + + return FALSE; + +}/* load_picture_info */ + +/* + * load_colour_map + * + * Helper function for os_draw_picture. Load a colour map from the + * graphics file then copy it to the palette registers. + * + */ + +static void load_colour_map (int first_colour) +{ + byte rgb[42]; + int n, i; + + fseek (file, pic_colour, SEEK_SET); + + /* Some pictures from Arthur mistakenly claim to have 16 colours */ + + if ((n = fgetc (file)) == 16) + n = 14; + + /* Each colour is stored in three bytes R-G-B */ + + fread (rgb, 3, n, file); + + /* MCGA boards can only handle R-G-B values from 0 to 63 */ + + for (i = 0; i < 42; i++) + rgb[i] = (rgb[i] * 63 + 128) / 255; + + /* Synchronise with vertical retrace */ + + while ((inportb (0x03da) & 8) == 0); + while ((inportb (0x03da) & 8) == 8); + + /* Copy colours to palette registers */ + + asm mov ax,0x1012 + asm mov bx,first_colour + asm mov cx,n + asm lea dx,rgb + asm push ss + asm pop es + asm int 0x10 + +}/* load_colour_map */ + +/* + * draw_picture + * + * Helper function for os_draw_picture. The real work of drawing a + * picture happens here. + * + */ + +#pragma warn -def + +static void pascal draw_picture (int y, int x) +{ + static int raise_bits[4] = { + 0x0100, 0x0300, 0x0700, 0x0000 + }; + + byte buf[512]; + byte far *screen; + byte transparent; + byte colour_shift; + int first_colour; + int code, prev_code; + int next_entry; + int bits_per_code; + int bits_shift; + int bits; + int current_y; + int current_x; + int bufpos; + int pixels; + int i; + + bufpos = 0; + + /* When the given picture provides a colour map then activate it. + This is only used for MCGA pictures; the colour map affects + every picture on the screen. The first colour to be defined is + colour 2. Every map defines up to 14 colours (colour 2 to 15). + These colours are not related to the standard Z-machine colour + scheme which remains unchanged. (This is based on the Amiga + interpreter which had to work with 16 colours. Colours 0 and 1 + were used for text; changing the text colours actually changed + palette entries 0 and 1. This interface uses the same trick in + Amiga mode.) */ + + if (display == _CGA_) + colour_shift = -2; + if (display == _EGA_) + colour_shift = 0; + if (display == _MCGA_) + { colour_shift = 32; first_colour = 34; } + if (display == _AMIGA_) + { colour_shift = -1; first_colour = 65; } + + if (pic_colour != 0) + load_colour_map (first_colour); + + fseek (file, pic_data, SEEK_SET); + + /* Bit 0 of "flags" indicates that the picture uses a transparent + colour, the top four bits tell us which colour it is. For CGA + and MCGA pictures this is always 0; for EGA pictures it can be + any colour between 0 and 15. */ + + transparent = 0xff; + + if (pic_flags & 1) + transparent = pic_flags >> 12; + + /* Prepare EGA hardware for setting pixels */ + + if (display >= _EGA_) { + outport (0x03ce, 0x0205); + outport (0x03ce, 0xff08); + } + + /* The uncompressed picture is a long sequence of bytes. Every + byte holds the colour of a pixel, starting at the top left, + stopping at the bottom right. We keep track of our position + in the current line. (There is a special case: CGA pictures + with no transparent colour are stored as bit patterns, i.e. + every byte holds the pattern for eight pixels. A pixel must + be white if the corresponding bit is set, otherwise it must + be black.) */ + + current_x = x + pic_width; + current_y = y - 1; + + /* The compressed picture is a stream of bits. We read the file + byte-wise, storing the current byte in the variable "bits". + Several bits make one code; the variable "bits_shift" helps + us to build the next code. */ + + bits_shift = 0; + bits = 0; + +reset_table: + + /* Clear the table. We use a table of 3840 entries. Each entry + consists of both a value and a reference to another table + entry. Following these references we get a sequence of + values. At the start of decompression all table entries are + undefined. Later we see how entries are set and used. */ + + next_entry = 1; + + /* At the start of decompression 9 bits make one code; during + the process this can rise to 12 bits per code. 9 bits are + sufficient to address both 256 literal values and 256 table + entries; 12 bits are sufficient to address both 256 literal + values and all 3840 table entries. The number of bits per + code rises with the number of table entries. When the table + is cleared, the number of bits per code drops back to 9. */ + + bits_per_code = 9; + +next_code: + + /* Read the next code from the graphics file. This requires + some confusing bit operations. Note that low bits always + come first. Usually there are a few bits left over from + the previous code; these bits must be used before further + bits are read from the graphics file. */ + + code = bits >> (8 - bits_shift); + + do { + + bits = fgetc (file); + + code |= bits << bits_shift; + + bits_shift += 8; + + } while (bits_shift < bits_per_code); + + bits_shift -= bits_per_code; + + code &= 0xfff >> (12 - bits_per_code); + + /* There are two codes with a special meaning. The first one + is 256 which clears the table and sets the number of bits + per code to 9. (This is necessary when the table is full.) + The second one is 257 which marks the end of the picture. + For the sake of efficiency, we drecement the code by 256. */ + + byte1 (code) --; + + if (code == 0) + goto reset_table; + if (code == 1) + return; + + /* Codes from 0 to 255 are literals, i.e. they represent a + plain byte value. Codes from 258 onwards are references + to table entries, i.e. they represent a sequence of byte + values (see the remarks on the table above). This means + that for each code one or several byte values are added + to the decompressed picture. But there is yet more work + to do: Every time we read a code one table entry is set. + As we said above, a table entry consist of both a value + and a reference to another table entry. If the current + code is a literal, then the value has to be set to this + literal; but if the code refers to a sequence of byte + values, then the value has to be set to the last byte of + this sequence. In any case, the reference is set to the + previous code. Finally, one should be aware that a code + may legally refer to the table entry which is currently + being set. This requires some extra care. */ + + table_ref[next_entry] = prev_code; + + prev_code = code; + + while (code >= 0) { + buf[bufpos++] = table_val[code]; + code = (short) table_ref[code]; + } + + if (next_entry == prev_code) + buf[0] = code; + + table_val[next_entry] = code; + + /* The number of bits per code is incremented when the current + number of bits no longer suffices to address all defined + table entries; but in any case the number of bits may never + be greater than 12. */ + + next_entry++; + + if (next_entry == raise_bits[bits_per_code - 9]) + bits_per_code++; + +reverse_buffer: + + /* Append the sequence of byte values (pixels) to the picture. + The order of the sequence must be reversed. (This is why we + have stored the sequence in a buffer; experiments show that + a buffer of 512 bytes suffices.) The sequence of values may + spread over several lines of the picture, so we must take + care to start a new line when we reach the right border of + the picture. */ + + if (current_x == x + pic_width) { + + screen = get_scrnptr (current_y); + + current_x -= pic_width; + current_y += scaler; + + } + + /* Either add a single pixel or a pattern of eight bits (b/w + CGA pictures without a transparent colour) to the current + line. Increment our position by 1 or 8 respectively. The + pixel may have to be painted several times if the scaling + factor is greater than one. */ + + if (display == _CGA_ && transparent == 0xff) { + + pixels = x + pic_width - current_x; + + if (pixels > 8) + pixels = 8; + + asm les bx,screen + asm mov dx,current_x + asm dec dx + asm push dx + asm mov cl,3 + asm shr dx,cl + asm add bx,dx + asm mov ax,es:[bx] + asm mov dx,0xffff + asm mov cl,byte ptr pixels + asm shr dl,cl + asm pop cx + asm and cl,7 + asm ror dx,cl + asm and ax,dx + asm mov dx,code + asm inc dh + asm ror dx,cl + asm or ax,dx + asm mov es:[bx],ax + + current_x += pixels; + + } else for (i = 0; i < scaler; i++) { + + _AH = code; + + if (_AH != transparent) { + + asm add ah,colour_shift + asm les bx,screen + asm mov dx,current_x + asm dec dx + + if (display != _MCGA_) { + + asm push dx + asm mov cl,3 + asm shr dx,cl + asm pop cx + asm and cl,7 + asm add bx,dx + asm mov al,es:[bx] + + if (display == _CGA_) { + asm mov dl,0x7f + asm ror dl,cl + asm and al,dl + asm xor ah,1 + asm ror ah,1 + asm shr ah,cl + asm or ah,al + } else { + asm mov al,0x80 + asm shr al,cl + asm mov dx,0x03cf + asm out dx,al + } + + } else asm add bx,dx + + asm mov es:[bx],ah + + if (display == _AMIGA_) { + asm add bx,80 + asm mov al,es:[bx] + asm mov es:[bx],ah + } + + } + + current_x++; + + } + + /* If there are no more values in the buffer then read the + next code from the file. Otherwise fetch the next byte + value from the buffer and continue painting the picture. */ + + if (bufpos == 0) + goto next_code; + + byte0 (code) = buf[--bufpos]; + + goto reverse_buffer; + +}/* draw_picture */ + +#pragma warn +def + +/* + * os_draw_picture + * + * Display a picture at the given coordinates. Top left is (1,1). + * + */ + +void os_draw_picture (int picture, int y, int x) +{ + + if (load_picture_info (picture)) + draw_picture (y, x); + +}/* os_draw_picture */ + +/* + * os_peek_colour + * + * Return the colour of the pixel below the cursor. This is used + * by V6 games to print text on top of pictures. The coulor need + * not be in the standard set of Z-machine colours. To handle + * this situation, Frotz extends the colour scheme: Values above + * 15 (and below 256) may be used by the interface to refer to + * non-standard colours. Of course, os_set_colour must be able to + * deal with these colours. Interfaces which refer to characters + * instead of pixels might return the current background colour + * instead. + * + */ + +int os_peek_colour (void) +{ + + if (display >= _CGA_) { + + asm mov ah,13 + asm mov bh,0 + asm mov cx,cursor_x + asm mov dx,cursor_y + asm int 0x10 + asm mov ah,0 + + return _AX + 16; + + } else return current_bg; + +}/* os_peek_colour */ + +/* + * os_picture_data + * + * Return true if the given picture is available. If so, write the + * width and height of the picture into the appropriate variables. + * Only when picture 0 is asked for, write the number of available + * pictures and the release number instead. + * + */ + +bool os_picture_data (int picture, int *height, int *width) +{ + bool avail; + + if (picture == 0) { + + avail = FALSE; + + /* This is the special case mentioned above. In practice, only + the release number is used; and even this is only used by + the DOS version of "Zork Zero". Infocom's Amiga interpreter + could not handle this feature, and the Amiga version of the + story file does not use it. */ + + pic_height = gheader.images; + pic_width = gheader.version; + + } else avail = load_picture_info (picture); + + *height = pic_height; + *width = pic_width; + + return avail; + +}/* os_picture_data */ diff --git a/bcsample.c b/bcsample.c new file mode 100644 index 0000000..bb50b23 --- /dev/null +++ b/bcsample.c @@ -0,0 +1,414 @@ +/* + * file "BCsample.c" + * + * Borland C front end, sound support + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define SWAP_BYTES(v) {_AX=v;asm xchg al,ah;v=_AX;} + +#define READ_DSP(v) {while(!inportb(sound_adr+14)&0x80);v=inportb(sound_adr+10);} +#define WRITE_DSP(v) {while(inportb(sound_adr+12)&0x80);outportb(sound_adr+12,v);} + +extern void end_of_sound (void); + +static struct { + word prefix; + byte repeats; + byte base_note; + word frequency; + word unused; + word length; +} sheader; + +static current_sample = 0; + +static void interrupt (*vect) (void) = NULL; + +static play_part = 0; +static play_count = 0; + +static word sound_adr = 0; +static word sound_irq = 0; +static word sound_dma = 0; +static word sound_int = 0; +static word sound_ver = 0; + +static byte far *sample_data = NULL; + +static long sample_adr1 = 0; +static long sample_adr2 = 0; +static word sample_len1 = 0; +static word sample_len2 = 0; + +/* + * start_of_dma + * + * Start the DMA transfer to the sound board. + * + */ + +static void start_of_dma (long address, unsigned length) +{ + static unsigned dma_page_port[] = { + 0x87, 0x83, 0x81, 0x82 + }; + + length--; + + /* Set up DMA chip */ + + outportb (0x0a, 0x04 | sound_dma); + outportb (0x0c, 0x00); + outportb (0x0b, 0x48 | sound_dma); + outportb (2 * sound_dma, byte0 (address)); + outportb (2 * sound_dma, byte1 (address)); + outportb (dma_page_port[sound_dma], byte2 (address)); + outportb (2 * sound_dma + 1, byte0 (length)); + outportb (2 * sound_dma + 1, byte1 (length)); + outportb (0x0a, sound_dma); + + /* Play 8-bit mono sample */ + + WRITE_DSP (0x14) + WRITE_DSP (byte0 (length)) + WRITE_DSP (byte1 (length)) + +}/* start_of_dma */ + +/* + * end_of_dma + * + * This function is called when a hardware interrupt signals the + * end of the current sound. We may have to play the second half + * of the sound effect, or we may have to repeat it, or call the + * end_of_sound function when we are finished. + * + */ + +static void interrupt end_of_dma (void) +{ + + /* Play the second half, play another cycle or finish */ + + if (play_part == 1 && sample_len2 != 0) { + play_part = 2; + start_of_dma (sample_adr2, sample_len2); + } else if (play_count == 255 || --play_count != 0) { + play_part = 1; + start_of_dma (sample_adr1, sample_len1); + } else { + play_part = 0; + end_of_sound (); + } + + /* Tell interrupt controller(s) + sound board we are done */ + + outportb (0x20, 0x20); + + if (sound_irq >= 8) + outportb (0xa0, 0x20); + + inportb (sound_adr + 14); + +}/* end_of_dma */ + +/* + * init_sound + * + * Initialise the sound board and various sound related variables. + * + */ + +bool init_sound (void) +{ + const char *settings = getenv ("BLASTER"); + word irc_mask_port; + + /* Read the IRQ, port address, DMA channel and SB version */ + + if ((settings = getenv ("BLASTER")) == NULL) + return FALSE; + + sound_irq = dectoi (strchr (settings, 'I') + 1); + sound_adr = hextoi (strchr (settings, 'A') + 1); + sound_dma = dectoi (strchr (settings, 'D') + 1); + sound_ver = dectoi (strchr (settings, 'T') + 1); + + /* Reset mixer chip and DSP */ + + outportb (sound_adr + 4, 0); + outportb (sound_adr + 5, 0); + + outportb (sound_adr + 6, 1); + inportb (sound_adr + 6); + inportb (sound_adr + 6); + inportb (sound_adr + 6); + outportb (sound_adr + 6, 0); + + /* Turn on speakers */ + + WRITE_DSP (0xd1) + + /* Install the end_of_dma interrupt */ + + if (sound_irq < 8) { + irc_mask_port = 0x21; + sound_int = 0x08 + sound_irq; + } else { + irc_mask_port = 0xa1; + sound_int = 0x68 + sound_irq; + } + + vect = getvect (sound_int); setvect (sound_int, end_of_dma); + + /* Allocate 64KB RAM for sample data */ + + if ((sample_data = (byte far *) farmalloc (0x10000L)) == NULL) + return FALSE; + + word0 (sample_adr1) = FP_OFF (sample_data) | (FP_SEG (sample_data) << 4); + word1 (sample_adr1) = FP_SEG (sample_data) >> 12; + word0 (sample_adr2) = 0; + word1 (sample_adr2) = word1 (sample_adr1) + 1; + + /* Enable the end_of_dma interrupt */ + + outportb (0x20, 0x20); + + if (sound_irq >= 8) + outportb (0xa0, 0x20); + + outportb (irc_mask_port, inportb (irc_mask_port) & ~(1 << (sound_irq & 7))); + + /* Indicate success */ + + return TRUE; + +}/* init_sound */ + +/* + * reset_sound + * + * Free resources allocated for playing samples. + * + */ + +void reset_sound (void) +{ + + os_stop_sample (); + + if (sample_data != NULL) + { farfree (sample_data); sample_data = NULL; } + if (sound_adr != 0) + { setvect (sound_int, vect); sound_adr = 0; } + +}/* reset_sound */ + +/* + * os_beep + * + * Play a beep sound. Ideally, the sound should be high- (number == 1) + * or low-pitched (number == 2). + * + */ + +void os_beep (int number) +{ + word T = 888 * number; + + outportb (0x43, 0xb6); + outportb (0x42, lo (T)); + outportb (0x42, hi (T)); + outportb (0x61, inportb (0x61) | 3); + + delay (75); + + outportb (0x61, inportb (0x61) & ~3); + +}/* os_beep */ + +/* + * os_prepare_sample + * + * Load the sample from the disk. + * + */ + +void os_prepare_sample (int number) +{ + + os_stop_sample (); + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Continue only if the desired sample is not already present */ + + if (current_sample != number) { + + char sample_name[MAX_FILE_NAME + 1]; + char numstr[2]; + FILE *fp; + + /* Build sample file name */ + + strcpy (sample_name, "sound\\"); + + numstr[0] = '0' + number / 10; + numstr[1] = '0' + number % 10; + + strncat (sample_name, stripped_story_name, 6); + strncat (sample_name, numstr, 2); + strncat (sample_name, ".snd", 4); + + /* Open sample file */ + + if ((fp = fopen (sample_name, "rb")) == NULL) + return; + + /* Load header and sample data */ + + fread (&sheader, sizeof (sheader), 1, fp); + + SWAP_BYTES (sheader.frequency) + SWAP_BYTES (sheader.length) + + fread (sample_data, 1, sheader.length, fp); + + sample_len1 = -word0 (sample_adr1); + + if (sample_len1 > sheader.length || sample_len1 == 0) + sample_len1 = sheader.length; + + sample_len2 = sheader.length - sample_len1; + + WRITE_DSP (0x40) + WRITE_DSP (256 - 1000000L / sheader.frequency) + + current_sample = number; + + /* Close sample file */ + + fclose (fp); + + } + +}/* os_prepare_sample */ + +/* + * os_start_sample + * + * Play the given sample at the given volume (ranging from 1 to 8 and + * 255 meaning a default volume). The sound is played once or several + * times in the background (255 meaning forever). The end_of_sound + * function is called as soon as the sound finishes. + * + */ + +void os_start_sample (int number, int volume, int repeats) +{ + + os_stop_sample (); + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Load new sample */ + + os_prepare_sample (number); + + /* Continue only if the sample's in memory now */ + + if (current_sample == number) { + + play_count = repeats; + + if (sound_ver < 6) { /* Set up SB pro mixer chip */ + + volume = (volume != 255) ? 7 + volume : 15; + + outportb (sound_adr + 4, 0x04); + outportb (sound_adr + 5, (volume << 4) | volume); + outportb (sound_adr + 4, 0x22); + outportb (sound_adr + 5, 0xff); + + } else { /* Set up SB16 mixer chip */ + + /* Many thanks to Linards Ticmanis for writing this part! */ + + volume = (volume != 255) ? 127 + 16 * volume : 255; + + outportb (sound_adr + 4, 0x32); + outportb (sound_adr + 5, volume); + outportb (sound_adr + 4, 0x33); + outportb (sound_adr + 5, volume); + outportb (sound_adr + 4, 0x30); + outportb (sound_adr + 5, 0xff); + outportb (sound_adr + 4, 0x31); + outportb (sound_adr + 5, 0xff); + + } + + play_part = 1; + start_of_dma (sample_adr1, sample_len1); + + } + +}/* os_start_sample */ + +/* + * os_stop_sample + * + * Turn off the current sample. + * + */ + +void os_stop_sample (void) +{ + + play_part = 0; + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Tell DSP to stop the current sample */ + + WRITE_DSP (0xd0) + +}/* os_stop_sample */ + +/* + * os_finish_with_sample + * + * Remove the current sample from memory (if any). + * + */ + +void os_finish_with_sample (void) +{ + + os_stop_sample (); /* we keep 64KB allocated all the time */ + +}/* os_finish_with_sample */ diff --git a/bcscreen.c b/bcscreen.c new file mode 100644 index 0000000..ed42e2e --- /dev/null +++ b/bcscreen.c @@ -0,0 +1,298 @@ +/* + * file "BCscreen.c" + * + * Borland C front end, screen manipulation + * + */ + +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +/* + * get_scrnptr + * + * Return a pointer to the given line in video RAM. + * + */ + +byte far *get_scrnptr (int y) +{ + + if (display == _CGA_) + return MK_FP ((y & 1) ? 0xba00 : 0xb800, 40 * (y & ~1)); + else if (display == _MCGA_) + return MK_FP (0xa000, 320 * y); + else + return MK_FP (0xa000, 80 * y); + +}/* get_scrnptr */ + +/* + * clear_byte + * + * Helper function for clear_line. + * + */ + +static void clear_byte (byte far *scrn, word mask) +{ + + if (display == _CGA_) + + if (scrn_attr == 0) + *scrn &= ~mask; + else + *scrn |= mask; + + else { + + outport (0x03ce, 0x0205); + + outportb (0x03ce, 0x08); + outportb (0x03cf, mask); + + asm les bx,scrn + asm mov al,es:[bx] + asm mov al,scrn_attr + asm mov es:[bx],al + + } + +}/* clear_byte */ + +/* + * clear_line + * + * Helper function for os_erase_area. + * + */ + +static void clear_line (int y, int left, int right) +{ + byte far *scrn = get_scrnptr (y); + + if (display == _MCGA_) + + _fmemset (scrn + left, scrn_attr, right - left + 1); + + else { + + word mask1 = 0x00ff >> (left & 7); + word mask2 = 0xff80 >> (right & 7); + + int x = right / 8 - left / 8; + + scrn += left / 8; + + if (x == 0) { + mask1 &= mask2; + mask2 = 0; + } + + /* Clear first byte */ + + clear_byte (scrn++, mask1); + + /* Clear middle bytes */ + + if (display >= _EGA_) + outport (0x03ce, 0xff08); + + while (--x > 0) + *scrn++ = scrn_attr; + + /* Clear last byte */ + + clear_byte (scrn, mask2); + + } + +}/* clear_line */ + +/* + * os_erase_area + * + * Fill a rectangular area of the screen with the current background + * colour. Top left coordinates are (1,1). The cursor does not move. + * + */ + +void os_erase_area (int top, int left, int bottom, int right) +{ + int y; + + top--; + left--; + bottom--; + right--; + + if (display <= _TEXT_) { + + asm mov ax,0x0600 + asm mov ch,byte ptr top + asm mov cl,byte ptr left + asm mov dh,byte ptr bottom + asm mov dl,byte ptr right + asm mov bh,scrn_attr + asm int 0x10 + + } else + + for (y = top; y <= bottom; y++) + clear_line (y, left, right); + +}/* os_erase_area */ + +/* + * copy_byte + * + * Helper function for copy_line. + * + */ + +static void copy_byte (byte far *scrn1, byte far *scrn2, byte mask) +{ + int i; + + if (display == _CGA_) + + *scrn1 = (*scrn1 & ~mask) | (*scrn2 & mask); + + else { + + outport (0x03ce, 0x0005); + + outportb (0x03ce, 0x08); + outportb (0x03cf, mask); + + outportb (0x03ce, 0x04); + outportb (0x03c4, 0x02); + + for (i = 0; i < 4; i++) { + + outportb (0x03cf, i); + outportb (0x03c5, 1 << i); + + asm les bx,scrn2 + asm mov ah,es:[bx] + asm les bx,scrn1 + asm mov al,es:[bx] + asm mov es:[bx],ah + + } + + outportb (0x03c5, 0x0f); + + } + +}/* copy_byte */ + +/* + * copy_line + * + * Helper function for os_scroll_area. + * + */ + +static void copy_line (int y1, int y2, int left, int right) +{ + byte far *scrn1 = get_scrnptr (y1); + byte far *scrn2 = get_scrnptr (y2); + + if (display == _MCGA_) + + _fmemcpy (scrn1 + left, scrn2 + left, right - left + 1); + + else { + + word mask1 = 0x00ff >> (left & 7); + word mask2 = 0xff80 >> (right & 7); + + int x = right / 8 - left / 8; + + scrn1 += left / 8; + scrn2 += left / 8; + + if (x == 0) { + mask1 &= mask2; + mask2 = 0; + } + + /* Copy first byte */ + + copy_byte (scrn1++, scrn2++, mask1); + + /* Copy middle bytes */ + + if (display >= _EGA_) + outport (0x03ce, 0x0105); + + while (--x > 0) + *scrn1++ = *scrn2++; + + /* Copy last byte */ + + copy_byte (scrn1, scrn2, mask2); + + } + +}/* copy_line */ + +/* + * os_scroll_area + * + * Scroll a rectangular area of the screen up (units > 0) or down + * (units < 0) and fill the empty space with the current background + * colour. Top left coordinates are (1,1). The cursor stays put. + * + */ + +void os_scroll_area (int top, int left, int bottom, int right, int units) +{ + int y; + + top--; + left--; + bottom--; + right--; + + if (display <= _TEXT_) { + + asm mov ah,6 + asm mov bx,units + asm cmp bx,0 + asm jg scroll + asm mov ah,7 + asm neg bx + scroll: + asm mov al,bl + asm mov ch,byte ptr top + asm mov cl,byte ptr left + asm mov dh,byte ptr bottom + asm mov dl,byte ptr right + asm mov bh,scrn_attr + asm int 0x10 + + } else + + if (units > 0) + + for (y = top; y <= bottom; y++) + + if (y <= bottom - units) + copy_line (y, y + units, left, right); + else + clear_line (y, left, right); + + else + + for (y = bottom; y >= top; y--) + + if (y >= top - units) + copy_line (y, y + units, left, right); + else + clear_line (y, left, right); + +}/* os_scroll_area */ diff --git a/bctext.c b/bctext.c new file mode 100644 index 0000000..4ab9100 --- /dev/null +++ b/bctext.c @@ -0,0 +1,842 @@ +/* + * file "BCtext.c" + * + * Borland C front end, text functions + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +extern byte far *get_scrnptr (int); + +int current_bg = 0; +int current_fg = 0; +int current_style = 0; +int current_font = 0; + +byte text_bg = 0; +byte text_fg = 0; +byte bg = 0; +byte fg = 0; +byte scrn_attr = 0; + +int cursor_x = 0; +int cursor_y = 0; + +char latin1_to_ascii[] = + " ! c L >o> 1/41/23/4? " + "A A A A Ae A AE C E E E E I I I I " + "Th N O O O O Oe * O U U U Ue Y Th ss " + "a a a a ae a ae c e e e e i i i i " + "th n o o o o oe : o u u u ue y th y "; + +char latin1_to_ibm[] = { + 0x20, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, + 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee, + 0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, + 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8, + 0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, + 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8, + 0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, + 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1, + 0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, + 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, + 0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, + 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98 +}; + +static byte far *graphics_font = NULL; +static byte far *mcga_font = NULL; +static byte far *mcga_width = NULL; +static word far *serif_font = NULL; +static byte far *serif_width = NULL; + +/* + * get_font + * + * Helper function for load_fonts. + * + */ + +static void far *get_chunk (FILE *fp, int num) +{ + static chunk_size[] = { + 0x0300, + 0x0360, + 0x18c0, + 0x18c0, + 0x18c0, + 0x18c0 }; + + void far *ptr; + long offs = 0; + int i; + + ptr = farmalloc (chunk_size[num]); + + for (i = 0; i <= num; i++) + offs -= chunk_size[i]; + + fseek (fp, offs, SEEK_END); + + if (ptr == NULL || !fread (ptr, chunk_size[num], 1, fp)) + os_fatal ("Cannot load fonts"); + + return ptr; + +}/* get_chunk */ + +/* + * load_fonts + * + * Load the proportional and graphics fonts. In the release version all + * font data is appended to the end of the executable. + * + */ + +void load_fonts (const char *progname) +{ + FILE *fp; + + if ((fp = fopen (progname, "rb")) == NULL) + os_fatal ("Cannot load fonts"); + + if (display == _MCGA_) { + + mcga_font = get_chunk (fp, 1); + mcga_width = (byte far *) mcga_font + 0x300; + + } else graphics_font = get_chunk (fp, 0); + + if (display == _AMIGA_ && user_font != 0) { + + serif_font = get_chunk (fp, 1 + user_font); + serif_width = (byte far *) serif_font + 0x1800; + + } + + fclose (fp); + +}/* load_fonts */ + +/* + * os_font_data + * + * Return true if the given font is available. The font can be + * + * TEXT_FONT + * PICTURE_FONT + * GRAPHICS_FONT + * FIXED_WIDTH_FONT + * + * The font size should be stored in "height" and "width". If the given + * font is unavailable then these values must _not_ be changed. + * + */ + +int os_font_data (int font, int *height, int *width) +{ + + /* All fonts of this interface have the same size */ + + *height = h_font_height; + *width = h_font_width; + + /* Not every font is available in every mode */ + + if (font == TEXT_FONT) + return TRUE; + if (font == GRAPHICS_FONT && (display == _CGA_ || display >= _EGA_)) + return TRUE; + if (font == FIXED_WIDTH_FONT) + return TRUE; + + /* Unavailable font */ + + return FALSE; + +}/* os_font_data */ + +/* + * switch_scrn_attr + * + * Parts of the screen are usually erased to background colour. However, + * for deleting text in the input routine it can be useful to erase to + * the current text background colour. The two colours can be different, + * for example when the reverse text style is used. This helper function + * toggles between the two possible behaviours. + * + */ + +void switch_scrn_attr (bool flag) +{ + byte scrn_bg; + byte scrn_fg; + + if (flag) { + scrn_bg = text_bg; + scrn_fg = text_fg; + } else { + scrn_bg = bg; + scrn_fg = fg; + } + + if (display <= _TEXT_) + scrn_attr = (scrn_bg << 4) | scrn_fg; + else if (display == _CGA_) + scrn_attr = (scrn_bg != BLACK) ? 0xff : 0x00; + else + scrn_attr = scrn_bg; + +}/* switch_scrn_attr */ + +/* + * adjust_style + * + * Set the current colours. This combines the current colour selection + * and the current text style. + * + */ + +static void adjust_style (void) +{ + static byte amiga_palette[][3] = { + { 0x00, 0x00, 0x00 }, + { 0x2a, 0x00, 0x00 }, + { 0x00, 0x2a, 0x00 }, + { 0x3f, 0x3f, 0x15 }, + { 0x00, 0x00, 0x2a }, + { 0x2a, 0x00, 0x2a }, + { 0x00, 0x2a, 0x2a }, + { 0x3f, 0x3f, 0x3f }, + { 0x30, 0x30, 0x30 }, + { 0x20, 0x20, 0x20 }, + { 0x10, 0x10, 0x10 }, + }; + + static byte pc_colour[] = { + BLACK, + RED, + GREEN, + YELLOW, + BLUE, + MAGENTA, + CYAN, + WHITE, + DARKGRAY + }; + + static byte palette_bg = 0xff; + static byte palette_fg = 0xff; + + fg = current_fg; + bg = current_bg; + + /* V6 game, Amiga mode: Alter the palette registers if the colours + of window 0 have changed. DAC register #79 holds the foreground, + DAC register #64 the background colour. */ + + if (display == _AMIGA_ && h_version == V6 && cwin == 0) { + + if (fg < 16 && fg != palette_fg) { + + byte R = amiga_palette[fg - 2][0]; + byte G = amiga_palette[fg - 2][1]; + byte B = amiga_palette[fg - 2][2]; + + asm mov ax,0x1010 + asm mov bx,79 + asm mov dh,R + asm mov ch,G + asm mov cl,B + asm int 0x10 + + palette_fg = fg; + + } + + if (bg < 16 && bg != palette_bg) { + + byte R = amiga_palette[bg - 2][0]; + byte G = amiga_palette[bg - 2][1]; + byte B = amiga_palette[bg - 2][2]; + + asm mov ax,0x1010 + asm mov bx,64 + asm mov dh,R + asm mov ch,G + asm mov cl,B + asm int 0x10 + + palette_bg = bg; + + } + + } + + /* Handle colours */ + + if (fg < 16) + + if (display == _MONO_) + fg = (fg == WHITE_COLOUR) ? LIGHTGRAY : BLACK; + else if (h_version == V6 && display == _AMIGA_) + fg = (palette_fg == fg) ? 15 : 0; + else + fg = pc_colour[fg - 2]; + + else fg -= 16; + + if (bg < 16) + + if (display == _MONO_) + bg = (bg == WHITE_COLOUR) ? LIGHTGRAY : BLACK; + else if (h_version == V6 && display == _AMIGA_) + bg = (palette_bg == bg) ? 0 : 15; + else + bg = pc_colour[bg - 2]; + + else bg -= 16; + + /* Handle reverse text style */ + + if (current_style & REVERSE_STYLE) { + text_fg = (user_reverse_fg != -1) ? user_reverse_fg : bg; + text_bg = (user_reverse_bg != -1) ? user_reverse_bg : fg; + } else { + text_fg = fg; + text_bg = bg; + } + + /* Handle emphasis style */ + + if (current_style & EMPHASIS_STYLE) { + + if (display == _MONO_ && text_bg == BLACK) + text_fg = BLUE; /* blue in monochrome mode is underlined */ + if (display == _TEXT_) + text_fg = (user_emphasis != -1) ? user_emphasis : YELLOW; + + } + + /* Handle boldface style */ + + if (current_style & BOLDFACE_STYLE) { + + if (display == _MONO_) + text_fg = WHITE; + if (display == _TEXT_) + text_fg ^= 8; + + } + + /* Set the screen attribute for scrolling and erasing */ + + switch_scrn_attr (FALSE); + +}/* adjust_style */ + +/* + * os_set_colour + * + * Set the foreground and background colours which can be: + * + * DEFAULT_COLOUR + * BLACK_COLOUR + * RED_COLOUR + * GREEN_COLOUR + * YELLOW_COLOUR + * BLUE_COLOUR + * MAGENTA_COLOUR + * CYAN_COLOUR + * WHITE_COLOUR + * + * MS-DOS 320 columns MCGA mode only: + * + * GREY_COLOUR + * + * Amiga only: + * + * LIGHTGREY_COLOUR + * MEDIUMGREY_COLOUR + * DARKGREY_COLOUR + * + * There may be more colours in the range from 16 to 255; see the remarks + * on os_peek_colour. + * + */ + +void os_set_colour (int new_foreground, int new_background) +{ + + current_fg = new_foreground; + current_bg = new_background; + + /* Apply changes */ + + adjust_style (); + +}/* os_set_colour */ + +/* + * os_set_text_style + * + * Set the current text style. Following flags can be set: + * + * REVERSE_STYLE + * BOLDFACE_STYLE + * EMPHASIS_STYLE (aka underline aka italics) + * FIXED_WIDTH_STYLE + * + */ + +void os_set_text_style (int new_style) +{ + + current_style = new_style; + + /* Apply changes */ + + adjust_style (); + +}/* os_set_text_style */ + +/* + * os_set_font + * + * Set the font for text output. The interpreter takes care not to + * choose fonts which aren't supported by the interface. + * + */ + +void os_set_font (int new_font) +{ + + current_font = new_font; + +}/* os_set_font */ + +/* + * write_pattern + * + * Helper function for drawing characters in EGA and Amiga mode. + * + */ + +void write_pattern (byte far *screen, byte val, byte mask) +{ + + if (mask != 0) { + + if (display == _CGA_) { + + if (text_bg == BLACK) + *screen &= ~mask; + if (text_bg == WHITE) + *screen |= mask; + if (text_fg != text_bg) + *screen ^= val; + + } else if (display == _MCGA_) { + + byte i; + + for (i = 0x80; (mask & i) != 0; i >>= 1) + *screen++ = (val & i) ? text_fg : text_bg; + + } else { + + asm mov dx,0x03cf + asm mov al,mask + asm out dx,al + asm les bx,screen + asm mov ch,text_bg + asm mov al,es:[bx] + asm mov es:[bx],ch + asm mov al,val + asm out dx,al + asm mov ch,text_fg + asm mov al,es:[bx] + asm mov es:[bx],ch + + } + + } + +}/* write_pattern */ + +/* + * os_display_char + * + * Display a character of the current font using the current colours and + * text style. The cursor moves to the next position. Printable codes are + * all ASCII values from 32 to 126, ISO Latin-1 characters from 160 to + * 255, ZC_GAP (gap between two sentences) and ZC_INDENT (paragraph + * indentation). The screen should not be scrolled after printing to the + * bottom right corner. + * + */ + +void os_display_char (zchar c) +{ + int width = os_char_width (c); + + /* Handle accented characters */ + + if (c >= ZC_LATIN1_MIN && (story_id != BEYOND_ZORK || (h_flags & GRAPHICS_FLAG))) + + if (display == _CGA_ || display == _MCGA_) { + + char *ptr = latin1_to_ascii + 3 * (c - ZC_LATIN1_MIN); + + char c1 = *ptr++; + char c2 = *ptr++; + char c3 = *ptr++; + + os_display_char (c1); + + if (c2 != ' ') + os_display_char (c2); + if (c3 != ' ') + os_display_char (c3); + + return; + + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) { + + if (c >= ZC_LATIN1_MIN) + c -= 32; + + } else c = latin1_to_ibm[c - ZC_LATIN1_MIN]; + + /* Handle special indentations */ + + if (c == ZC_INDENT) + { os_display_char (' '); os_display_char (' '); os_display_char (' '); return; } + if (c == ZC_GAP) + { os_display_char (' '); os_display_char (' '); return; } + + /* Display character */ + + if (display <= _TEXT_) { + + asm mov ah,2 + asm mov bh,0 + asm mov dh,byte ptr cursor_y + asm mov dl,byte ptr cursor_x + asm int 0x10 + asm mov ah,9 + asm mov bh,0 + asm mov bl,byte ptr text_bg + asm mov cl,4 + asm shl bl,cl + asm or bl,byte ptr text_fg + asm mov cx,1 + asm mov al,byte ptr c + asm int 0x10 + + } else { + + void far *table; + word mask; + word val; + byte mask0; + byte mask1; + int align; + int underline; + int boldface; + int type; + + int shift = (display != _MCGA_) ? cursor_x % 8 : 0; + int offset = (display != _MCGA_) ? cursor_x / 8 : cursor_x; + + int i; + + if (current_font == GRAPHICS_FONT) { + table = graphics_font + 8 * (c - 32); + mask = 0xff; + underline = -1; + boldface = -1; + align = 0; + type = 1; + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) { + table = serif_font + 16 * (c - 32); + mask = 0xffff << (16 - width); + underline = 14; + boldface = 1; + align = 0; + type = 2; + } else if (display == _CGA_) { + table = (byte far *) MK_FP (0xf000, 0xfa6e) + 8 * c; + mask = 0xff; + underline = 7; + boldface = (user_bold_typing != -1) ? 1 : -1; + align = 0; + type = 3; + } else if (display >= _EGA_) { + table = (byte far *) getvect (0x43) + h_font_height * c; + mask = 0xff; + underline = h_font_height - 1; + boldface = (user_bold_typing != -1) ? 1 : -1; + align = 0; + type = 3; + } else { + table = mcga_font + 8 * (c - 32); + mask = 0xff & (0xff << (8 - width)); + underline = 7; + boldface = -1; + align = (width + 1 - mcga_width[c - 32]) / 2; + type = 3; + } + + mask0 = mask >> shift; + mask1 = mask << (8 - shift); + + if (!(current_style & BOLDFACE_STYLE)) + boldface = -1; + if (!(current_style & EMPHASIS_STYLE)) + underline = -1; + + if (display >= _EGA_) { + outport (0x03ce, 0x0205); + outport (0x03ce, 0xff08); + } + + for (i = 0; i < h_font_height; i++) { + + byte far *screen = get_scrnptr (cursor_y + i) + offset; + + if (type == 1) + val = *((byte far *) table + 8 * i / h_font_height); + if (type == 2) + val = *((word far *) table + i); + if (type == 3) + val = *((byte far *) table + i); + + if (align != 0) + val >>= align; + + if (boldface == 1) + val |= val >> 1; + if (underline == i) + val ^= mask; + + if (type == 2) + write_pattern (screen++, val >> (8 + shift), mask >> (8 + shift)); + + write_pattern (screen + 0, val >> shift, mask0); + write_pattern (screen + 1, val << (8 - shift), mask1); + + } + + } + + /* Move cursor to next position */ + + cursor_x += width; + +}/* os_display_char */ + +/* + * os_display_string + * + * Pass a string of characters to os_display_char. + * + */ + +void os_display_string (const zchar *s) +{ + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) { + + int arg = *s++; + + if (c == ZC_NEW_FONT) + os_set_font (arg); + if (c == ZC_NEW_STYLE) + os_set_text_style (arg); + + } else os_display_char (c); + +}/* os_display_string */ + +/* + * os_char_width + * + * Return the width of the character in screen units. + * + */ + +int os_char_width (zchar c) +{ + + /* Handle accented characters */ + + if (c >= ZC_LATIN1_MIN && (story_id != BEYOND_ZORK || (h_flags & GRAPHICS_FLAG))) + + if (display == _CGA_ || display == _MCGA_) { + + const char *ptr = latin1_to_ascii + 3 * (c - ZC_LATIN1_MIN); + + int width = 0; + + char c1 = *ptr++; + char c2 = *ptr++; + char c3 = *ptr++; + + width = os_char_width (c1); + + if (c2 != ' ') + width += os_char_width (c2); + if (c3 != ' ') + width += os_char_width (c3); + + return width; + + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) + + if (c >= ZC_LATIN1_MIN) + c -= 32; + + /* Handle special indentations */ + + if (c == ZC_INDENT) + return 3 * os_char_width (' '); + if (c == ZC_GAP) + return 2 * os_char_width (' '); + + /* Calculate width */ + + if (display <= _TEXT_) + return 1; + if (display == _CGA_) + return 8; + if (display == _EGA_) + return 8; + + if (current_font == GRAPHICS_FONT) + return 8; + if (current_font == FIXED_WIDTH_FONT || (current_style & FIXED_WIDTH_STYLE) || (display == _AMIGA_ && user_font == 0)) + return (display == _AMIGA_) ? 8 : 5; + + if (display == _MCGA_) + return mcga_width[c - 32]; + if (display == _AMIGA_) + return serif_width[c - 32] + ((current_style & BOLDFACE_STYLE) ? 1 : 0); + + return 0; + +}/* os_char_width */ + +/* + * os_string_width + * + * Calculate the length of a word in screen units. Apart from letters, + * the word may contain special codes: + * + * ZC_NEW_STYLE - next character is a new text style + * ZC_NEW_FONT - next character is a new font + * + */ + +int os_string_width (const zchar *s) +{ + int width = 0; + + int saved_font = current_font; + int saved_style = current_style; + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT) { + + int arg = *s++; + + if (c == ZC_NEW_FONT) + current_font = arg; + if (c == ZC_NEW_STYLE) + current_style = arg; + + } else width += os_char_width (c); + + current_font = saved_font; + current_style = saved_style; + + return width; + +}/* os_string_width */ + +/* + * os_set_cursor + * + * Place the text cursor at the given coordinates. Top left is (1,1). + * + */ + +void os_set_cursor (int y, int x) +{ + + cursor_y = y - 1; + cursor_x = x - 1; + +}/* os_set_cursor */ + +/* + * os_more_prompt + * + * Display a MORE prompt, wait for a keypress and remove the MORE + * prompt from the screen. + * + */ + +void os_more_prompt (void) +{ + int saved_x; + + /* Save text font and style */ + + int saved_font = current_font; + int saved_style = current_style; + + /* Choose plain text style */ + + current_font = TEXT_FONT; + current_style = 0; + + adjust_style (); + + /* Wait until the user presses a key */ + + saved_x = cursor_x; + + os_display_string ((zchar *) "[MORE]"); + os_read_key (0, TRUE); + + os_erase_area (cursor_y + 1, + saved_x + 1, + cursor_y + h_font_height, + cursor_x + 1); + + cursor_x = saved_x; + + /* Restore text font and style */ + + current_font = saved_font; + current_style = saved_style; + + adjust_style (); + +}/* os_more_prompt */ diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..6aa48a1 --- /dev/null +++ b/buffer.c @@ -0,0 +1,112 @@ +/* + * buffer.c + * + * Text buffering and word wrapping + * + */ + +#include "frotz.h" + +extern void stream_char (zchar); +extern void stream_word (const zchar *); +extern void stream_new_line (void); + +static zchar buffer[TEXT_BUFFER_SIZE]; +static bufpos = 0; + +static zchar prev_c = 0; + +/* + * flush_buffer + * + * Copy the contents of the text buffer to the output streams. + * + */ + +void flush_buffer (void) +{ + static bool locked = FALSE; + + /* Make sure we stop when flush_buffer is called from flush_buffer. + Note that this is difficult to avoid as we might print a newline + during flush_buffer, which might cause a newline interrupt, that + might execute any arbitrary opcode, which might flush the buffer. */ + + if (locked || bufpos == 0) + return; + + /* Send the buffer to the output streams */ + + buffer[bufpos] = 0; + + locked = TRUE; stream_word (buffer); locked = FALSE; + + /* Reset the buffer */ + + bufpos = 0; + prev_c = 0; + +}/* flush_buffer */ + +/* + * print_char + * + * High level output function. + * + */ + +void print_char (zchar c) +{ + static bool flag = FALSE; + + if (message || ostream_memory || enable_buffering) { + + if (!flag) { + + /* Characters 0 and ZC_RETURN are special cases */ + + if (c == ZC_RETURN) + { new_line (); return; } + if (c == 0) + return; + + /* Flush the buffer before a whitespace or after a hyphen */ + + if (c == ' ' || c == ZC_INDENT || c == ZC_GAP || prev_c == '-' && c != '-') + flush_buffer (); + + /* Set the flag if this is part one of a style or font change */ + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) + flag = TRUE; + + /* Remember the current character code */ + + prev_c = c; + + } else flag = FALSE; + + /* Insert the character into the buffer */ + + buffer[bufpos++] = c; + + if (bufpos == TEXT_BUFFER_SIZE) + runtime_error ("Text buffer overflow"); + + } else stream_char (c); + +}/* print_char */ + +/* + * new_line + * + * High level newline function. + * + */ + +void new_line (void) +{ + + flush_buffer (); stream_new_line (); + +}/* new_line */ diff --git a/fastmem.c b/fastmem.c new file mode 100644 index 0000000..d121f1b --- /dev/null +++ b/fastmem.c @@ -0,0 +1,910 @@ +/* + * fastmem.c + * + * Memory related functions (fast version without virtual memory) + * + */ + +#include +#include +#include "frotz.h" + +#ifdef __MSDOS__ + +#include + +#define malloc(size) farmalloc (size) +#define realloc(size,p) farrealloc (size,p) +#define free(size) farfree (size) +#define memcpy(d,s,n) _fmemcpy (d,s,n) + +#else + +#include + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +#define far + +#endif + +extern void seed_random (int); +extern void restart_screen (void); +extern void refresh_text_style (void); +extern void call (zword, int, zword *, int); +extern void split_window (zword); +extern void script_open (void); +extern void script_close (void); + +extern void (*op0_opcodes[]) (void); +extern void (*op1_opcodes[]) (void); +extern void (*op2_opcodes[]) (void); +extern void (*var_opcodes[]) (void); + +char save_name[MAX_FILE_NAME + 1] = DEFAULT_SAVE_NAME; +char auxilary_name[MAX_FILE_NAME + 1] = DEFAULT_AUXILARY_NAME; + +zbyte far *zmp = NULL; +zbyte far *pcp = NULL; + +static FILE *story_fp = NULL; + +static zbyte far *undo[MAX_UNDO_SLOTS]; + +static undo_slots = 0; +static undo_count = 0; +static undo_valid = 0; + +/* + * get_header_extension + * + * Read a value from the header extension (former mouse table). + * + */ + +zword get_header_extension (int entry) +{ + zword addr; + zword val; + + if (h_extension_table == 0 || entry > hx_table_size) + return 0; + + addr = h_extension_table + 2 * entry; + LOW_WORD (addr, val) + + return val; + +}/* get_header_extension */ + +/* + * set_header_extension + * + * Set an entry in the header extension (former mouse table). + * + */ + +void set_header_extension (int entry, zword val) +{ + zword addr; + + if (h_extension_table == 0 || entry > hx_table_size) + return; + + addr = h_extension_table + 2 * entry; + SET_WORD (addr, val) + +}/* set_header_extension */ + +/* + * restart_header + * + * Set all header fields which hold information about the interpreter. + * + */ + +void restart_header (void) +{ + zword screen_x_size; + zword screen_y_size; + zbyte font_x_size; + zbyte font_y_size; + + int i; + + SET_BYTE (H_CONFIG, h_config) + SET_WORD (H_FLAGS, h_flags) + + if (h_version >= V4) { + SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number) + SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version) + SET_BYTE (H_SCREEN_ROWS, h_screen_rows) + SET_BYTE (H_SCREEN_COLS, h_screen_cols) + } + + /* It's less trouble to use font size 1x1 for V5 games, especially + because of a bug in the unreleased German version of "Zork 1" */ + + if (h_version != V6) { + screen_x_size = (zword) h_screen_cols; + screen_y_size = (zword) h_screen_rows; + font_x_size = 1; + font_y_size = 1; + } else { + screen_x_size = h_screen_width; + screen_y_size = h_screen_height; + font_x_size = h_font_width; + font_y_size = h_font_height; + } + + if (h_version >= V5) { + SET_WORD (H_SCREEN_WIDTH, screen_x_size) + SET_WORD (H_SCREEN_HEIGHT, screen_y_size) + SET_BYTE (H_FONT_HEIGHT, font_y_size) + SET_BYTE (H_FONT_WIDTH, font_x_size) + SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background) + SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground) + } + + if (h_version == V6) + for (i = 0; i < 8; i++) + storeb ((zword) (H_USER_NAME + i), h_user_name[i]); + + SET_BYTE (H_STANDARD_HIGH, h_standard_high) + SET_BYTE (H_STANDARD_LOW, h_standard_low) + +}/* restart_header */ + +/* + * init_memory + * + * Allocate memory and load the story file. + * + */ + +void init_memory (void) +{ + long size; + zword addr; + unsigned n; + int i, j; + + static struct { + enum story story_id; + zword release; + zbyte serial[6]; + } records[] = { + { SHERLOCK, 21, "871214" }, + { SHERLOCK, 26, "880127" }, + { BEYOND_ZORK, 47, "870915" }, + { BEYOND_ZORK, 49, "870917" }, + { BEYOND_ZORK, 51, "870923" }, + { BEYOND_ZORK, 57, "871221" }, + { ZORK_ZERO, 296, "881019" }, + { ZORK_ZERO, 366, "890323" }, + { ZORK_ZERO, 383, "890602" }, + { ZORK_ZERO, 393, "890714" }, + { SHOGUN, 292, "890314" }, + { SHOGUN, 295, "890321" }, + { SHOGUN, 311, "890510" }, + { SHOGUN, 322, "890706" }, + { ARTHUR, 54, "890606" }, + { ARTHUR, 63, "890622" }, + { ARTHUR, 74, "890714" }, + { JOURNEY, 26, "890316" }, + { JOURNEY, 30, "890322" }, + { JOURNEY, 77, "890616" }, + { JOURNEY, 83, "890706" }, + { LURKING_HORROR, 203, "870506" }, + { LURKING_HORROR, 219, "870912" }, + { LURKING_HORROR, 221, "870918" }, + { UNKNOWN, 0, "------" } + }; + + /* Open story file */ + + if ((story_fp = fopen (story_name, "rb")) == NULL) + os_fatal ("Cannot open story file"); + + /* Allocate memory for story header */ + + if ((zmp = (zbyte far *) malloc (64)) == NULL) + os_fatal ("Out of memory"); + + /* Load header into memory */ + + if (fread (zmp, 1, 64, story_fp) != 64) + os_fatal ("Story file read error"); + + /* Copy header fields to global variables */ + + LOW_BYTE (H_VERSION, h_version) + + if (h_version < V1 || h_version > V8) + os_fatal ("Unknown Z-code version"); + + LOW_BYTE (H_CONFIG, h_config) + + if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) + os_fatal ("Byte swapped story file"); + + LOW_WORD (H_RELEASE, h_release) + LOW_WORD (H_RESIDENT_SIZE, h_resident_size) + LOW_WORD (H_START_PC, h_start_pc) + LOW_WORD (H_DICTIONARY, h_dictionary) + LOW_WORD (H_OBJECTS, h_objects) + LOW_WORD (H_GLOBALS, h_globals) + LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size) + LOW_WORD (H_FLAGS, h_flags) + + for (i = 0, addr = H_SERIAL; i < 6; i++, addr++) + LOW_BYTE (addr, h_serial[i]) + + /* Auto-detect buggy story files that need special fixes */ + + for (i = 0; records[i].story_id != UNKNOWN; i++) { + + if (h_release == records[i].release) { + + for (j = 0; j < 6; j++) + if (h_serial[j] != records[i].serial[j]) + goto no_match; + + story_id = records[i].story_id; + + } + + no_match: + + } + + LOW_WORD (H_ABBREVIATIONS, h_abbreviations) + LOW_WORD (H_FILE_SIZE, h_file_size) + + /* Calculate story file size in bytes */ + + if (h_file_size != 0) { + + story_size = (long) 2 * h_file_size; + + if (h_version >= V4) + story_size *= 2; + if (h_version >= V6) + story_size *= 2; + + } else { /* some old games lack the file size entry */ + + fseek (story_fp, 0, SEEK_END); + story_size = ftell (story_fp); + fseek (story_fp, 64, SEEK_SET); + + } + + LOW_WORD (H_CHECKSUM, h_checksum) + LOW_WORD (H_ALPHABET, h_alphabet) + LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset) + LOW_WORD (H_STRINGS_OFFSET, h_strings_offset) + LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys) + LOW_WORD (H_EXTENSION_TABLE, h_extension_table) + + /* Zork Zero Macintosh doesn't have the graphics flag set */ + + if (story_id == ZORK_ZERO && h_release == 296) + h_flags |= GRAPHICS_FLAG; + + /* Adjust opcode tables */ + + if (h_version <= V4) { + op0_opcodes[0x09] = z_pop; + op1_opcodes[0x0f] = z_not; + } else { + op0_opcodes[0x09] = z_catch; + op1_opcodes[0x0f] = z_call_n; + } + + /* Allocate memory for story data */ + + if ((zmp = (zbyte far *) realloc (zmp, story_size)) == NULL) + os_fatal ("Out of memory"); + + /* Load story file in chunks of 32KB */ + + n = 0x8000; + + for (size = 64; size < story_size; size += n) { + + if (story_size - size < 0x8000) + n = (unsigned) (story_size - size); + + SET_PC (size) + + if (fread (pcp, 1, n, story_fp) != n) + os_fatal ("Story file read error"); + + } + + /* Read header extension table */ + + hx_table_size = get_header_extension (HX_TABLE_SIZE); + hx_unicode_table = get_header_extension (HX_UNICODE_TABLE); + +}/* init_memory */ + +/* + * init_undo + * + * Allocate memory for multiple undo. It is important not to occupy + * all the memory available, since the IO interface may need memory + * during the game, e.g. for loading sounds or pictures. + * + */ + +void init_undo (void) +{ + void far *reserved; + + if (reserve_mem != 0) + if ((reserved = malloc (reserve_mem)) == NULL) + return; + + while (undo_slots < option_undo_slots && undo_slots < MAX_UNDO_SLOTS) { + + void far *mem = malloc ((long) sizeof (stack) + h_dynamic_size); + + if (mem == NULL) + break; + + undo[undo_slots++] = mem; + + } + + if (reserve_mem != 0) + free (reserved); + +}/* init_undo */ + +/* + * reset_memory + * + * Close the story file and deallocate memory. + * + */ + +void reset_memory (void) +{ + + fclose (story_fp); + + while (undo_slots--) + free (undo[undo_slots]); + + free (zmp); + +}/* reset_memory */ + +/* + * storeb + * + * Write a byte value to the dynamic Z-machine memory. + * + */ + +void storeb (zword addr, zbyte value) +{ + + if (addr >= h_dynamic_size) + runtime_error ("Store out of dynamic memory"); + + if (addr == H_FLAGS + 1) { /* flags register is modified */ + + h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG); + h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG); + + if (value & SCRIPTING_FLAG) { + if (!ostream_script) + script_open (); + } else { + if (ostream_script) + script_close (); + } + + refresh_text_style (); + + } + + SET_BYTE (addr, value) + +}/* storeb */ + +/* + * storew + * + * Write a word value to the dynamic Z-machine memory. + * + */ + +void storew (zword addr, zword value) +{ + + storeb ((zword) (addr + 0), hi (value)); + storeb ((zword) (addr + 1), lo (value)); + +}/* storew */ + +/* + * z_restart, re-load dynamic area, clear the stack and set the PC. + * + * no zargs used + * + */ + +void z_restart (void) +{ + static bool first_restart = TRUE; + + flush_buffer (); + + os_restart_game (RESTART_BEGIN); + + seed_random (0); + + if (!first_restart) { + + fseek (story_fp, 0, SEEK_SET); + + if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size) + os_fatal ("Story file read error"); + + } else first_restart = FALSE; + + restart_header (); + restart_screen (); + + sp = fp = stack + STACK_SIZE; + + if (h_version != V6) { + + long pc = (long) h_start_pc; + SET_PC (pc) + + } else call (h_start_pc, 0, NULL, 0); + + os_restart_game (RESTART_END); + +}/* z_restart */ + +/* + * get_default_name + * + * Read a default file name from the memory of the Z-machine and + * copy it to a string. + * + */ + +static void get_default_name (char *default_name, zword addr) +{ + + if (addr != 0) { + + zbyte len; + int i; + + LOW_BYTE (addr, len) + addr++; + + for (i = 0; i < len; i++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + default_name[i] = c; + + } + + default_name[i] = 0; + + if (strchr (default_name, '.') == NULL) + strcpy (default_name + i, ".AUX"); + + } else strcpy (default_name, auxilary_name); + +}/* get_default_name */ + +/* + * z_restore, restore [a part of] a Z-machine state from disk + * + * zargs[0] = address of area to restore (optional) + * zargs[1] = number of bytes to restore + * zargs[2] = address of suggested file name + * + */ + +void z_restore (void) +{ + char new_name[MAX_FILE_NAME + 1]; + char default_name[MAX_FILE_NAME + 1]; + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_LOAD_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = fopen (new_name, "rb")) == NULL) + goto finished; + + /* Load auxilary file */ + + success = fread (zmp + zargs[0], 1, zargs[1], gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword release; + zword addr; + int i; + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_RESTORE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = fopen (new_name, "rb")) == NULL) + goto finished; + + /* Load game file */ + + release = (unsigned) fgetc (gfp) << 8; + release |= fgetc (gfp); + + (void) fgetc (gfp); + (void) fgetc (gfp); + + /* Check the release number */ + + if (release == h_release) { + + pc = (long) fgetc (gfp) << 16; + pc |= (unsigned) fgetc (gfp) << 8; + pc |= fgetc (gfp); + + SET_PC (pc) + + sp = stack + (fgetc (gfp) << 8); + sp += fgetc (gfp); + fp = stack + (fgetc (gfp) << 8); + fp += fgetc (gfp); + + for (i = (int) (sp - stack); i < STACK_SIZE; i++) { + stack[i] = (unsigned) fgetc (gfp) << 8; + stack[i] |= fgetc (gfp); + } + + fseek (story_fp, 0, SEEK_SET); + + for (addr = 0; addr < h_dynamic_size; addr++) { + int skip = fgetc (gfp); + for (i = 0; i < skip; i++) + zmp[addr++] = fgetc (story_fp); + zmp[addr] = fgetc (gfp); + (void) fgetc (story_fp); + } + + /* Check for errors */ + + if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size) + os_fatal ("Error reading save file"); + + /* Reset upper window (V3 only) */ + + if (h_version == V3) + split_window (0); + + /* Initialise story header */ + + restart_header (); + + /* Success */ + + success = 2; + + } else print_string ("Invalid save file\n"); + + /* Close game file */ + + fclose (gfp); + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_restore */ + +/* + * restore_undo + * + * This function does the dirty work for z_restore_undo. + * + */ + +int restore_undo (void) +{ + + if (undo_slots == 0) /* undo feature unavailable */ + + return -1; + + else if (undo_valid == 0) /* no saved game state */ + + return 0; + + else { /* undo possible */ + + long pc; + + if (undo_count == 0) + undo_count = undo_slots; + + memcpy (stack, undo[undo_count - 1], sizeof (stack)); + memcpy (zmp, undo[undo_count - 1] + sizeof (stack), h_dynamic_size); + + pc = ((long) stack[0] << 16) | stack[1]; + sp = stack + stack[2]; + fp = stack + stack[3]; + + SET_PC (pc) + + restart_header (); + + undo_count--; + undo_valid--; + + return 2; + + } + +}/* restore_undo */ + +/* + * z_restore_undo, restore a Z-machine state from memory. + * + * no zargs used + * + */ + +void z_restore_undo (void) +{ + + store ((zword) restore_undo ()); + +}/* restore_undo */ + +/* + * z_save, save [a part of] the Z-machine state to disk. + * + * zargs[0] = address of memory area to save (optional) + * zargs[1] = number of bytes to save + * zargs[2] = address of suggested file name + * + */ + +void z_save (void) +{ + char new_name[MAX_FILE_NAME + 1]; + char default_name[MAX_FILE_NAME + 1]; + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = fopen (new_name, "wb")) == NULL) + goto finished; + + /* Write auxilary file */ + + success = fwrite (zmp + zargs[0], zargs[1], 1, gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword addr; + zword nsp, nfp; + int skip; + int i; + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_SAVE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = fopen (new_name, "wb")) == NULL) + goto finished; + + /* Write game file */ + + fputc ((int) hi (h_release), gfp); + fputc ((int) lo (h_release), gfp); + fputc ((int) hi (h_checksum), gfp); + fputc ((int) lo (h_checksum), gfp); + + GET_PC (pc) + + fputc ((int) (pc >> 16) & 0xff, gfp); + fputc ((int) (pc >> 8) & 0xff, gfp); + fputc ((int) (pc) & 0xff, gfp); + + nsp = (int) (sp - stack); + nfp = (int) (fp - stack); + + fputc ((int) hi (nsp), gfp); + fputc ((int) lo (nsp), gfp); + fputc ((int) hi (nfp), gfp); + fputc ((int) lo (nfp), gfp); + + for (i = nsp; i < STACK_SIZE; i++) { + fputc ((int) hi (stack[i]), gfp); + fputc ((int) lo (stack[i]), gfp); + } + + fseek (story_fp, 0, SEEK_SET); + + for (addr = 0, skip = 0; addr < h_dynamic_size; addr++) + if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) { + fputc (skip, gfp); + fputc (zmp[addr], gfp); + skip = 0; + } else skip++; + + /* Close game file and check for errors */ + + if (fclose (gfp) == EOF || ferror (story_fp)) { + print_string ("Error writing save file\n"); + goto finished; + } + + /* Success */ + + success = 1; + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_save */ + +/* + * save_undo + * + * This function does the dirty work for z_save_undo. + * + */ + +int save_undo (void) +{ + long pc; + + if (undo_slots == 0) /* undo feature unavailable */ + + return -1; + + else { /* save undo possible */ + + if (undo_count == undo_slots) + undo_count = 0; + + GET_PC (pc) + + stack[0] = (zword) (pc >> 16); + stack[1] = (zword) (pc & 0xffff); + stack[2] = (zword) (sp - stack); + stack[3] = (zword) (fp - stack); + + memcpy (undo[undo_count], stack, sizeof (stack)); + memcpy (undo[undo_count] + sizeof (stack), zmp, h_dynamic_size); + + if (++undo_count == undo_slots) + undo_count = 0; + if (++undo_valid > undo_slots) + undo_valid = undo_slots; + + return 1; + + } + +}/* save_undo */ + +/* + * z_save_undo, save the current Z-machine state for a future undo. + * + * no zargs used + * + */ + +void z_save_undo (void) +{ + + store ((zword) save_undo ()); + +}/* z_save_undo */ + +/* + * z_verify, check the story file integrity. + * + * no zargs used + * + */ + +void z_verify (void) +{ + zword checksum = 0; + long i; + + /* Sum all bytes in story file except header bytes */ + + fseek (story_fp, 64, SEEK_SET); + + for (i = 64; i < story_size; i++) + checksum += fgetc (story_fp); + + /* Branch if the checksums are equal */ + + branch (checksum == h_checksum); + +}/* z_verify */ diff --git a/files.c b/files.c new file mode 100644 index 0000000..784f04d --- /dev/null +++ b/files.c @@ -0,0 +1,555 @@ +/* + * files.c + * + * Transscription, recording and playback + * + */ + +#include +#include +#include "frotz.h" + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +extern void set_more_prompts (bool); + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); + +char script_name[MAX_FILE_NAME + 1] = DEFAULT_SCRIPT_NAME; +char command_name[MAX_FILE_NAME + 1] = DEFAULT_COMMAND_NAME; + +#ifdef __MSDOS__ +extern char latin1_to_ibm[]; +#endif + +static script_width = 0; + +static FILE *sfp = NULL; +static FILE *rfp = NULL; +static FILE *pfp = NULL; + +/* + * script_open + * + * Open the transscript file. 'AMFV' makes this more complicated as it + * turns transscription on/off several times to exclude some text from + * the transscription file. This wasn't a problem for the original V4 + * interpreters which always sent transscription to the printer, but it + * means a problem to modern interpreters that offer to open a new file + * every time transscription is turned on. Our solution is to append to + * the old transscription file in V1 to V4, and to ask for a new file + * name in V5+. + * + */ + +void script_open (void) +{ + static bool script_valid = FALSE; + + char new_name[MAX_FILE_NAME + 1]; + + h_flags &= ~SCRIPTING_FLAG; + + if (h_version >= V5 || !script_valid) { + + if (!os_read_file_name (new_name, script_name, FILE_SCRIPT)) + goto done; + + strcpy (script_name, new_name); + + } + + /* Opening in "at" mode doesn't work for script_erase_input... */ + + if ((sfp = fopen (script_name, "r+t")) != NULL || (sfp = fopen (script_name, "w+t")) != NULL) { + + fseek (sfp, 0, SEEK_END); + + h_flags |= SCRIPTING_FLAG; + + script_valid = TRUE; + ostream_script = TRUE; + + script_width = 0; + + } else print_string ("Cannot open file\n"); + +done: + + SET_WORD (H_FLAGS, h_flags) + +}/* script_open */ + +/* + * script_close + * + * Stop transscription. + * + */ + +void script_close (void) +{ + + h_flags &= ~SCRIPTING_FLAG; + SET_WORD (H_FLAGS, h_flags) + + fclose (sfp); ostream_script = FALSE; + +}/* script_close */ + +/* + * script_new_line + * + * Write a newline to the transscript file. + * + */ + +void script_new_line (void) +{ + + if (fputc ('\n', sfp) == EOF) + script_close (); + + script_width = 0; + +}/* script_new_line */ + +/* + * script_char + * + * Write a single character to the transscript file. + * + */ + +void script_char (zchar c) +{ + + if (c == ZC_INDENT && script_width != 0) + c = ' '; + + if (c == ZC_INDENT) + { script_char (' '); script_char (' '); script_char (' '); return; } + if (c == ZC_GAP) + { script_char (' '); script_char (' '); return; } + +#ifdef __MSDOS__ + if (c >= ZC_LATIN1_MIN) + c = latin1_to_ibm[c - ZC_LATIN1_MIN]; +#endif + + fputc (c, sfp); script_width++; + +}/* script_char */ + +/* + * script_word + * + * Write a string to the transscript file. + * + */ + +void script_word (const zchar *s) +{ + int width; + int i; + + if (*s == ZC_INDENT && script_width != 0) + script_char (*s++); + + for (i = 0, width = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_STYLE || s[i] == ZC_NEW_FONT) + i++; + else if (s[i] == ZC_GAP) + width += 3; + else if (s[i] == ZC_INDENT) + width += 2; + else + width += 1; + + if (option_script_cols != 0 && script_width + width > option_script_cols) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + s++; + + script_new_line (); + + } + + for (i = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE) + i++; + else + script_char (s[i]); + +}/* script_word */ + +/* + * script_write_input + * + * Send an input line to the transscript file. + * + */ + +void script_write_input (const zchar *buf, zchar key) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + if (option_script_cols != 0 && script_width + width > option_script_cols) + script_new_line (); + + for (i = 0; buf[i] != 0; i++) + script_char (buf[i]); + + if (key == ZC_RETURN) + script_new_line (); + +}/* script_write_input */ + +/* + * script_erase_input + * + * Remove an input line from the transscript file. + * + */ + +void script_erase_input (const zchar *buf) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + fseek (sfp, -width, SEEK_CUR); script_width -= width; + +}/* script_erase_input */ + +/* + * script_mssg_on + * + * Start sending a "debugging" message to the transscript file. + * + */ + +void script_mssg_on (void) +{ + + if (script_width != 0) + script_new_line (); + + script_char (ZC_INDENT); + +}/* script_mssg_on */ + +/* + * script_mssg_off + * + * Stop writing a "debugging" message. + * + */ + +void script_mssg_off (void) +{ + + script_new_line (); + +}/* script_mssg_off */ + +/* + * record_open + * + * Open a file to record the player's input. + * + */ + +void record_open (void) +{ + char new_name[MAX_FILE_NAME + 1]; + + if (os_read_file_name (new_name, command_name, FILE_RECORD)) { + + strcpy (command_name, new_name); + + if ((rfp = fopen (new_name, "wt")) != NULL) + ostream_record = TRUE; + else + print_string ("Cannot open file\n"); + + } + +}/* record_open */ + +/* + * record_close + * + * Stop recording the player's input. + * + */ + +void record_close (void) +{ + + fclose (rfp); ostream_record = FALSE; + +}/* record_close */ + +/* + * record_code + * + * Helper function for record_char. + * + */ + +static void record_code (int c, bool force_encoding) +{ + + if (force_encoding || c == '[' || c < 0x20 || c > 0x7e) { + + int i; + + fputc ('[', rfp); + + for (i = 10000; i != 0; i /= 10) + if (c >= i || i == 1) + fputc ('0' + (c / i) % 10, rfp); + + fputc (']', rfp); + + } else fputc (c, rfp); + +}/* record_code */ + +/* + * record_char + * + * Write a character to the command file. + * + */ + +static void record_char (zchar c) +{ + + if (c != ZC_RETURN) + + if (c < ZC_HKEY_MIN || c > ZC_HKEY_MAX) { + + record_code (translate_to_zscii (c), FALSE); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + record_code (mouse_x, TRUE); + record_code (mouse_y, TRUE); + } + + } else record_code (1000 + c - ZC_HKEY_MIN, TRUE); + +}/* record_char */ + +/* + * record_write_key + * + * Copy a keystroke to the command file. + * + */ + +void record_write_key (zchar key) +{ + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_key */ + +/* + * record_write_input + * + * Copy a line of input to a command file. + * + */ + +void record_write_input (const zchar *buf, zchar key) +{ + zchar c; + + while ((c = *buf++) != 0) + record_char (c); + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_input */ + +/* + * replay_open + * + * Open a file of commands for playback. + * + */ + +void replay_open (void) +{ + char new_name[MAX_FILE_NAME + 1]; + + if (os_read_file_name (new_name, command_name, FILE_PLAYBACK)) { + + strcpy (command_name, new_name); + + if ((pfp = fopen (new_name, "rt")) != NULL) { + + set_more_prompts (read_yes_or_no ("Do you want MORE prompts")); + + istream_replay = TRUE; + + } else print_string ("Cannot open file\n"); + + } + +}/* replay_open */ + +/* + * replay_close + * + * Stop playback of commands. + * + */ + +void replay_close (void) +{ + + set_more_prompts (TRUE); + + fclose (pfp); istream_replay = FALSE; + +}/* replay_close */ + +/* + * replay_code + * + * Helper function for replay_key and replay_line. + * + */ + +static int replay_code (void) +{ + int c; + + if ((c = fgetc (pfp)) == '[') { + + int c2; + + c = 0; + + while ((c2 = fgetc (pfp)) != EOF && c2 >= '0' && c2 <= '9') + c = 10 * c + c2 - '0'; + + return (c2 == ']') ? c : EOF; + + } else return c; + +}/* replay_code */ + +/* + * replay_char + * + * Read a character from the command file. + * + */ + +static zchar replay_char (void) +{ + int c; + + if ((c = replay_code ()) != EOF) { + + if (c != '\n') + + if (c < 1000) { + + c = translate_from_zscii (c); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + mouse_x = replay_code (); + mouse_y = replay_code (); + } + + return c; + + } else return ZC_HKEY_MIN + c - 1000; + + ungetc ('\n', pfp); + + return ZC_RETURN; + + } else return ZC_BAD; + +}/* replay_char */ + +/* + * replay_read_key + * + * Read a keystroke from a command file. + * + */ + +zchar replay_read_key (void) +{ + zchar key; + + key = replay_char (); + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return key; + +}/* replay_read_key */ + +/* + * replay_read_input + * + * Read a line of input from a command file. + * + */ + +zchar replay_read_input (zchar *buf) +{ + zchar c; + + for (;;) { + + c = replay_char (); + + if (c == ZC_BAD || is_terminator (c)) + break; + + *buf++ = c; + + } + + *buf = 0; + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return c; + +}/* replay_read_input */ diff --git a/font.dat b/font.dat new file mode 100644 index 0000000..fe9799c Binary files /dev/null and b/font.dat differ diff --git a/frotz.h b/frotz.h new file mode 100644 index 0000000..93a2c96 --- /dev/null +++ b/frotz.h @@ -0,0 +1,597 @@ +/* + * frotz.h + * + * Global declarations and definitions + * + */ + +typedef int bool; + +#define TRUE 1 +#define FALSE 0 + +typedef unsigned char zbyte; +typedef unsigned short zword; + +enum story { + BEYOND_ZORK, + SHERLOCK, + ZORK_ZERO, + SHOGUN, + ARTHUR, + JOURNEY, + LURKING_HORROR, + UNKNOWN +}; + +typedef unsigned char zchar; + +/*** Constants that may be set at compile time ***/ + +#ifndef MAX_UNDO_SLOTS +#define MAX_UNDO_SLOTS 25 +#endif +#ifndef MAX_FILE_NAME +#define MAX_FILE_NAME 80 +#endif +#ifndef TEXT_BUFFER_SIZE +#define TEXT_BUFFER_SIZE 200 +#endif +#ifndef INPUT_BUFFER_SIZE +#define INPUT_BUFFER_SIZE 200 +#endif +#ifndef STACK_SIZE +#define STACK_SIZE 1024 +#endif + +#ifndef DEFAULT_SAVE_NAME +#define DEFAULT_SAVE_NAME "story.sav" +#endif +#ifndef DEFAULT_SCRIPT_NAME +#define DEFAULT_SCRIPT_NAME "story.scr" +#endif +#ifndef DEFAULT_COMMAND_NAME +#define DEFAULT_COMMAND_NAME "story.rec" +#endif +#ifndef DEFAULT_AUXILARY_NAME +#define DEFAULT_AUXILARY_NAME "story.aux" +#endif + +/*** Story file header format ***/ + +#define H_VERSION 0 +#define H_CONFIG 1 +#define H_RELEASE 2 +#define H_RESIDENT_SIZE 4 +#define H_START_PC 6 +#define H_DICTIONARY 8 +#define H_OBJECTS 10 +#define H_GLOBALS 12 +#define H_DYNAMIC_SIZE 14 +#define H_FLAGS 16 +#define H_SERIAL 18 +#define H_ABBREVIATIONS 24 +#define H_FILE_SIZE 26 +#define H_CHECKSUM 28 +#define H_INTERPRETER_NUMBER 30 +#define H_INTERPRETER_VERSION 31 +#define H_SCREEN_ROWS 32 +#define H_SCREEN_COLS 33 +#define H_SCREEN_WIDTH 34 +#define H_SCREEN_HEIGHT 36 +#define H_FONT_HEIGHT 38 /* this is the font width in V5 */ +#define H_FONT_WIDTH 39 /* this is the font height in V5 */ +#define H_FUNCTIONS_OFFSET 40 +#define H_STRINGS_OFFSET 42 +#define H_DEFAULT_BACKGROUND 44 +#define H_DEFAULT_FOREGROUND 45 +#define H_TERMINATING_KEYS 46 +#define H_LINE_WIDTH 48 +#define H_STANDARD_HIGH 50 +#define H_STANDARD_LOW 51 +#define H_ALPHABET 52 +#define H_EXTENSION_TABLE 54 +#define H_USER_NAME 56 + +#define HX_TABLE_SIZE 0 +#define HX_MOUSE_X 1 +#define HX_MOUSE_Y 2 +#define HX_UNICODE_TABLE 3 + +/*** Various Z-machine constants ***/ + +#define V1 1 +#define V2 2 +#define V3 3 +#define V4 4 +#define V5 5 +#define V6 6 +#define V7 7 +#define V8 8 + +#define CONFIG_BYTE_SWAPPED 0x01 /* Story file is byte swapped - V3 */ +#define CONFIG_TIME 0x02 /* Status line displays time - V3 */ +#define CONFIG_TWODISKS 0x04 /* Story file occupied two disks - V3 */ +#define CONFIG_TANDY 0x08 /* Tandy licensed game - V3 */ +#define CONFIG_NOSTATUSLINE 0x10 /* Interpr can't support status lines - V3 */ +#define CONFIG_SPLITSCREEN 0x20 /* Interpr supports split screen mode - V3 */ +#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font - V3 */ + +#define CONFIG_COLOUR 0x01 /* Interpr supports colour - V5+ */ +#define CONFIG_PICTURES 0x02 /* Interpr supports pictures - V6 */ +#define CONFIG_BOLDFACE 0x04 /* Interpr supports boldface style - V4+ */ +#define CONFIG_EMPHASIS 0x08 /* Interpr supports emphasis style - V4+ */ +#define CONFIG_FIXED 0x10 /* Interpr supports fixed width style - V4+ */ +#define CONFIG_TIMEDINPUT 0x80 /* Interpr supports timed input - V4+ */ + +#define SCRIPTING_FLAG 0x0001 /* Outputting to transscription file - V1+ */ +#define FIXED_FONT_FLAG 0x0002 /* Use fixed width font - V3+ */ +#define REFRESH_FLAG 0x0004 /* Refresh the screen - V6 */ +#define GRAPHICS_FLAG 0x0008 /* Game wants to use graphics - V5+ */ +#define OLD_SOUND_FLAG 0x0010 /* Game wants to use sound effects - V3 */ +#define UNDO_FLAG 0x0010 /* Game wants to use UNDO feature - V5+ */ +#define MOUSE_FLAG 0x0020 /* Game wants to use a mouse - V5+ */ +#define COLOUR_FLAG 0x0040 /* Game wants to use colours - V5+ */ +#define SOUND_FLAG 0x0080 /* Game wants to use sound effects - V5+ */ +#define MENU_FLAG 0x0100 /* Game wants to use menus - V6 */ + +#define INTERP_DEC_20 1 +#define INTERP_APPLE_IIE 2 +#define INTERP_MACINTOSH 3 +#define INTERP_AMIGA 4 +#define INTERP_ATARI_ST 5 +#define INTERP_MSDOS 6 +#define INTERP_CBM_128 7 +#define INTERP_CBM_64 8 +#define INTERP_APPLE_IIC 9 +#define INTERP_APPLE_IIGS 10 +#define INTERP_TANDY 11 + +#define BLACK_COLOUR 2 +#define RED_COLOUR 3 +#define GREEN_COLOUR 4 +#define YELLOW_COLOUR 5 +#define BLUE_COLOUR 6 +#define MAGENTA_COLOUR 7 +#define CYAN_COLOUR 8 +#define WHITE_COLOUR 9 +#define GREY_COLOUR 10 /* INTERP_MSDOS only */ +#define LIGHTGREY_COLOUR 10 /* INTERP_AMIGA only */ +#define MEDIUMGREY_COLOUR 11 /* INTERP_AMIGA only */ +#define DARKGREY_COLOUR 12 /* INTERP_AMIGA only */ + +#define REVERSE_STYLE 1 +#define BOLDFACE_STYLE 2 +#define EMPHASIS_STYLE 4 +#define FIXED_WIDTH_STYLE 8 + +#define TEXT_FONT 1 +#define PICTURE_FONT 2 +#define GRAPHICS_FONT 3 +#define FIXED_WIDTH_FONT 4 + +/*** Constants for os_restart_game */ + +#define RESTART_BEGIN 0 +#define RESTART_WPROP_SET 1 +#define RESTART_END 2 + +/*** Character codes ***/ + +#define ZC_TIME_OUT 0x00 +#define ZC_NEW_STYLE 0x01 +#define ZC_NEW_FONT 0x02 +#define ZC_BACKSPACE 0x08 +#define ZC_INDENT 0x09 +#define ZC_GAP 0x0b +#define ZC_RETURN 0x0d +#define ZC_HKEY_MIN 0x0e +#define ZC_HKEY_RECORD 0x0e +#define ZC_HKEY_PLAYBACK 0x0f +#define ZC_HKEY_SEED 0x10 +#define ZC_HKEY_UNDO 0x11 +#define ZC_HKEY_RESTART 0x12 +#define ZC_HKEY_QUIT 0x13 +#define ZC_HKEY_DEBUG 0x14 +#define ZC_HKEY_HELP 0x15 +#define ZC_HKEY_MAX 0x15 +#define ZC_ESCAPE 0x1b +#define ZC_ASCII_MIN 0x20 +#define ZC_ASCII_MAX 0x7e +#define ZC_BAD 0x7f +#define ZC_ARROW_MIN 0x81 +#define ZC_ARROW_UP 0x81 +#define ZC_ARROW_DOWN 0x82 +#define ZC_ARROW_LEFT 0x83 +#define ZC_ARROW_RIGHT 0x84 +#define ZC_ARROW_MAX 0x84 +#define ZC_FKEY_MIN 0x85 +#define ZC_FKEY_MAX 0x90 +#define ZC_NUMPAD_MIN 0x91 +#define ZC_NUMPAD_MAX 0x9a +#define ZC_SINGLE_CLICK 0x9b +#define ZC_DOUBLE_CLICK 0x9c +#define ZC_MENU_CLICK 0x9d +#define ZC_LATIN1_MIN 0xa0 +#define ZC_LATIN1_MAX 0xff + +/*** File types ***/ + +#define FILE_RESTORE 0 +#define FILE_SAVE 1 +#define FILE_SCRIPT 2 +#define FILE_PLAYBACK 3 +#define FILE_RECORD 4 +#define FILE_LOAD_AUX 5 +#define FILE_SAVE_AUX 6 + +/*** Data access macros ***/ + +#define SET_BYTE(addr,v) { zmp[addr] = v; } +#define LOW_BYTE(addr,v) { v = zmp[addr]; } +#define CODE_BYTE(v) { v = *pcp++; } + +#if defined (AMIGA) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) ((zbyte *)&v)[1] +#define hi(v) ((zbyte *)&v)[0] + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define HIGH_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define CODE_WORD(v) { hi(v) = *pcp++; lo(v) = *pcp++; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +#if defined (__MSDOS__) + +extern zbyte far *pcp; +extern zbyte far *zmp; + +#define lo(v) ((zbyte *)&v)[0] +#define hi(v) ((zbyte *)&v)[1] + +#define SET_WORD(addr,v) asm {\ + les bx,zmp;\ + add bx,addr;\ + mov ax,v;\ + xchg al,ah;\ + mov es:[bx],ax } + +#define LOW_WORD(addr,v) asm {\ + les bx,zmp;\ + add bx,addr;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax } + +#define HIGH_WORD(addr,v) asm {\ + mov bx,word ptr zmp;\ + add bx,word ptr addr;\ + mov al,bh;\ + mov bh,0;\ + mov ah,0;\ + adc ah,byte ptr addr+2;\ + mov cl,4;\ + shl ax,cl;\ + add ax,word ptr zmp+2;\ + mov es,ax;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax } + +#define CODE_WORD(v) asm {\ + les bx,pcp;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax;\ + add word ptr pcp,2 } + +#define GET_PC(v) asm {\ + mov bx,word ptr pcp+2;\ + sub bx,word ptr zmp+2;\ + mov ax,bx;\ + mov cl,4;\ + shl bx,cl;\ + mov cl,12;\ + shr ax,cl;\ + add bx,word ptr pcp;\ + adc al,0;\ + sub bx,word ptr zmp;\ + sbb al,0;\ + mov word ptr v,bx;\ + mov word ptr v+2,ax } + +#define SET_PC(v) asm {\ + mov bx,word ptr zmp;\ + add bx,word ptr v;\ + mov al,bh;\ + mov bh,0;\ + mov ah,0;\ + adc ah,byte ptr v+2;\ + mov cl,4;\ + shl ax,cl;\ + add ax,word ptr zmp+2;\ + mov word ptr pcp,bx;\ + mov word ptr pcp+2,ax } + +#endif + +#if !defined (AMIGA) && !defined (__MSDOS__) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) (v & 0xff) +#define hi(v) (v >> 8) + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define HIGH_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define CODE_WORD(v) { v = ((zword) pcp[0] << 8) | pcp[1]; pcp += 2; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +/*** Story file header data ***/ + +extern zbyte h_version; +extern zbyte h_config; +extern zword h_release; +extern zword h_resident_size; +extern zword h_start_pc; +extern zword h_dictionary; +extern zword h_objects; +extern zword h_globals; +extern zword h_dynamic_size; +extern zword h_flags; +extern zbyte h_serial[6]; +extern zword h_abbreviations; +extern zword h_file_size; +extern zword h_checksum; +extern zbyte h_interpreter_number; +extern zbyte h_interpreter_version; +extern zbyte h_screen_rows; +extern zbyte h_screen_cols; +extern zword h_screen_width; +extern zword h_screen_height; +extern zbyte h_font_height; +extern zbyte h_font_width; +extern zword h_functions_offset; +extern zword h_strings_offset; +extern zbyte h_default_background; +extern zbyte h_default_foreground; +extern zword h_terminating_keys; +extern zword h_line_width; +extern zbyte h_standard_high; +extern zbyte h_standard_low; +extern zword h_alphabet; +extern zword h_extension_table; +extern zbyte h_user_name[8]; + +extern zword hx_table_size; +extern zword hx_mouse_x; +extern zword hx_mouse_y; +extern zword hx_unicode_table; + +/*** Various data ***/ + +extern const char *story_name; + +extern enum story story_id; +extern long story_size; + +extern zword stack[STACK_SIZE]; +extern zword *sp; +extern zword *fp; + +extern zword zargs[8]; +extern zargc; + +extern bool ostream_screen; +extern bool ostream_script; +extern bool ostream_memory; +extern bool ostream_record; +extern bool istream_replay; +extern bool message; + +extern cwin; +extern mwin; + +extern mouse_x; +extern mouse_y; + +extern bool enable_wrapping; +extern bool enable_scripting; +extern bool enable_scrolling; +extern bool enable_buffering; + +extern option_attribute_assignment; +extern option_attribute_testing; +extern option_object_locating; +extern option_object_movement; +extern option_context_lines; +extern option_left_margin; +extern option_right_margin; +extern option_ignore_errors; +extern option_piracy; +extern option_undo_slots; +extern option_expand_abbreviations; +extern option_script_cols; + +extern long reserve_mem; + +/*** Z-machine opcodes ***/ + +void z_add (void); +void z_and (void); +void z_art_shift (void); +void z_buffer_mode (void); +void z_call_n (void); +void z_call_s (void); +void z_catch (void); +void z_check_arg_count (void); +void z_check_unicode (void); +void z_clear_attr (void); +void z_copy_table (void); +void z_dec (void); +void z_dec_chk (void); +void z_div (void); +void z_draw_picture (void); +void z_encode_text (void); +void z_erase_line (void); +void z_erase_picture (void); +void z_erase_window (void); +void z_get_child (void); +void z_get_cursor (void); +void z_get_next_prop (void); +void z_get_parent (void); +void z_get_prop (void); +void z_get_prop_addr (void); +void z_get_prop_len (void); +void z_get_sibling (void); +void z_get_wind_prop (void); +void z_inc (void); +void z_inc_chk (void); +void z_input_stream (void); +void z_insert_obj (void); +void z_je (void); +void z_jg (void); +void z_jin (void); +void z_jl (void); +void z_jump (void); +void z_jz (void); +void z_load (void); +void z_loadb (void); +void z_loadw (void); +void z_log_shift (void); +void z_make_menu (void); +void z_mod (void); +void z_mouse_window (void); +void z_move_window (void); +void z_mul (void); +void z_new_line (void); +void z_nop (void); +void z_not (void); +void z_or (void); +void z_output_stream (void); +void z_picture_data (void); +void z_picture_table (void); +void z_piracy (void); +void z_pop (void); +void z_pop_stack (void); +void z_print (void); +void z_print_addr (void); +void z_print_char (void); +void z_print_form (void); +void z_print_num (void); +void z_print_obj (void); +void z_print_paddr (void); +void z_print_ret (void); +void z_print_table (void); +void z_print_unicode (void); +void z_pull (void); +void z_push (void); +void z_push_stack (void); +void z_put_prop (void); +void z_put_wind_prop (void); +void z_quit (void); +void z_random (void); +void z_read (void); +void z_read_char (void); +void z_read_mouse (void); +void z_remove_obj (void); +void z_restart (void); +void z_restore (void); +void z_restore_undo (void); +void z_ret (void); +void z_ret_popped (void); +void z_rfalse (void); +void z_rtrue (void); +void z_save (void); +void z_save_undo (void); +void z_scan_table (void); +void z_scroll_window (void); +void z_set_attr (void); +void z_set_font (void); +void z_set_colour (void); +void z_set_cursor (void); +void z_set_margins (void); +void z_set_window (void); +void z_set_text_style (void); +void z_show_status (void); +void z_sound_effect (void); +void z_split_window (void); +void z_store (void); +void z_storeb (void); +void z_storew (void); +void z_sub (void); +void z_test (void); +void z_test_attr (void); +void z_throw (void); +void z_tokenise (void); +void z_verify (void); +void z_window_size (void); +void z_window_style (void); + +/*** Various global functions ***/ + +zchar translate_from_zscii (zbyte); +zbyte translate_to_zscii (zchar); + +void flush_buffer (void); +void new_line (void); +void print_char (zchar); +void print_num (zword); +void print_object (zword); +void print_string (const char *); + +void stream_mssg_on (void); +void stream_mssg_off (void); + +void runtime_error (const char *); + +void ret (zword); +void store (zword); +void branch (bool); + +void storeb (zword, zbyte); +void storew (zword, zword); + +/*** Interface functions ***/ + +void os_beep (int); +int os_char_width (zchar); +void os_display_char (zchar); +void os_display_string (const zchar *); +void os_draw_picture (int, int, int); +void os_erase_area (int, int, int, int); +void os_fatal (const char *); +void os_finish_with_sample (void); +int os_font_data (int, int *, int *); +void os_init_screen (void); +void os_more_prompt (void); +int os_peek_colour (void); +int os_picture_data (int, int *, int *); +void os_prepare_sample (int); +void os_process_arguments (int, char *[]); +int os_random_seed (void); +int os_read_file_name (char *, const char *, int); +zchar os_read_key (int, int); +zchar os_read_line (int, zchar *, int, int, int); +void os_reset_screen (void); +void os_restart_game (int); +void os_scroll_area (int, int, int, int, int); +void os_set_colour (int, int); +void os_set_cursor (int, int); +void os_set_font (int); +void os_set_text_style (int); +void os_start_sample (int, int, int); +void os_stop_sample (void); +int os_string_width (const zchar *); diff --git a/getopt.c b/getopt.c new file mode 100644 index 0000000..c1e40e1 --- /dev/null +++ b/getopt.c @@ -0,0 +1,69 @@ +/* + * getopt.c + * + * Replacement for a Unix style getopt function + * + */ + +#include +#include + +#ifndef __MSDOS__ +#define cdecl +#endif + +int optind = 1; +int optopt = 0; + +const char *optarg = NULL; + +int cdecl getopt (int argc, char *argv[], const char *options) +{ + static pos = 1; + + const char *p; + + if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == 0) + return EOF; + + optopt = argv[optind][pos++]; + optarg = NULL; + + if (argv[optind][pos] == 0) + { pos = 1; optind++; } + + p = strchr (options, optopt); + + if (optopt == ':' || p == NULL) { + + fputs ("illegal option -- ", stderr); + goto error; + + } else if (p[1] == ':') + + if (optind >= argc) { + + fputs ("option requires an argument -- ", stderr); + goto error; + + } else { + + optarg = argv[optind]; + + if (pos != 1) + optarg += pos; + + pos = 1; optind++; + + } + + return optopt; + +error: + + fputc (optopt, stderr); + fputc ('\n', stderr); + + return '?'; + +}/* getopt */ diff --git a/hotkey.c b/hotkey.c new file mode 100644 index 0000000..56bc413 --- /dev/null +++ b/hotkey.c @@ -0,0 +1,242 @@ +/* + * hotkey.c + * + * Hot key functions + * + */ + +#include "frotz.h" + +extern int restore_undo (void); + +extern int read_number (void); + +extern bool read_yes_or_no (const char *); + +extern void replay_open (void); +extern void replay_close (void); +extern void record_open (void); +extern void record_close (void); + +extern void seed_random (int); + +/* + * hot_key_debugging + * + * ...allows user to toggle cheating options on/off. + * + */ + +static bool hot_key_debugging (void) +{ + + print_string ("Debugging options\n"); + + option_attribute_assignment = read_yes_or_no ("Watch attribute assignment"); + option_attribute_testing = read_yes_or_no ("Watch attribute testing"); + option_object_movement = read_yes_or_no ("Watch object movement"); + option_object_locating = read_yes_or_no ("Watch object locating"); + + return FALSE; + +}/* hot_key_debugging */ + +/* + * hot_key_help + * + * ...displays a list of all hot keys. + * + */ + +static bool hot_key_help (void) { + + print_string ("Help\n"); + + print_string ( + "\n" + "Alt-D debugging options\n" + "Alt-H help\n" + "Alt-N new game\n" + "Alt-P playback on\n" + "Alt-R recording on/off\n" + "Alt-S seed random numbers\n" + "Alt-U undo one turn\n" + "Alt-X exit game\n"); + + return FALSE; + +}/* hot_key_help */ + +/* + * hot_key_playback + * + * ...allows user to turn playback on. + * + */ + +static bool hot_key_playback (void) +{ + + print_string ("Playback on\n"); + + if (!istream_replay) + replay_open (); + + return FALSE; + +}/* hot_key_playback */ + +/* + * hot_key_recording + * + * ...allows user to turn recording on/off. + * + */ + +static bool hot_key_recording (void) +{ + + if (istream_replay) { + print_string ("Playback off\n"); + replay_close (); + } else if (ostream_record) { + print_string ("Recording off\n"); + record_close (); + } else { + print_string ("Recording on\n"); + record_open (); + } + + return FALSE; + +}/* hot_key_recording */ + +/* + * hot_key_seed + * + * ...allows user to seed the random number seed. + * + */ + +static bool hot_key_seed (void) +{ + + print_string ("Seed random numbers\n"); + + print_string ("Enter seed value (or return to randomize): "); + seed_random (read_number ()); + + return FALSE; + +}/* hot_key_seed */ + +/* + * hot_key_undo + * + * ...allows user to undo the previous turn. + * + */ + +static bool hot_key_undo (void) +{ + + print_string ("Undo one turn\n"); + + if (restore_undo ()) { + + if (h_version >= V5) { /* for V5+ games we must */ + store (2); /* store 2 (for success) */ + return TRUE; /* and abort the input */ + } + + if (h_version <= V3) { /* for V3- games we must */ + z_show_status (); /* draw the status line */ + return FALSE; /* and continue input */ + } + + } else print_string ("No more undo information available.\n"); + + return FALSE; + +}/* hot_key_undo */ + +/* + * hot_key_restart + * + * ...allows user to start a new game. + * + */ + +static bool hot_key_restart (void) +{ + + print_string ("New game\n"); + + if (read_yes_or_no ("Do you wish to restart")) { + + z_restart (); + return TRUE; + + } else return FALSE; + +}/* hot_key_restart */ + +/* + * hot_key_quit + * + * ...allows user to exit the game. + * + */ + +static bool hot_key_quit (void) +{ + + print_string ("Exit game\n"); + + if (read_yes_or_no ("Do you wish to quit")) { + + z_quit (); + return TRUE; + + } else return FALSE; + +}/* hot_key_quit */ + +/* + * handle_hot_key + * + * Perform the action associated with a so-called hot key. Return + * true to abort the current input action. + * + */ + +bool handle_hot_key (zchar key) +{ + + if (cwin == 0) { + + bool aborting; + + print_string ("\nHot key -- "); + + switch (key) { + case ZC_HKEY_RECORD: aborting = hot_key_recording (); break; + case ZC_HKEY_PLAYBACK: aborting = hot_key_playback (); break; + case ZC_HKEY_SEED: aborting = hot_key_seed (); break; + case ZC_HKEY_UNDO: aborting = hot_key_undo (); break; + case ZC_HKEY_RESTART: aborting = hot_key_restart (); break; + case ZC_HKEY_QUIT: aborting = hot_key_quit (); break; + case ZC_HKEY_DEBUG: aborting = hot_key_debugging (); break; + case ZC_HKEY_HELP: aborting = hot_key_help (); break; + } + + if (aborting) + return TRUE; + + print_string ("\nContinue input...\n"); + + } + + return FALSE; + +}/* handle_hot_key */ diff --git a/input.c b/input.c new file mode 100644 index 0000000..0642aef --- /dev/null +++ b/input.c @@ -0,0 +1,303 @@ +/* + * input.c + * + * High level input functions + * + */ + +#include "frotz.h" + +extern int save_undo (void); + +extern zchar stream_read_key (zword, zword, bool); +extern zchar stream_read_input (int, zchar *, zword, zword, bool, bool); + +extern void tokenise_line (zword, zword, zword, bool); + +/* + * is_terminator + * + * Check if the given key is an input terminator. + * + */ + +bool is_terminator (zchar key) +{ + + if (key == ZC_TIME_OUT) + return TRUE; + if (key == ZC_RETURN) + return TRUE; + if (key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) + return TRUE; + + if (h_terminating_keys != 0) + + if (key >= ZC_ARROW_MIN && key <= ZC_MENU_CLICK) { + + zword addr = h_terminating_keys; + zbyte c; + + do { + LOW_BYTE (addr, c) + if (c == 255 || key == translate_from_zscii (c)) + return TRUE; + addr++; + } while (c != 0); + + } + + return FALSE; + +}/* is_terminator */ + +/* + * z_make_menu, add or remove a menu and branch if successful. + * + * zargs[0] = number of menu + * zargs[1] = table of menu entries or 0 to remove menu + * + */ + +void z_make_menu (void) +{ + + /* This opcode was only used for the Macintosh version of Journey. + It controls menus with numbers greater than 2 (menus 0, 1 and 2 + are system menus). Frotz doesn't implement menus yet. */ + + branch (FALSE); + +}/* z_make_menu */ + +/* + * read_yes_or_no + * + * Ask the user a question; return true if the answer is yes. + * + */ + +bool read_yes_or_no (const char *s) +{ + zchar key; + + print_string (s); + print_string ("? (y/n) >"); + + key = stream_read_key (0, 0, FALSE); + + if (key == 'y' || key == 'Y') { + print_string ("y\n"); + return TRUE; + } else { + print_string ("n\n"); + return FALSE; + } + +}/* read_yes_or_no */ + +/* + * read_string + * + * Read a string from the current input stream. + * + */ + +void read_string (int max, zchar *buffer) +{ + zchar key; + + buffer[0] = 0; + + do { + + key = stream_read_input (max, buffer, 0, 0, FALSE, FALSE); + + } while (key != ZC_RETURN); + +}/* read_string */ + +/* + * read_number + * + * Ask the user to type in a number and return it. + * + */ + +int read_number (void) +{ + zchar buffer[6]; + int value = 0; + int i; + + read_string (5, buffer); + + for (i = 0; buffer[i] != 0; i++) + if (buffer[i] >= '0' && buffer[i] <= '9') + value = 10 * value + buffer[i] - '0'; + + return value; + +}/* read_number */ + +/* + * z_read, read a line of input and (in V5+) store the terminating key. + * + * zargs[0] = address of text buffer + * zargs[1] = address of token buffer + * zargs[2] = timeout in tenths of a second (optional) + * zargs[3] = packed address of routine to be called on timeout + * + */ + +void z_read (void) +{ + zchar buffer[INPUT_BUFFER_SIZE]; + zword addr; + zchar key; + zbyte max, size; + zbyte c; + int i; + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Get maximum input size */ + + addr = zargs[0]; + + LOW_BYTE (addr, max) + + if (h_version <= V4) + max--; + + if (max >= INPUT_BUFFER_SIZE) + max = INPUT_BUFFER_SIZE - 1; + + /* Get initial input size */ + + if (h_version >= V5) { + addr++; + LOW_BYTE (addr, size) + } else size = 0; + + /* Copy initial input to local buffer */ + + for (i = 0; i < size; i++) { + addr++; + LOW_BYTE (addr, c) + buffer[i] = translate_from_zscii (c); + } + + buffer[i] = 0; + + /* Draw status line for V1 to V3 games */ + + if (h_version <= V3) + z_show_status (); + + /* Read input from current input stream */ + + key = stream_read_input ( + max, buffer, /* buffer and size */ + zargs[2], /* timeout value */ + zargs[3], /* timeout routine */ + TRUE, /* enable hot keys */ + h_version == V6); /* no script in V6 */ + + if (key == ZC_BAD) + return; + + /* Perform save_undo for V1 to V4 games */ + + if (h_version <= V4) + save_undo (); + + /* Copy local buffer back to dynamic memory */ + + for (i = 0; buffer[i] != 0; i++) { + + if (key == ZC_RETURN) { + + if (buffer[i] >= 'A' && buffer[i] <= 'Z') + buffer[i] += 'a' - 'A'; + if (buffer[i] >= 0xc0 && buffer[i] <= 0xde && buffer[i] != 0xd7) + buffer[i] += 0x20; + + } + + storeb ((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i])); + + } + + /* Add null character (V1-V4) or write input length into 2nd byte */ + + if (h_version <= V4) + storeb ((zword) (zargs[0] + 1 + i), 0); + else + storeb ((zword) (zargs[0] + 1), i); + + /* Tokenise line if a token buffer is present */ + + if (key == ZC_RETURN && zargs[1] != 0) + tokenise_line (zargs[0], zargs[1], 0, FALSE); + + /* Store key */ + + if (h_version >= V5) + store (translate_to_zscii (key)); + +}/* z_read */ + +/* + * z_read_char, read and store a key. + * + * zargs[0] = input device (must be 1) + * zargs[1] = timeout in tenths of a second (optional) + * zargs[2] = packed address of routine to be called on timeout + * + */ + +void z_read_char (void) +{ + zchar key; + + /* Supply default arguments */ + + if (zargc < 2) + zargs[1] = 0; + + /* Read input from the current input stream */ + + key = stream_read_key ( + zargs[1], /* timeout value */ + zargs[2], /* timeout routine */ + TRUE); /* enable hot keys */ + + if (key == ZC_BAD) + return; + + /* Store key */ + + store (translate_to_zscii (key)); + +}/* z_read_char */ + +/* + * z_read_mouse, write the current mouse status into a table. + * + * zargs[0] = address of table + * + */ + +void z_read_mouse (void) +{ + + storew ((zword) (zargs[0] + 0), hx_mouse_y); + storew ((zword) (zargs[0] + 2), hx_mouse_x); + storew ((zword) (zargs[0] + 4), 1); /* mouse button bits */ + storew ((zword) (zargs[0] + 6), 0); /* menu selection */ + +}/* z_read_mouse */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..9a8e9ba --- /dev/null +++ b/main.c @@ -0,0 +1,181 @@ +/* + * main.c + * + * Frotz V2.32 main function + * + * This is an interpreter for Infocom V1 to V6 games. It also supports + * the recently defined V7 and V8 games. Please report bugs to + * + * s.jokisch@avu.de + * + * Frotz is freeware. It may be used and distributed freely provided + * no commercial profit is involved. (c) 1995-1997 Stefan Jokisch + * + */ + +#include "frotz.h" + +#ifndef __MSDOS__ +#define cdecl +#endif + +extern void interpret (void); +extern void init_memory (void); +extern void init_undo (void); +extern void reset_memory (void); + +/* Story file name, id number and size */ + +const char *story_name = 0; + +enum story story_id = UNKNOWN; +long story_size = 0; + +/* Story file header data */ + +zbyte h_version = 0; +zbyte h_config = 0; +zword h_release = 0; +zword h_resident_size = 0; +zword h_start_pc = 0; +zword h_dictionary = 0; +zword h_objects = 0; +zword h_globals = 0; +zword h_dynamic_size = 0; +zword h_flags = 0; +zbyte h_serial[6] = { 0, 0, 0, 0, 0, 0 }; +zword h_abbreviations = 0; +zword h_file_size = 0; +zword h_checksum = 0; +zbyte h_interpreter_number = 0; +zbyte h_interpreter_version = 0; +zbyte h_screen_rows = 0; +zbyte h_screen_cols = 0; +zword h_screen_width = 0; +zword h_screen_height = 0; +zbyte h_font_height = 1; +zbyte h_font_width = 1; +zword h_functions_offset = 0; +zword h_strings_offset = 0; +zbyte h_default_background = 0; +zbyte h_default_foreground = 0; +zword h_terminating_keys = 0; +zword h_line_width = 0; +zbyte h_standard_high = 1; +zbyte h_standard_low = 0; +zword h_alphabet = 0; +zword h_extension_table = 0; +zbyte h_user_name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +zword hx_table_size = 0; +zword hx_mouse_x = 0; +zword hx_mouse_y = 0; +zword hx_unicode_table = 0; + +/* Stack data */ + +zword stack[STACK_SIZE]; +zword *sp = 0; +zword *fp = 0; + +/* IO streams */ + +bool ostream_screen = TRUE; +bool ostream_script = FALSE; +bool ostream_memory = FALSE; +bool ostream_record = FALSE; +bool istream_replay = FALSE; +bool message = FALSE; + +/* Current window and mouse data */ + +int cwin = 0; +int mwin = 0; + +int mouse_y = 0; +int mouse_x = 0; + +/* Window attributes */ + +bool enable_wrapping = FALSE; +bool enable_scripting = FALSE; +bool enable_scrolling = FALSE; +bool enable_buffering = FALSE; + +/* User options */ + +int option_attribute_assignment = 0; +int option_attribute_testing = 0; +int option_context_lines = 0; +int option_object_locating = 0; +int option_object_movement = 0; +int option_left_margin = 0; +int option_right_margin = 0; +int option_ignore_errors = 0; +int option_piracy = 0; +int option_undo_slots = MAX_UNDO_SLOTS; +int option_expand_abbreviations = 0; +int option_script_cols = 80; + +/* Size of memory to reserve (in bytes) */ + +long reserve_mem = 0; + +/* + * runtime_error + * + * An error has occured. Ignore it or pass it to os_fatal. + * + */ + +void runtime_error (const char *s) +{ + + if (!option_ignore_errors) + { flush_buffer (); os_fatal (s); } + +}/* runtime_error */ + +/* + * z_piracy, branch if the story file is a legal copy. + * + * no zargs used + * + */ + +void z_piracy (void) +{ + + branch (!option_piracy); + +}/* z_piracy */ + +/* + * main + * + * Prepare and run the game. + * + */ + +int cdecl main (int argc, char *argv[]) +{ + + os_process_arguments (argc, argv); + + init_memory (); + + os_init_screen (); + + init_undo (); + + z_restart (); + + interpret (); + + reset_memory (); + + os_reset_screen (); + + return 0; + +}/* main */ diff --git a/math.c b/math.c new file mode 100644 index 0000000..20ae247 --- /dev/null +++ b/math.c @@ -0,0 +1,248 @@ +/* + * math.c + * + * Arithmetic, compare and logical opcodes + * + */ + +#include "frotz.h" + +/* + * z_add, 16bit addition. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_add (void) +{ + + store ((zword) ((short) zargs[0] + (short) zargs[1])); + +}/* z_add */ + +/* + * z_and, bitwise AND operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_and (void) +{ + + store ((zword) (zargs[0] & zargs[1])); + +}/* z_and */ + +/* + * z_art_shift, arithmetic SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right + * + */ + +void z_art_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) ((short) zargs[0] << (short) zargs[1])); + else + store ((zword) ((short) zargs[0] >> - (short) zargs[1])); + +}/* z_art_shift */ + +/* + * z_div, signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_div (void) +{ + + if (zargs[1] == 0) + runtime_error ("Division by zero"); + + store ((zword) ((short) zargs[0] / (short) zargs[1])); + +}/* z_div */ + +/* + * z_je, branch if the first value equals any of the following. + * + * zargs[0] = first value + * zargs[1] = second value (optional) + * ... + * zargs[3] = fourth value (optional) + * + */ + +void z_je (void) +{ + + branch ( + zargc > 1 && (zargs[0] == zargs[1] || ( + zargc > 2 && (zargs[0] == zargs[2] || ( + zargc > 3 && (zargs[0] == zargs[3])))))); + +}/* z_je */ + +/* + * z_jg, branch if the first value is greater than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jg (void) +{ + + branch ((short) zargs[0] > (short) zargs[1]); + +}/* z_jg */ + +/* + * z_jl, branch if the first value is less than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jl (void) +{ + + branch ((short) zargs[0] < (short) zargs[1]); + +}/* z_jl */ + +/* + * z_jz, branch if value is zero. + * + * zargs[0] = value + * + */ + +void z_jz (void) +{ + + branch ((short) zargs[0] == 0); + +}/* z_jz */ + +/* + * z_log_shift, logical SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right (negative) + * + */ + +void z_log_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) (zargs[0] << (short) zargs[1])); + else + store ((zword) (zargs[0] >> - (short) zargs[1])); + +}/* z_log_shift */ + +/* + * z_mod, remainder after signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mod (void) +{ + + if (zargs[1] == 0) + runtime_error ("Division by zero"); + + store ((zword) ((short) zargs[0] % (short) zargs[1])); + +}/* z_mod */ + +/* + * z_mul, 16bit multiplication. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mul (void) +{ + + store ((zword) ((short) zargs[0] * (short) zargs[1])); + +}/* z_mul */ + +/* + * z_not, bitwise NOT operation. + * + * zargs[0] = value + * + */ + +void z_not (void) +{ + + store ((zword) ~zargs[0]); + +}/* z_not */ + +/* + * z_or, bitwise OR operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_or (void) +{ + + store ((zword) (zargs[0] | zargs[1])); + +}/* z_or */ + +/* + * z_sub, 16bit substraction. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_sub (void) +{ + + store ((zword) ((short) zargs[0] - (short) zargs[1])); + +}/* z_sub */ + +/* + * z_test, branch if all the flags of a bit mask are set in a value. + * + * zargs[0] = value to be examined + * zargs[1] = bit mask + * + */ + +void z_test (void) +{ + + branch ((zargs[0] & zargs[1]) == zargs[1]); + +}/* z_test */ diff --git a/object.c b/object.c new file mode 100644 index 0000000..c6c79e5 --- /dev/null +++ b/object.c @@ -0,0 +1,904 @@ +/* + * object.c + * + * Object manipulation opcodes + * + */ + +#include "frotz.h" + +#define MAX_OBJECT 2000 + +#define O1_PARENT 4 +#define O1_SIBLING 5 +#define O1_CHILD 6 +#define O1_PROPERTY_OFFSET 7 +#define O1_SIZE 9 + +#define O4_PARENT 6 +#define O4_SIBLING 8 +#define O4_CHILD 10 +#define O4_PROPERTY_OFFSET 12 +#define O4_SIZE 14 + +/* + * object_address + * + * Calculate the address of an object. + * + */ + +static zword object_address (zword obj) +{ + + /* Check object number */ + + if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) + runtime_error ("Illegal object"); + + /* Return object address */ + + if (h_version <= V3) + return h_objects + ((obj - 1) * O1_SIZE + 62); + else + return h_objects + ((obj - 1) * O4_SIZE + 126); + +}/* object_address */ + +/* + * object_name + * + * Return the address of the given object's name. + * + */ + +zword object_name (zword object) +{ + zword obj_addr; + zword name_addr; + + obj_addr = object_address (object); + + /* The object name address is found at the start of the properties */ + + if (h_version <= V3) + obj_addr += O1_PROPERTY_OFFSET; + else + obj_addr += O4_PROPERTY_OFFSET; + + LOW_WORD (obj_addr, name_addr) + + return name_addr; + +}/* object_name */ + +/* + * first_property + * + * Calculate the start address of the property list associated with + * an object. + * + */ + +static zword first_property (zword obj) +{ + zword prop_addr; + zbyte size; + + /* Fetch address of object name */ + + prop_addr = object_name (obj); + + /* Get length of object name */ + + LOW_BYTE (prop_addr, size) + + /* Add name length to pointer */ + + return prop_addr + 1 + 2 * size; + +}/* first_property */ + +/* + * next_property + * + * Calculate the address of the next property in a property list. + * + */ + +static zword next_property (zword prop_addr) +{ + zbyte value; + + /* Load the current property id */ + + LOW_BYTE (prop_addr, value) + prop_addr++; + + /* Calculate the length of this property */ + + if (h_version <= V3) + value >>= 5; + else if (!(value & 0x80)) + value >>= 6; + else { + + LOW_BYTE (prop_addr, value) + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Add property length to current property pointer */ + + return prop_addr + value + 1; + +}/* next_property */ + +/* + * unlink_object + * + * Unlink an object from its parent and siblings. + * + */ + +static void unlink_object (zword object) +{ + zword obj_addr; + zword parent_addr; + zword sibling_addr; + + obj_addr = object_address (object); + + if (h_version <= V3) { + + zbyte parent; + zbyte younger_sibling; + zbyte older_sibling; + zbyte zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_BYTE (obj_addr, zero) + obj_addr += O1_SIBLING - O1_PARENT; + LOW_BYTE (obj_addr, older_sibling) + SET_BYTE (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O1_CHILD; + LOW_BYTE (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_BYTE (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O1_SIBLING; + LOW_BYTE (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_BYTE (sibling_addr, older_sibling) + } + + } else { + + zword parent; + zword younger_sibling; + zword older_sibling; + zword zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_WORD (obj_addr, zero) + obj_addr += O4_SIBLING - O4_PARENT; + LOW_WORD (obj_addr, older_sibling) + SET_WORD (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O4_CHILD; + LOW_WORD (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_WORD (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O4_SIBLING; + LOW_WORD (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_WORD (sibling_addr, older_sibling) + } + + } + +}/* unlink_object */ + +/* + * z_clear_attr, clear an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to be cleared + * + */ + +void z_clear_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute assignment display a short note */ + + if (option_attribute_assignment) { + stream_mssg_on (); + print_string ("@clear_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Clear attribute bit */ + + LOW_BYTE (obj_addr, value) + value &= ~(0x80 >> (zargs[1] & 7)); + SET_BYTE (obj_addr, value) + +}/* z_clear_attr */ + +/* + * z_jin, branch if the first object is inside the second. + * + * zargs[0] = first object + * zargs[1] = second object + * + */ + +void z_jin (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@jin "); + print_object (zargs[0]); + print_string (" "); + print_object (zargs[1]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } + +}/* z_jin */ + +/* + * z_get_child, store the child of an object. + * + * zargs[0] = object + * + */ + +void z_get_child (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@get_child "); + print_object (zargs[0]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte child; + + /* Get child id from object */ + + obj_addr += O1_CHILD; + LOW_BYTE (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } else { + + zword child; + + /* Get child id from object */ + + obj_addr += O4_CHILD; + LOW_WORD (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } + +}/* z_get_child */ + +/* + * z_get_next_prop, store the number of the first or next property. + * + * zargs[0] = object + * zargs[1] = address of current property (0 gets the first property) + * + */ + +void z_get_next_prop (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + if (zargs[1] != 0) { + + /* Scan down the property list */ + + do { + LOW_BYTE (prop_addr, value) + prop_addr = next_property (prop_addr); + } while ((value & mask) > zargs[1]); + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error ("No such property"); + + } + + /* Return the property id */ + + LOW_BYTE (prop_addr, value) + store ((zword) (value & mask)); + +}/* z_get_next_prop */ + +/* + * z_get_parent, store the parent of an object. + * + * zargs[0] = object + * + */ + +void z_get_parent (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@get_parent "); + print_object (zargs[0]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } + +}/* z_get_parent */ + +/* + * z_get_prop, store the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop (void) +{ + zword prop_addr; + zword wprop_val; + zbyte bprop_val; + zbyte value; + zbyte mask; + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + if ((value & mask) == zargs[1]) { /* property found */ + + /* Load property (byte or word sized) */ + + prop_addr++; + + if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) { + + LOW_BYTE (prop_addr, bprop_val) + wprop_val = bprop_val; + + } else LOW_WORD (prop_addr, wprop_val) + + } else { /* property not found */ + + /* Load default value */ + + prop_addr = h_objects + 2 * (zargs[1] - 1); + LOW_WORD (prop_addr, wprop_val) + + } + + /* Store the property value */ + + store (wprop_val); + +}/* z_get_prop */ + +/* + * z_get_prop_addr, store the address of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop_addr (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (story_id == BEYOND_ZORK) + if (zargs[0] > MAX_OBJECT) + { store (0); return; } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Calculate the property address or return zero */ + + if ((value & mask) == zargs[1]) { + + if (h_version >= V4 && (value & 0x80)) + prop_addr++; + store ((zword) (prop_addr + 1)); + + } else store (0); + +}/* z_get_prop_addr */ + +/* + * z_get_prop_len, store the length of an object property. + * + * zargs[0] = address of property to be examined + * + */ + +void z_get_prop_len (void) +{ + zword addr; + zbyte value; + + /* Back up the property pointer to the property id */ + + addr = zargs[0] - 1; + LOW_BYTE (addr, value) + + /* Calculate length of property */ + + if (h_version <= V3) + value = (value >> 5) + 1; + else if (!(value & 0x80)) + value = (value >> 6) + 1; + else { + + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Store length of property */ + + store (value); + +}/* z_get_prop_len */ + +/* + * z_get_sibling, store the sibling of an object. + * + * zargs[0] = object + * + */ + +void z_get_sibling (void) +{ + zword obj_addr; + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte sibling; + + /* Get sibling id from object */ + + obj_addr += O1_SIBLING; + LOW_BYTE (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } else { + + zword sibling; + + /* Get sibling id from object */ + + obj_addr += O4_SIBLING; + LOW_WORD (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } + +}/* z_get_sibling */ + +/* + * z_insert_obj, make an object the first child of another object. + * + * zargs[0] = object to be moved + * zargs[1] = destination object + * + */ + +void z_insert_obj (void) +{ + zword obj1 = zargs[0]; + zword obj2 = zargs[1]; + zword obj1_addr; + zword obj2_addr; + + /* If we are monitoring object movements display a short note */ + + if (option_object_movement) { + stream_mssg_on (); + print_string ("@move_obj "); + print_object (obj1); + print_string (" "); + print_object (obj2); + stream_mssg_off (); + } + + /* Get addresses of both objects */ + + obj1_addr = object_address (obj1); + obj2_addr = object_address (obj2); + + /* Remove object 1 from current parent */ + + unlink_object (obj1); + + /* Make object 1 first child of object 2 */ + + if (h_version <= V3) { + + zbyte child; + + obj1_addr += O1_PARENT; + SET_BYTE (obj1_addr, obj2) + obj2_addr += O1_CHILD; + LOW_BYTE (obj2_addr, child) + SET_BYTE (obj2_addr, obj1) + obj1_addr += O1_SIBLING - O1_PARENT; + SET_BYTE (obj1_addr, child) + + } else { + + zword child; + + obj1_addr += O4_PARENT; + SET_WORD (obj1_addr, obj2) + obj2_addr += O4_CHILD; + LOW_WORD (obj2_addr, child) + SET_WORD (obj2_addr, obj1) + obj1_addr += O4_SIBLING - O4_PARENT; + SET_WORD (obj1_addr, child) + + } + +}/* z_insert_obj */ + +/* + * z_put_prop, set the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to set + * zargs[2] = value to set property to + * + */ + +void z_put_prop (void) +{ + zword prop_addr; + zword value; + zbyte mask; + + /* Property id is in bottom five or six bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error ("No such property"); + + /* Store the new property value (byte or word sized) */ + + prop_addr++; + + if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) { + zbyte v = zargs[2]; + SET_BYTE (prop_addr, v) + } else { + zword v = zargs[2]; + SET_WORD (prop_addr, v) + } + +}/* z_put_prop */ + +/* + * z_remove_obj, unlink an object from its parent and siblings. + * + * zargs[0] = object + * + */ + +void z_remove_obj (void) +{ + + /* If we are monitoring object movements display a short note */ + + if (option_object_movement) { + stream_mssg_on (); + print_string ("@remove_obj "); + print_object (zargs[0]); + stream_mssg_off (); + } + + /* Call unlink_object to do the job */ + + unlink_object (zargs[0]); + +}/* z_remove_obj */ + +/* + * z_set_attr, set an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to set + * + */ + +void z_set_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute assignment display a short note */ + + if (option_attribute_assignment) { + stream_mssg_on (); + print_string ("@set_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Set attribute bit */ + + value |= 0x80 >> (zargs[1] & 7); + + /* Store attribute byte */ + + SET_BYTE (obj_addr, value) + +}/* z_set_attr */ + +/* + * z_test_attr, branch if an object attribute is set. + * + * zargs[0] = object + * zargs[1] = number of attribute to test + * + */ + +void z_test_attr (void) +{ + zword obj_addr; + zbyte value; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute testing display a short note */ + + if (option_attribute_testing) { + stream_mssg_on (); + print_string ("@test_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Test attribute */ + + branch (value & (0x80 >> (zargs[1] & 7))); + +}/* z_test_attr */ diff --git a/process.c b/process.c new file mode 100644 index 0000000..dd42baa --- /dev/null +++ b/process.c @@ -0,0 +1,746 @@ +/* + * process.c + * + * Interpreter loop and program control + * + */ + +#include "frotz.h" + +zword zargs[8]; +int zargc; + +static finished = 0; + +static void __extended__ (void); +static void __illegal__ (void); + +void (*op0_opcodes[0x10]) (void) = { + z_rtrue, + z_rfalse, + z_print, + z_print_ret, + z_nop, + z_save, + z_restore, + z_restart, + z_ret_popped, + z_catch, + z_quit, + z_new_line, + z_show_status, + z_verify, + __extended__, + z_piracy +}; + +void (*op1_opcodes[0x10]) (void) = { + z_jz, + z_get_sibling, + z_get_child, + z_get_parent, + z_get_prop_len, + z_inc, + z_dec, + z_print_addr, + z_call_s, + z_remove_obj, + z_print_obj, + z_ret, + z_jump, + z_print_paddr, + z_load, + z_call_n +}; + +void (*var_opcodes[0x40]) (void) = { + __illegal__, + z_je, + z_jl, + z_jg, + z_dec_chk, + z_inc_chk, + z_jin, + z_test, + z_or, + z_and, + z_test_attr, + z_set_attr, + z_clear_attr, + z_store, + z_insert_obj, + z_loadw, + z_loadb, + z_get_prop, + z_get_prop_addr, + z_get_next_prop, + z_add, + z_sub, + z_mul, + z_div, + z_mod, + z_call_s, + z_call_n, + z_set_colour, + z_throw, + __illegal__, + __illegal__, + __illegal__, + z_call_s, + z_storew, + z_storeb, + z_put_prop, + z_read, + z_print_char, + z_print_num, + z_random, + z_push, + z_pull, + z_split_window, + z_set_window, + z_call_s, + z_erase_window, + z_erase_line, + z_set_cursor, + z_get_cursor, + z_set_text_style, + z_buffer_mode, + z_output_stream, + z_input_stream, + z_sound_effect, + z_read_char, + z_scan_table, + z_not, + z_call_n, + z_call_n, + z_tokenise, + z_encode_text, + z_copy_table, + z_print_table, + z_check_arg_count +}; + +void (*ext_opcodes[0x1d]) (void) = { + z_save, + z_restore, + z_log_shift, + z_art_shift, + z_set_font, + z_draw_picture, + z_picture_data, + z_erase_picture, + z_set_margins, + z_save_undo, + z_restore_undo, + z_print_unicode, + z_check_unicode, + __illegal__, + __illegal__, + __illegal__, + z_move_window, + z_window_size, + z_window_style, + z_get_wind_prop, + z_scroll_window, + z_pop_stack, + z_read_mouse, + z_mouse_window, + z_push_stack, + z_put_wind_prop, + z_print_form, + z_make_menu, + z_picture_table +}; + +/* + * load_operand + * + * Load an operand, either a variable or a constant. + * + */ + +static void load_operand (zbyte type) +{ + zword value; + + if (type & 2) { /* variable */ + + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + value = *sp++; + else if (variable < 16) + value = *(fp - variable); + else { + zword addr = h_globals + 2 * (variable - 16); + LOW_WORD (addr, value) + } + + } else if (type & 1) { /* small constant */ + + zbyte bvalue; + + CODE_BYTE (bvalue) + value = bvalue; + + } else CODE_WORD (value) /* large constant */ + + zargs[zargc++] = value; + +}/* load_operand */ + +/* + * load_all_operands + * + * Given the operand specifier byte, load all (up to four) operands + * for a VAR or EXT opcode. + * + */ + +static void load_all_operands (zbyte specifier) +{ + int i; + + for (i = 6; i >= 0; i -= 2) { + + zbyte type = (specifier >> i) & 0x03; + + if (type == 3) + break; + + load_operand (type); + + } + +}/* load_all_operands */ + +/* + * interpret + * + * Z-code interpreter main loop + * + */ + +void interpret (void) +{ + + do { + + zbyte opcode; + + CODE_BYTE (opcode) + + zargc = 0; + + if (opcode < 0x80) { /* 2OP opcodes */ + + load_operand ((zbyte) (opcode & 0x40) ? 2 : 1); + load_operand ((zbyte) (opcode & 0x20) ? 2 : 1); + + var_opcodes[opcode & 0x1f] (); + + } else if (opcode < 0xb0) { /* 1OP opcodes */ + + load_operand ((zbyte) (opcode >> 4)); + + op1_opcodes[opcode & 0x0f] (); + + } else if (opcode < 0xc0) { /* 0OP opcodes */ + + op0_opcodes[opcode - 0xb0] (); + + } else { /* VAR opcodes */ + + zbyte specifier1; + zbyte specifier2; + + if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ + CODE_BYTE (specifier1) /* and 0xfa are */ + CODE_BYTE (specifier2) /* call opcodes */ + load_all_operands (specifier1); /* with up to 8 */ + load_all_operands (specifier2); /* arguments */ + } else { + CODE_BYTE (specifier1) + load_all_operands (specifier1); + } + + var_opcodes[opcode - 0xc0] (); + + } + + } while (finished == 0); + + finished--; + +}/* interpret */ + +/* + * call + * + * Call a subroutine. Save PC and FP then load new PC and initialise + * new stack frame. Note that the caller may legally provide less or + * more arguments than the function actually has. The call type "ct" + * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). + * + */ + +void call (zword routine, int argc, zword *args, int ct) +{ + long pc; + zword value; + zbyte count; + int i; + + if (sp - stack < 4) + runtime_error ("Stack overflow"); + + GET_PC (pc) + + *--sp = (zword) (pc >> 9); /* for historical reasons */ + *--sp = (zword) (pc & 0x1ff); /* Frotz keeps its stack */ + *--sp = (zword) (fp - stack - 1); /* format compatible with */ + *--sp = (zword) (argc | (ct << 8)); /* Mark Howell's Zip */ + + fp = sp; + + /* Calculate byte address of routine */ + + if (h_version <= V3) + pc = (long) routine << 1; + else if (h_version <= V5) + pc = (long) routine << 2; + else if (h_version <= V7) + pc = ((long) routine << 2) + ((long) h_functions_offset << 3); + else /* h_version == V8 */ + pc = (long) routine << 3; + + if (pc >= story_size) + runtime_error ("Call to illegal address"); + + SET_PC (pc) + + /* Initialise local variables */ + + CODE_BYTE (count) + + if (count > 15) + runtime_error ("Call to non-routine"); + if (sp - stack < count) + runtime_error ("Stack overflow"); + + value = 0; + + for (i = 0; i < count; i++) { + + if (h_version <= V4) /* V1 to V4 games provide default */ + CODE_WORD (value) /* values for all local variables */ + + *--sp = (zword) ((argc-- > 0) ? args[i] : value); + + } + + /* Start main loop for direct calls */ + + if (ct == 2) + interpret (); + +}/* call */ + +/* + * ret + * + * Return from the current subroutine and restore the previous stack + * frame. The result may be stored (0), thrown away (1) or pushed on + * the stack (2). In the latter case a direct call has been finished + * and we must exit the interpreter loop. + * + */ + +void ret (zword value) +{ + long pc; + int ct; + + if (sp > fp) + runtime_error ("Stack underflow"); + + sp = fp; + + ct = *sp++ >> 8; + fp = stack + 1 + *sp++; + pc = *sp++; + pc = ((long) *sp++ << 9) | pc; + + SET_PC (pc) + + /* Handle resulting value */ + + if (ct == 0) + store (value); + if (ct == 2) + *--sp = value; + + /* Stop main loop for direct calls */ + + if (ct == 2) + finished++; + +}/* ret */ + +/* + * branch + * + * Take a jump after an instruction based on the flag, either true or + * false. The branch can be short or long; it is encoded in one or two + * bytes respectively. When bit 7 of the first byte is set, the jump + * takes place if the flag is true; otherwise it is taken if the flag + * is false. When bit 6 of the first byte is set, the branch is short; + * otherwise it is long. The offset occupies the bottom 6 bits of the + * first byte plus all the bits in the second byte for long branches. + * Uniquely, an offset of 0 means return false, and an offset of 1 is + * return true. + * + */ + +void branch (bool flag) +{ + long pc; + zword offset; + zbyte specifier; + zbyte off1; + zbyte off2; + + CODE_BYTE (specifier) + + off1 = specifier & 0x3f; + + if (!flag) + specifier ^= 0x80; + + if (!(specifier & 0x40)) { /* it's a long branch */ + + if (off1 & 0x20) /* propagate sign bit */ + off1 |= 0xc0; + + CODE_BYTE (off2) + + offset = (off1 << 8) | off2; + + } else offset = off1; /* it's a short branch */ + + if (specifier & 0x80) + + if (offset > 1) { /* normal branch */ + + GET_PC (pc) + pc += (short) offset - 2; + SET_PC (pc) + + } else ret (offset); /* special case, return 0 or 1 */ + +}/* branch */ + +/* + * store + * + * Store an operand, either as a variable or pushed on the stack. + * + */ + +void store (zword value) +{ + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + *--sp = value; + else if (variable < 16) + *(fp - variable) = value; + else { + zword addr = h_globals + 2 * (variable - 16); + SET_WORD (addr, value) + } + +}/* store */ + +/* + * direct_call + * + * Call the interpreter loop directly. This is necessary when + * + * - a sound effect has been finished + * - a read instruction has timed out + * - a newline countdown has hit zero + * + * The interpreter returns the result value on the stack. + * + */ + +int direct_call (zword addr) +{ + zword saved_zargs[8]; + int saved_zargc; + int i; + + /* Calls to address 0 return false */ + + if (addr == 0) + return 0; + + /* Save operands and operand count */ + + for (i = 0; i < 8; i++) + saved_zargs[i] = zargs[i]; + + saved_zargc = zargc; + + /* Call routine directly */ + + call (addr, 0, 0, 2); + + /* Restore operands and operand count */ + + for (i = 0; i < 8; i++) + zargs[i] = saved_zargs[i]; + + zargc = saved_zargc; + + /* Resulting value lies on top of the stack */ + + return (short) *sp++; + +}/* direct_call */ + +/* + * __extended__ + * + * Load and execute an extended opcode. + * + */ + +static void __extended__ (void) +{ + zbyte opcode; + zbyte specifier; + + CODE_BYTE (opcode) + CODE_BYTE (specifier) + + load_all_operands (specifier); + + if (opcode < 0x1d) /* extended opcodes from 0x1d on */ + ext_opcodes[opcode] (); /* are reserved for future spec' */ + +}/* __extended__ */ + +/* + * __illegal__ + * + * Exit game because an unknown opcode has been hit. + * + */ + +static void __illegal__ (void) +{ + + runtime_error ("Illegal opcode"); + +}/* __illegal__ */ + +/* + * z_catch, store the current stack frame for later use with z_throw. + * + * no zargs used + * + */ + +void z_catch (void) +{ + + store ((zword) (fp - stack)); + +}/* z_catch */ + +/* + * z_throw, go back to the given stack frame and return the given value. + * + * zargs[0] = value to return + * zargs[1] = stack frame + * + */ + +void z_throw (void) +{ + + if (zargs[1] > STACK_SIZE) + runtime_error ("Bad stack frame"); + + fp = stack + zargs[1]; + + ret (zargs[0]); + +}/* z_throw */ + +/* + * z_call_n, call a subroutine and discard its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_n (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 1); + +}/* z_call_n */ + +/* + * z_call_s, call a subroutine and store its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_s (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 0); + else + store (0); + +}/* z_call_s */ + +/* + * z_check_arg_count, branch if subroutine was called with >= n arg's. + * + * zargs[0] = number of arguments + * + */ + +void z_check_arg_count (void) +{ + + if (fp == stack + STACK_SIZE) + branch (zargs[0] == 0); + else + branch (zargs[0] <= (*fp & 0xff)); + +}/* z_check_arg_count */ + +/* + * z_jump, jump unconditionally to the given address. + * + * zargs[0] = PC relative address + * + */ + +void z_jump (void) +{ + long pc; + + GET_PC (pc) + + pc += (short) zargs[0] - 2; + + if (pc >= story_size) + runtime_error ("Jump to illegal address"); + + SET_PC (pc) + +}/* z_jump */ + +/* + * z_nop, no operation. + * + * no zargs used + * + */ + +void z_nop (void) +{ + + /* Do nothing */ + +}/* z_nop */ + +/* + * z_quit, stop game and exit interpreter. + * + * no zargs used + * + */ + +void z_quit (void) +{ + + finished = 9999; + +}/* z_quit */ + +/* + * z_ret, return from a subroutine with the given value. + * + * zargs[0] = value to return + * + */ + +void z_ret (void) +{ + + ret (zargs[0]); + +}/* z_ret */ + +/* + * z_ret_popped, return from a subroutine with a value popped off the stack. + * + * no zargs used + * + */ + +void z_ret_popped (void) +{ + + ret (*sp++); + +}/* z_ret_popped */ + +/* + * z_rfalse, return from a subroutine with false (0). + * + * no zargs used + * + */ + +void z_rfalse (void) +{ + + ret (0); + +}/* z_rfalse */ + +/* + * z_rtrue, return from a subroutine with true (1). + * + * no zargs used + * + */ + +void z_rtrue (void) +{ + + ret (1); + +}/* z_rtrue */ diff --git a/random.c b/random.c new file mode 100644 index 0000000..c2df1d1 --- /dev/null +++ b/random.c @@ -0,0 +1,69 @@ +/* + * random.c + * + * Z-machine random number generator + * + */ + +#include "frotz.h" + +static long A = 1; + +static interval = 0; +static counter = 0; + +/* + * seed_random + * + * Set the seed value for the random number generator. + * + */ + +void seed_random (int value) +{ + + if (value == 0) { /* ask interface for seed value */ + A = os_random_seed (); + interval = 0; + } else if (value < 1000) { /* special seed value */ + counter = 0; + interval = value; + } else { /* standard seed value */ + A = value; + interval = 0; + } + +}/* seed_random */ + +/* + * z_random, store a random number or set the random number seed. + * + * zargs[0] = range (positive) or seed value (negative) + * + */ + +void z_random () +{ + + if ((short) zargs[0] <= 0) { /* set random seed */ + + seed_random (- (short) zargs[0]); + store (0); + + } else { /* generate random number */ + + zword result; + + if (interval != 0) { /* ...in special mode */ + result = counter++; + if (counter == interval) counter = 0; + } else { /* ...in standard mode */ + A = 0x015a4e35L * A + 1; + result = (A >> 16) & 0x7fff; + } + + store ((zword) (result % zargs[0] + 1)); + + } + +}/* z_random */ diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..bba204f --- /dev/null +++ b/readme.txt @@ -0,0 +1,103 @@ + + + +This is the source distribution of DOS Frotz 2.32. Sorry, +documentation is not complete yet, and this small readme +file is all you get at the moment... + + + +The latest changes in the 2.32 interface are: + +-> Text and keys (except for file names) are now presented + using the new "zchar" type. This is a 1-byte value that + contains an ISO Latin-1 character or a special Infocom + character like code $09 (paragraph indentation). See + FROTZ.H for possible character codes. Some Infocom codes + have changed, too, since they collided with ISO Latin-1 + codes. I strongly recommend using the constants defined + in FROTZ.H. + + If you want to support different character sets, look at + + * zchar definition (FROTZ.H), + * z_print_unicode (TEXT.C), + * z_check_unicode (TEXT.C), + * translate_from_zscii (TEXT.C), + * translate_to_zscii (TEXT.C), + * conversion to lower case in z_read (INPUT.C), + * script_char (FILES.C). + + If you can't read accented characters from the keyboard, + make sure z_check_unicode stores 1 for codes from $0a to + $ff. If you can't write accented characters, prints some + suitable ASCII representation instead. + +-> There are some differences between Amiga and DOS picture + files, and an interpreter must take some extra care to + combine Amiga story files with DOS picture files. So far + the Amiga and DOS front-ends contained the necessary + code to make this work. Since Frotz 2.32 this problem is + dealt with in SCREEN.C, and interfaces no longer have to + worry about this. + +-> os_cursor_on and os_cursor_off are no more. Instead, an + additional argument tells os_read_key to make the cursor + visible or not. + + (I believe a game would not want to turn off the cursor + for reading a string.) + +-> os_wait_sample is no longer needed! + + SOUND.C uses the end_of_sound call to solve all problems + with 'The Lurking Horror' more effectively than before. + +-> os_start_sample should now ignore the play-once-or-loop- + forever information in the sound files and rely on its + arguments instead. The necessary information for 'The + Lurking Horror' has been wired into SOUND.C. This should + make it easier to use a more common sound format in the + future. + +-> 'Journey' asked for font #4 (fixed font) on most systems + since it simply expected the interpreter to be unable to + print a proportional font. This unpleasant behaviour is + now overwritten by Frotz. + +-> Many extern declarations in FROTZ.H have been removed. In + particular, this includes + + * end_of_sound, + * restart_header, + * resize_screen, + * completion, + * is_terminator, + * read_yes_or_no, + * read_string. + + It's fine to call these functions from your front-end, + though. Just add their prototypes to your code. + +-> For different reasons, several front-ends felt the need + to interfere with the process of restarting a game. + + There is now an interface function os_restart_game that + gives the front-end a chance to do its business while + the game restarts. The function is called several times + during the process, its sole argument indicating the + current "stage" of the restart: + + * RESTART_BEGIN -- restart has just begun + * RESTART_WPROP_SET -- window properties have been set + * RESTART_END -- restart completed + + os_restart_game is also useful to display the 'Beyond + Zork' title picture that is now available in MG1 format. + + + +Final note: The FONT.DAT file in this distribution must be +appended to the DOS executable -- sorry about this! + + diff --git a/redirect.c b/redirect.c new file mode 100644 index 0000000..fc92b41 --- /dev/null +++ b/redirect.c @@ -0,0 +1,159 @@ +/* + * redirect.c + * + * Output redirection to Z-machine memory + * + */ + +#include "frotz.h" + +#define MAX_NESTING 16 + +extern zword get_max_width (zword); + +static depth = -1; + +static struct { + zword xsize; + zword table; + zword width; + zword total; +} redirect[MAX_NESTING]; + +/* + * memory_open + * + * Begin output redirection to the memory of the Z-machine. + * + */ + +void memory_open (zword table, zword xsize, bool buffering) +{ + + if (++depth < MAX_NESTING) { + + if (!buffering) + xsize = 0xffff; + if (buffering && (short) xsize <= 0) + xsize = get_max_width ((zword) (- (short) xsize)); + + storew (table, 0); + + redirect[depth].table = table; + redirect[depth].width = 0; + redirect[depth].total = 0; + redirect[depth].xsize = xsize; + + ostream_memory = TRUE; + + } else runtime_error ("Nesting stream #3 too deep"); + +}/* memory_open */ + +/* + * memory_new_line + * + * Redirect a newline to the memory of the Z-machine. + * + */ + +void memory_new_line (void) +{ + zword size; + zword addr; + + redirect[depth].total += redirect[depth].width; + redirect[depth].width = 0; + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + if (redirect[depth].xsize != 0xffff) { + + redirect[depth].table = addr + size; + size = 0; + + } else storeb ((zword) (addr + (size++)), 13); + + storew (redirect[depth].table, size); + +}/* memory_new_line */ + +/* + * memory_word + * + * Redirect a string of characters to the memory of the Z-machine. + * + */ + +void memory_word (const zchar *s) +{ + zword size; + zword addr; + zchar c; + + if (h_version == V6) { + + int width = os_string_width (s); + + if (redirect[depth].xsize != 0xffff) + + if (redirect[depth].width + width > redirect[depth].xsize) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + + memory_new_line (); + + } + + redirect[depth].width += width; + + } + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + while ((c = *s++) != 0) + storeb ((zword) (addr + (size++)), translate_to_zscii (c)); + + storew (redirect[depth].table, size); + +}/* memory_word */ + +/* + * memory_close + * + * End of output redirection. + * + */ + +void memory_close (void) +{ + + if (depth >= 0) { + + if (redirect[depth].xsize != 0xffff) + memory_new_line (); + + if (h_version == V6) { + + h_line_width = (redirect[depth].xsize != 0xffff) ? + redirect[depth].total : redirect[depth].width; + + SET_WORD (H_LINE_WIDTH, h_line_width) + + } + + if (depth == 0) + ostream_memory = FALSE; + + depth--; + + } + +}/* memory_close */ diff --git a/screen.c b/screen.c new file mode 100644 index 0000000..851451e --- /dev/null +++ b/screen.c @@ -0,0 +1,1725 @@ +/* + * screen.c + * + * Generic screen manipulation + * + */ + +#include "frotz.h" + +extern void set_header_extension (int, zword); + +extern int direct_call (zword); + +static struct { + enum story story_id; + int pic; + int pic1; + int pic2; +} mapper[] = { + { ZORK_ZERO, 5, 497, 498 }, + { ZORK_ZERO, 6, 501, 502 }, + { ZORK_ZERO, 7, 499, 500 }, + { ZORK_ZERO, 8, 503, 504 }, + { ARTHUR, 54, 170, 171 }, + { SHOGUN, 50, 61, 62 }, + { UNKNOWN, 0, 0, 0 } +}; + +static font_height = 1; +static font_width = 1; + +static bool input_redraw = FALSE; +static bool more_prompts = TRUE; +static bool discarding = FALSE; +static bool cursor = TRUE; + +static input_window = 0; + +static struct { + zword y_pos; + zword x_pos; + zword y_size; + zword x_size; + zword y_cursor; + zword x_cursor; + zword left; + zword right; + zword nl_routine; + zword nl_countdown; + zword style; + zword colour; + zword font; + zword font_size; + zword attribute; + zword line_count; +} wp[8], *cwp; + +/* + * winarg0 + * + * Return the window number in zargs[0]. In V6 only, -3 refers to the + * current window. + * + */ + +static zword winarg0 (void) +{ + + if (h_version == V6 && (short) zargs[0] == -3) + return cwin; + + if (zargs[0] >= ((h_version == V6) ? 8 : 2)) + runtime_error ("Illegal window"); + + return zargs[0]; + +}/* winarg0 */ + +/* + * winarg2 + * + * Return the (optional) window number in zargs[2]. -3 refers to the + * current window. This optional window number was only used by some + * V6 opcodes: set_cursor, set_margins, set_colour. + * + */ + +static zword winarg2 (void) +{ + + if (zargc < 3 || (short) zargs[2] == -3) + return cwin; + + if (zargs[2] >= 8) + runtime_error ("Illegal window"); + + return zargs[2]; + +}/* winarg2 */ + +/* + * update_cursor + * + * Move the hardware cursor to make it match the window properties. + * + */ + +static void update_cursor (void) +{ + + os_set_cursor ( + cwp->y_pos + cwp->y_cursor - 1, + cwp->x_pos + cwp->x_cursor - 1); + +}/* update_cursor */ + +/* + * reset_cursor + * + * Reset the cursor of a given window to its initial position. + * + */ + +static void reset_cursor (zword win) +{ + int lines = 0; + + if (h_version <= V4 && win == 0) + lines = wp[0].y_size / hi (wp[0].font_size) - 1; + + wp[win].y_cursor = hi (wp[0].font_size) * lines + 1; + wp[win].x_cursor = wp[win].left + 1; + + if (win == cwin) + update_cursor (); + +}/* reset_cursor */ + +/* + * set_more_prompts + * + * Turn more prompts on/off. + * + */ + +void set_more_prompts (bool flag) +{ + + if (flag && !more_prompts) + cwp->line_count = 0; + + more_prompts = flag; + +}/* set_more_prompts */ + +/* + * units_left + * + * Return the #screen units from the cursor to the end of the line. + * + */ + +static int units_left (void) +{ + + return cwp->x_size - cwp->right - cwp->x_cursor + 1; + +}/* units_left */ + +/* + * get_max_width + * + * Return maximum width of a line in the given window. This is used in + * connection with the extended output stream #3 call in V6. + * + */ + +zword get_max_width (zword win) +{ + + if (h_version == V6) { + + if (win >= 8) + runtime_error ("Illegal window"); + + return wp[win].x_size - wp[win].left - wp[win].right; + + } else return 0xffff; + +}/* get_max_width */ + +/* + * countdown + * + * Decrement the newline counter. Call the newline interrupt when the + * counter hits zero. This is a helper function for screen_new_line. + * + */ + +static void countdown (void) +{ + + if (cwp->nl_countdown != 0) + if (--cwp->nl_countdown == 0) + direct_call (cwp->nl_routine); + +}/* countdown */ + +/* + * screen_new_line + * + * Print a newline to the screen. + * + */ + +void screen_new_line (void) +{ + + if (discarding) return; + + /* Handle newline interrupts at the start (for most cases) */ + + if (h_interpreter_number != INTERP_MSDOS || story_id != ZORK_ZERO || h_release != 393) + countdown (); + + /* Check whether the last input line gets destroyed */ + + if (input_window == cwin) + input_redraw = TRUE; + + /* If the cursor has not reached the bottom line, then move it to + the next line; otherwise scroll the window or reset the cursor + to the top left. */ + + cwp->x_cursor = cwp->left + 1; + + if (cwp->y_cursor + 2 * font_height - 1 > cwp->y_size) + + if (enable_scrolling) { + + zword y = cwp->y_pos; + zword x = cwp->x_pos; + + os_scroll_area (y, + x, + y + cwp->y_size - 1, + x + cwp->x_size - 1, + font_height); + + } else cwp->y_cursor = 1; + + else cwp->y_cursor += font_height; + + update_cursor (); + + /* See if we need to print a more prompt (unless the game has set + the line counter to -999 in order to suppress more prompts). */ + + if (enable_scrolling && (short) cwp->line_count != -999) { + + zword above = (cwp->y_cursor - 1) / font_height; + zword below = (cwp->y_size - cwp->y_cursor + 1) / font_height; + + cwp->line_count++; + + if ((short) cwp->line_count >= (short) above + below - 1) { + + if (more_prompts) + os_more_prompt (); + + cwp->line_count = option_context_lines; + + } + + } + + /* Handle newline interrupts at the end for Zork Zero under DOS */ + + if (h_interpreter_number == INTERP_MSDOS && story_id == ZORK_ZERO && h_release == 393) + countdown (); + +}/* screen_new_line */ + +/* + * screen_char + * + * Display a single character on the screen. + * + */ + +void screen_char (zchar c) +{ + int width; + + if (discarding) return; + + if (c == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + c = ' '; + + if (units_left () < (width = os_char_width (c))) { + + if (!enable_wrapping) + { cwp->x_cursor = cwp->x_size - cwp->right; return; } + + screen_new_line (); + + } + + os_display_char (c); cwp->x_cursor += width; + +}/* screen_char */ + +/* + * screen_word + * + * Display a string of characters on the screen. If the word doesn't fit + * then use wrapping or clipping depending on the current setting of the + * enable_wrapping flag. + * + */ + +void screen_word (const zchar *s) +{ + int width; + + if (discarding) return; + + if (*s == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + screen_char (*s++); + + if (units_left () < (width = os_string_width (s))) { + + if (!enable_wrapping) { + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) { + + int arg = (int) *s++; + + if (c == ZC_NEW_FONT) + os_set_font (arg); + if (c == ZC_NEW_STYLE) + os_set_text_style (arg); + + } else screen_char (c); + + return; + + } + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + +#ifdef AMIGA + if (cwin == 0) Justifiable (); +#endif + + screen_new_line (); + + } + + os_display_string (s); cwp->x_cursor += width; + +}/* screen_word */ + +/* + * screen_write_input + * + * Display an input line on the screen. This is required during playback. + * + */ + +void screen_write_input (const zchar *buf, zchar key) +{ + int width; + + if (units_left () < (width = os_string_width (buf))) + screen_new_line (); + + os_display_string (buf); cwp->x_cursor += width; + + if (key == ZC_RETURN) + screen_new_line (); + +}/* screen_write_input */ + +/* + * screen_erase_input + * + * Remove an input line that has already been printed from the screen + * as if it was deleted by the player. This could be necessary during + * playback. + * + */ + +void screen_erase_input (const zchar *buf) +{ + + if (buf[0] != 0) { + + int width = os_string_width (buf); + + zword y; + zword x; + + cwp->x_cursor -= width; + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + width - 1); + os_set_cursor (y, x); + + } + +}/* screen_erase_input */ + +/* + * console_read_input + * + * Read an input line from the keyboard and return the terminating key. + * + */ + +zchar console_read_input (int max, zchar *buf, zword timeout, bool continued) +{ + zchar key; + int i; + + /* Make sure there is some space for input */ + + if (cwin == 0 && units_left () + os_string_width (buf) < 10 * font_width) + screen_new_line (); + + /* Make sure the input line is visible */ + + if (continued && input_redraw) + screen_write_input (buf, -1); + + input_window = cwin; + input_redraw = FALSE; + + /* Get input line from IO interface */ + + cwp->x_cursor -= os_string_width (buf); + key = os_read_line (max, buf, timeout, units_left (), continued); + cwp->x_cursor += os_string_width (buf); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + /* Add a newline if the input was terminated normally */ + + if (key == ZC_RETURN) + screen_new_line (); + + return key; + +}/* console_read_input */ + +/* + * console_read_key + * + * Read a single keystroke and return it. + * + */ + +zchar console_read_key (zword timeout) +{ + zchar key; + int i; + + key = os_read_key (timeout, cursor); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + return key; + +}/* console_read_key */ + +/* + * update_attributes + * + * Set the three enable_*** variables to make them match the attributes + * of the current window. + * + */ + +static void update_attributes (void) +{ + zword attr = cwp->attribute; + + enable_wrapping = attr & 1; + enable_scrolling = attr & 2; + enable_scripting = attr & 4; + enable_buffering = attr & 8; + + /* Some story files forget to select wrapping for printing hints */ + + if (story_id == ZORK_ZERO && h_release == 366) + if (cwin == 0) + enable_wrapping = TRUE; + if (story_id == SHOGUN && h_release <= 295) + if (cwin == 0) + enable_wrapping = TRUE; + +}/* update_attributes */ + +/* + * refresh_text_style + * + * Set the right text style. This can be necessary when the fixed font + * flag is changed, or when a new window is selected, or when the game + * uses the set_text_style opcode. + * + */ + +void refresh_text_style (void) +{ + zword style; + + if (h_version != V6) { + + style = wp[0].style; + + if (cwin != 0 || h_flags & FIXED_FONT_FLAG) + style |= FIXED_WIDTH_STYLE; + + } else style = cwp->style; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_STYLE); + print_char (style); + + } else os_set_text_style (style); + +}/* refresh_text_style */ + +/* + * set_window + * + * Set the current window. In V6 every window has its own set of window + * properties such as colours, text style, cursor position and size. + * + */ + +static void set_window (zword win) +{ + + flush_buffer (); + + cwin = win; cwp = wp + win; + + update_attributes (); + + if (h_version == V6) { + + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + if (os_font_data (cwp->font, &font_height, &font_width)) + os_set_font (cwp->font); + + os_set_text_style (cwp->style); + + } else refresh_text_style (); + + if (h_version != V6 && win != 0) { + wp[win].y_cursor = 1; + wp[win].x_cursor = 1; + } + + update_cursor (); + +}/* set_window */ + +/* + * erase_window + * + * Erase a window to background colour. + * + */ + +static void erase_window (zword win) +{ + zword y = wp[win].y_pos; + zword x = wp[win].x_pos; + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + os_erase_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1); + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + reset_cursor (win); + + wp[win].line_count = 0; + +}/* erase_window */ + +/* + * split_window + * + * Divide the screen into upper (1) and lower (0) windows. In V3 the upper + * window appears below the status line. + * + */ + +void split_window (zword height) +{ + zword stat_height = 0; + + flush_buffer (); + + /* Calculate height of status line and upper window */ + + if (h_version != V6) + height *= hi (wp[1].font_size); + + if (h_version <= V3) + stat_height = hi (wp[7].font_size); + + /* Cursor of upper window mustn't be swallowed by the lower window */ + + wp[1].y_cursor += wp[1].y_pos - 1 - stat_height; + + wp[1].y_pos = 1 + stat_height; + wp[1].y_size = height; + + if ((short) wp[1].y_cursor > (short) wp[1].y_size) + reset_cursor (1); + + /* Cursor of lower window mustn't be swallowed by the upper window */ + + wp[0].y_cursor += wp[0].y_pos - 1 - stat_height - height; + + wp[0].y_pos = 1 + stat_height + height; + wp[0].y_size = h_screen_height - stat_height - height; + + if ((short) wp[0].y_cursor < 1) + reset_cursor (0); + + /* Erase the upper window in V3 only */ + + if (h_version == V3 && height != 0) + erase_window (1); + +}/* split_window */ + +/* + * erase_screen + * + * Erase the entire screen to background colour. + * + */ + +static void erase_screen (zword win) +{ + int i; + + os_erase_area (1, 1, h_screen_height, h_screen_width); + + if ((short) win == -1) { + split_window (0); + set_window (0); + reset_cursor (0); + } + + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + +}/* erase_screen */ + +#ifdef AMIGA + +/* + * resize_screen + * + * Try to adapt the window properties to a new screen size. + * + */ + +void resize_screen (void) +{ + + if (h_version != V6) { + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + wp[7].x_size = h_screen_width; + + wp[0].y_size = h_screen_height - wp[1].y_size - wp[7].y_size; + + } + +}/* resize_screen */ + +#endif + +/* + * restart_screen + * + * Prepare the screen for a new game. + * + */ + +void restart_screen (void) +{ + + /* Use default settings */ + + os_set_colour (h_default_foreground, h_default_background); + + if (os_font_data (TEXT_FONT, &font_height, &font_width)) + os_set_font (TEXT_FONT); + + os_set_text_style (0); + + cursor = TRUE; + + /* Initialise window properties */ + + mwin = 1; + + for (cwp = wp; cwp < wp + 8; cwp++) { + cwp->y_pos = 1; + cwp->x_pos = 1; + cwp->y_size = 0; + cwp->x_size = 0; + cwp->y_cursor = 1; + cwp->x_cursor = 1; + cwp->left = 0; + cwp->right = 0; + cwp->nl_routine = 0; + cwp->nl_countdown = 0; + cwp->style = 0; + cwp->colour = (h_default_background << 8) | h_default_foreground; + cwp->font = TEXT_FONT; + cwp->font_size = (font_height << 8) | font_width; + cwp->attribute = 8; + } + + /* Prepare lower/upper windows and status line */ + + wp[0].attribute = 15; + + wp[0].left = option_left_margin; + wp[0].right = option_right_margin; + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + + if (h_version <= V3) + wp[7].x_size = h_screen_width; + + os_restart_game (RESTART_WPROP_SET); + + /* Clear the screen, unsplit it and select window 0 */ + + erase_screen ((zword) (-1)); + +}/* restart_screen */ + +/* + * validate_click + * + * Return false if the last mouse click occured outside the current + * mouse window; otherwise write the mouse arrow coordinates to the + * memory of the header extension table and return true. + * + */ + +bool validate_click (void) +{ + + if (mwin >= 0) { + + if (mouse_y < wp[mwin].y_pos || mouse_y >= wp[mwin].y_pos + wp[mwin].y_size) + return FALSE; + if (mouse_x < wp[mwin].x_pos || mouse_x >= wp[mwin].x_pos + wp[mwin].x_size) + return FALSE; + + hx_mouse_y = mouse_y - wp[mwin].y_pos + 1; + hx_mouse_x = mouse_x - wp[mwin].x_pos + 1; + + } else { + + if (mouse_y < 1 || mouse_y > h_screen_height) + return FALSE; + if (mouse_x < 1 || mouse_x > h_screen_width) + return FALSE; + + hx_mouse_y = mouse_y; + hx_mouse_x = mouse_x; + + } + + if (h_version != V6) { + hx_mouse_y = (hx_mouse_y - 1) / h_font_height + 1; + hx_mouse_x = (hx_mouse_x - 1) / h_font_width + 1; + } + + set_header_extension (HX_MOUSE_Y, hx_mouse_y); + set_header_extension (HX_MOUSE_X, hx_mouse_x); + + return TRUE; + +}/* validate_click */ + +/* + * screen_mssg_on + * + * Start printing a so-called debugging message. The contents of the + * message are passed to the message stream, a Frotz specific output + * stream with maximum priority. + * + */ + +void screen_mssg_on (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + os_set_text_style (0); + + if (cwp->x_cursor != cwp->left + 1) + screen_new_line (); + + screen_char (ZC_INDENT); + + } else discarding = TRUE; /* discard messages in other windows */ + +}/* screen_mssg_on */ + +/* + * screen_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void screen_mssg_off (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + screen_new_line (); + + refresh_text_style (); + + } else discarding = FALSE; /* message has been discarded */ + +}/* screen_mssg_off */ + +/* + * z_buffer_mode, turn text buffering on/off. + * + * zargs[0] = new text buffering flag (0 or 1) + * + */ + +void z_buffer_mode (void) +{ + + /* Infocom's V6 games rarely use the buffer_mode opcode. If they do + then only to print text immediately, without any delay. This was + used to give the player some sign of life while the game was + spending much time on parsing a complicated input line. (To turn + off word wrapping, V6 games use the window_style opcode instead.) + Today we can afford to ignore buffer_mode in V6. */ + + if (h_version != V6) { + + flush_buffer (); + + wp[0].attribute &= ~8; + + if (zargs[0] != 0) + wp[0].attribute |= 8; + + update_attributes (); + + } + +}/* z_buffer_mode */ + +/* + * z_draw_picture, draw a picture. + * + * zargs[0] = number of picture to draw + * zargs[1] = y-coordinate of top left corner + * zargs[2] = x-coordinate of top left corner + * + */ + +void z_draw_picture (void) +{ + zword pic = zargs[0]; + + zword y = zargs[1]; + zword x = zargs[2]; + + int i; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + /* The following is necessary to make Amiga and Macintosh story + files work with MCGA graphics files. Some screen-filling + pictures of the original Amiga release like the borders of + Zork Zero were split into several MCGA pictures (left, right + and top borders). We pretend this has not happened. */ + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id && pic == mapper[i].pic) { + + int height1, width1; + int height2, width2; + + int delta = 0; + + os_picture_data (pic, &height1, &width1); + os_picture_data (mapper[i].pic2, &height2, &width2); + + if (story_id == ARTHUR && pic == 54) + delta = h_screen_width / 160; + + os_draw_picture (mapper[i].pic1, y + height1, x + delta); + os_draw_picture (mapper[i].pic2, y + height1, x + width1 - width2 - delta); + + } + + os_draw_picture (pic, y, x); + + if (story_id == SHOGUN) + + if (pic == 3) { + + int height, width; + + os_picture_data (59, &height, &width); + os_draw_picture (59, y, h_screen_width - width + 1); + + } + +}/* z_draw_picture */ + +/* + * z_erase_line, erase the line starting at the cursor position. + * + * zargs[0] = 1 + #units to erase (1 clears to the end of the line) + * + */ + +void z_erase_line (void) +{ + zword pixels = zargs[0]; + zword y, x; + + flush_buffer (); + + /* Clipping at the right margin of the current window */ + + if (--pixels == 0 || pixels > units_left ()) + pixels = units_left (); + + /* Erase from cursor position */ + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + pixels - 1); + +}/* z_erase_line */ + +/* + * z_erase_picture, erase a picture with background colour. + * + * zargs[0] = number of picture to erase + * zargs[1] = y-coordinate of top left corner (optional) + * zargs[2] = x-coordinate of top left corner (optional) + * + */ + +void z_erase_picture (void) +{ + int height, width; + + zword y = zargs[1]; + zword x = zargs[2]; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + os_picture_data (zargs[0], &height, &width); + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + os_erase_area (y, x, y + height - 1, x + width - 1); + +}/* z_erase_picture */ + +/* + * z_erase_window, erase a window or the screen to background colour. + * + * zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit) + * + */ + +void z_erase_window (void) +{ + + flush_buffer (); + + if ((short) zargs[0] == -1 || (short) zargs[0] == -2) + erase_screen (zargs[0]); + else + erase_window (winarg0 ()); + +}/* z_erase_window */ + +/* + * z_get_cursor, write the cursor coordinates into a table. + * + * zargs[0] = address to write information to + * + */ + +void z_get_cursor (void) +{ + zword y, x; + + flush_buffer (); + + y = cwp->y_cursor; + x = cwp->x_cursor; + + if (h_version != V6) { /* convert to grid positions */ + y = (y - 1) / h_font_height + 1; + x = (x - 1) / h_font_width + 1; + } + + storew ((zword) (zargs[0] + 0), y); + storew ((zword) (zargs[0] + 2), x); + +}/* z_get_cursor */ + +/* + * z_get_wind_prop, store the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to be stored + * + */ + +void z_get_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error ("Illegal window property"); + + store (((zword *) (wp + winarg0 ())) [zargs[1]]); + +}/* z_get_wind_prop */ + +/* + * z_mouse_window, select a window as mouse window. + * + * zargs[0] = window number (-3 is the current) or -1 for the screen + * + */ + +void z_mouse_window (void) +{ + + mwin = ((short) zargs[0] == -1) ? -1 : winarg0 (); + +}/* z_mouse_window */ + +/* + * z_move_window, place a window on the screen. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = y-coordinate + * zargs[2] = x-coordinate + * + */ + +void z_move_window (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_pos = zargs[1]; + wp[win].x_pos = zargs[2]; + + if (win == cwin) + update_cursor (); + +}/* z_move_window */ + +/* + * z_picture_data, get information on a picture or the graphics file. + * + * zargs[0] = number of picture or 0 for the graphics file + * zargs[1] = address to write information to + * + */ + +void z_picture_data (void) +{ + zword pic = zargs[0]; + zword table = zargs[1]; + + int height, width; + int i; + + bool avail = os_picture_data (pic, &height, &width); + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id) + + if (pic == mapper[i].pic) { + + int height2, width2; + + avail &= os_picture_data (mapper[i].pic1, &height2, &width2); + avail &= os_picture_data (mapper[i].pic2, &height2, &width2); + + height += height2; + + } else if (pic == mapper[i].pic1 || pic == mapper[i].pic2) + + avail = FALSE; + + storew ((zword) (table + 0), (zword) (height)); + storew ((zword) (table + 2), (zword) (width)); + + branch (avail); + +}/* z_picture_data */ + +/* + * z_picture_table, prepare a group of pictures for faster display. + * + * zargs[0] = address of table holding the picture numbers + * + */ + +void z_picture_table (void) +{ + + /* This opcode is used by Shogun and Zork Zero when the player + encounters built-in games such as Peggleboz. Nowadays it is + not very helpful to hold the picture data in memory because + even a small disk cache avoids re-loading of data. */ + +}/* z_picture_table */ + +/* + * z_print_table, print ASCII text in a rectangular area. + * + * zargs[0] = address of text to be printed + * zargs[1] = width of rectangular area + * zargs[2] = height of rectangular area (optional) + * zargs[3] = number of char's to skip between lines (optional) + * + */ + +void z_print_table (void) +{ + zword addr = zargs[0]; + zword x; + int i, j; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 1; + if (zargc < 4) + zargs[3] = 0; + + /* Write text in width x height rectangle */ + + x = cwp->x_cursor; + + for (i = 0; i < zargs[2]; i++) { + + if (i != 0) { + + flush_buffer (); + + cwp->y_cursor += font_height; + cwp->x_cursor = x; + + update_cursor (); + + } + + for (j = 0; j < zargs[1]; j++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (c); + + } + + addr += zargs[3]; + + } + +}/* z_print_table */ + +/* + * z_put_wind_prop, set the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to set + * zargs[2] = value to set window property to + * + */ + +void z_put_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error ("Illegal window property"); + + ((zword *) (wp + winarg0 ())) [zargs[1]] = zargs[2]; + +}/* z_put_wind_prop */ + +/* + * z_scroll_window, scroll a window up or down. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = #screen units to scroll up (positive) or down (negative) + * + */ + +void z_scroll_window (void) +{ + zword win = winarg0 (); + zword y, x; + + flush_buffer (); + + /* Use the correct set of colours when scrolling the window */ + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + y = wp[win].y_pos; + x = wp[win].x_pos; + + os_scroll_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1, + (short) zargs[1]); + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + +}/* z_scroll_window */ + +/* + * z_set_colour, set the foreground and background colours. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_colour (void) +{ + zword win = (h_version == V6) ? winarg2 () : 0; + + zword fg = zargs[0]; + zword bg = zargs[1]; + + flush_buffer (); + + if ((short) fg == -1) /* colour -1 is the colour at the cursor */ + fg = os_peek_colour (); + if ((short) bg == -1) + bg = os_peek_colour (); + + if (fg == 0) /* colour 0 means keep current colour */ + fg = lo (wp[win].colour); + if (bg == 0) + bg = hi (wp[win].colour); + + if (fg == 1) /* colour 1 is the system default colour */ + fg = h_default_foreground; + if (bg == 1) + bg = h_default_background; + + if (h_version == V6 && h_interpreter_number == INTERP_AMIGA) + + /* Changing colours of window 0 affects the entire screen */ + + if (win == 0) { + + int i; + + for (i = 1; i < 8; i++) { + + zword bg2 = hi (wp[i].colour); + zword fg2 = lo (wp[i].colour); + + if (bg2 < 16) + bg2 = (bg2 == lo (wp[0].colour)) ? fg : bg; + if (fg2 < 16) + fg2 = (fg2 == lo (wp[0].colour)) ? fg : bg; + + wp[i].colour = (bg2 << 8) | fg2; + + } + + } + + wp[win].colour = (bg << 8) | fg; + + if (win == cwin || h_version != V6) + os_set_colour (fg, bg); + +}/* z_set_colour */ + +/* + * z_set_font, set the font for text output and store the previous font. + * + * zargs[0] = number of font or 0 to keep current font + * + */ + +void z_set_font (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword font = zargs[0]; + + if (font != 0) { + + if (story_id == JOURNEY && font == 4) /* Journey uses fixed fonts */ + font = 1; /* for most interpreter #'s */ + + if (os_font_data (font, &font_height, &font_width)) { + + store (wp[win].font); + + wp[win].font = font; + wp[win].font_size = (font_height << 8) | font_width; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_FONT); + print_char (font); + + } else os_set_font (font); + + } else store (0); + + } else store (wp[win].font); + +}/* z_set_font */ + +/* + * z_set_cursor, set the cursor position or turn the cursor on/off. + * + * zargs[0] = y-coordinate or -2/-1 for cursor on/off + * zargs[1] = x-coordinate + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_cursor (void) +{ + zword win = (h_version == V6) ? winarg2 () : 1; + + zword y = zargs[0]; + zword x = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = -3; + + /* Handle cursor on/off */ + + if ((short) y < 0) { + + if ((short) y == -2) + cursor = TRUE; + if ((short) y == -1) + cursor = FALSE; + + return; + + } + + /* Convert grid positions to screen units if this is not V6 */ + + if (h_version != V6) { + + if (cwin == 0) + return; + + y = (y - 1) * h_font_height + 1; + x = (x - 1) * h_font_width + 1; + + } + + /* Protect the margins */ + + if (x <= wp[win].left || x > wp[win].x_size - wp[win].right) + x = wp[win].left + 1; + + /* Move the cursor */ + + wp[win].y_cursor = y; + wp[win].x_cursor = x; + + if (win == cwin) + update_cursor (); + +}/* z_set_cursor */ + +/* + * z_set_margins, set the left and right margins of a window. + * + * zargs[0] = left margin in pixels + * zargs[1] = right margin in pixels + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_margins (void) +{ + zword win = winarg2 (); + + flush_buffer (); + + wp[win].left = zargs[0]; + wp[win].right = zargs[1]; + + /* Protect the margins */ + + if (wp[win].x_cursor <= zargs[0] || wp[win].x_cursor > wp[win].x_size - zargs[1]) { + + wp[win].x_cursor = zargs[0] + 1; + + if (win == cwin) + update_cursor (); + + } + +}/* z_set_margins */ + +/* + * z_set_text_style, set the style for text output. + * + * zargs[0] = style flags to set or 0 to reset text style + * + */ + +void z_set_text_style (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword style = zargs[0]; + + wp[win].style |= style; + + if (style == 0) + wp[win].style = 0; + + refresh_text_style (); + +}/* z_set_text_style */ + +/* + * z_set_window, select the current window. + * + * zargs[0] = window to be selected (-3 is the current one) + * + */ + +void z_set_window (void) +{ + + set_window (winarg0 ()); + +}/* z_set_window */ + +/* + * pad_status_line + * + * Pad the status line with spaces up to the given position. + * + */ + +static void pad_status_line (int column) +{ + int spaces; + + flush_buffer (); + + spaces = units_left () / os_char_width (' ') - column; + + while (spaces--) + screen_char (' '); + +}/* pad_status_line */ + +/* + * z_show_status, display the status line for V1 to V3 games. + * + * no zargs used + * + */ + +void z_show_status (void) +{ + zword global0; + zword global1; + zword global2; + zword addr; + + bool brief = FALSE; + + /* One V5 game (Wishbringer Solid Gold) contains this opcode by + accident, so just return if the version number does not fit */ + + if (h_version >= V4) + return; + + /* Read all relevant global variables from the memory of the + Z-machine into local variables */ + + addr = h_globals; + LOW_WORD (addr, global0) + addr += 2; + LOW_WORD (addr, global1) + addr += 2; + LOW_WORD (addr, global2) + + /* Frotz uses window 7 for the status line. Don't forget to select + reverse and fixed width text style */ + + set_window (7); + + print_char (ZC_NEW_STYLE); + print_char (REVERSE_STYLE | FIXED_WIDTH_STYLE); + + /* If the screen width is below 55 characters then we have to use + the brief status line format */ + + if (h_screen_cols < 55) + brief = TRUE; + + /* Print the object description for the global variable 0 */ + + print_char (' '); + print_object (global0); + + /* A header flag tells us whether we have to display the current + time or the score/moves information */ + + if (h_config & CONFIG_TIME) { /* print hours and minutes */ + + zword hours = (global1 + 11) % 12 + 1; + + pad_status_line (brief ? 15 : 20); + + print_string ("Time: "); + + if (hours < 10) + print_char (' '); + print_num (hours); + + print_char (':'); + + if (global2 < 10) + print_char ('0'); + print_num (global2); + + print_char (' '); + + print_char ((global1 >= 12) ? 'p' : 'a'); + print_char ('m'); + + } else { /* print score and moves */ + + pad_status_line (brief ? 15 : 30); + + print_string (brief ? "S: " : "Score: "); + print_num (global1); + + pad_status_line (brief ? 8 : 14); + + print_string (brief ? "M: " : "Moves: "); + print_num (global2); + + } + + /* Pad the end of the status line with spaces */ + + pad_status_line (0); + + /* Return to the lower window */ + + set_window (0); + +}/* z_show_status */ + +/* + * z_split_window, split the screen into an upper (1) and lower (0) window. + * + * zargs[0] = height of upper window in screen units (V6) or #lines + * + */ + +void z_split_window (void) +{ + + split_window (zargs[0]); + +}/* z_split_window */ + +/* + * z_window_size, change the width and height of a window. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = new height in screen units + * zargs[2] = new width in screen units + * + */ + +void z_window_size (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_size = zargs[1]; + wp[win].x_size = zargs[2]; + + /* Keep the cursor within the window */ + + if (wp[win].y_cursor > zargs[1] || wp[win].x_cursor > zargs[2]) + reset_cursor (win); + +}/* z_window_size */ + +/* + * z_window_style, set / clear / toggle window attributes. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = window attribute flags + * zargs[2] = operation to perform (optional, defaults to 0) + * + */ + +void z_window_style (void) +{ + zword win = winarg0 (); + zword flags = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Set window style */ + + switch (zargs[2]) { + case 0: wp[win].attribute = flags; break; + case 1: wp[win].attribute |= flags; break; + case 2: wp[win].attribute &= ~flags; break; + case 3: wp[win].attribute ^= flags; break; + } + + if (cwin == win) + update_attributes (); + +}/* z_window_style */ diff --git a/sound.c b/sound.c new file mode 100644 index 0000000..bfaf156 --- /dev/null +++ b/sound.c @@ -0,0 +1,161 @@ +/* + * sound.c + * + * Sound effect function + * + */ + +#include "frotz.h" + +#define EFFECT_PREPARE 1 +#define EFFECT_PLAY 2 +#define EFFECT_STOP 3 +#define EFFECT_FINISH_WITH 4 + +extern int direct_call (zword); + +static zword routine = 0; + +static next_sample = 0; +static next_volume = 0; + +static bool locked = FALSE; +static bool playing = FALSE; + +/* + * start_sample + * + * Call the IO interface to play a sample. + * + */ + +static void start_sample (int number, int volume, int repeats, zword eos) +{ + + static zbyte lh_repeats[] = { + 0x00, 0x00, 0x00, 0x01, 0xff, + 0x00, 0x01, 0x01, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (story_id == LURKING_HORROR) + repeats = lh_repeats[number]; + + os_start_sample (number, volume, repeats); + + routine = eos; + playing = TRUE; + +}/* start_sample */ + +/* + * start_next_sample + * + * Play a sample that has been delayed until the previous sound effect has + * finished. This is necessary for two samples in The Lurking Horror that + * immediately follow other samples. + * + */ + +static void start_next_sample (void) +{ + + if (next_sample != 0) + start_sample (next_sample, next_volume, 0, 0); + + next_sample = 0; + next_volume = 0; + +}/* start_next_sample */ + +/* + * end_of_sound + * + * Call the Z-code routine which was given as the last parameter of + * a sound_effect call. This function may be called from a hardware + * interrupt (which requires extremely careful programming). + * + */ + +void end_of_sound (void) +{ + + playing = FALSE; + + if (!locked) { + + if (story_id == LURKING_HORROR) + start_next_sample (); + + direct_call (routine); + + } + +}/* end_of_sound */ + +/* + * z_sound_effect, load / play / stop / discard a sound effect. + * + * zargs[0] = number of bleep (1 or 2) or sample + * zargs[1] = operation to perform (samples only) + * zargs[2] = repeats and volume (play sample only) + * zargs[3] = end-of-sound routine (play sample only, optional) + * + * Note: Volumes range from 1 to 8, volume 255 is the default volume. + * Repeats are stored in the high byte, 255 is infinite loop. + * + */ + +void z_sound_effect (void) +{ + zword number = zargs[0]; + zword effect = zargs[1]; + zword volume = zargs[2]; + + if (number >= 3) { + + locked = TRUE; + + if (story_id == LURKING_HORROR && (number == 9 || number == 16)) { + + if (effect == EFFECT_PLAY) { + + next_sample = number; + next_volume = volume; + + locked = FALSE; + + if (!playing) + start_next_sample (); + + } else locked = FALSE; + + return; + + } + + playing = FALSE; + + switch (effect) { + + case EFFECT_PREPARE: + os_prepare_sample (number); + break; + case EFFECT_PLAY: + start_sample (number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0); + break; + case EFFECT_STOP: + os_stop_sample (); + break; + case EFFECT_FINISH_WITH: + os_finish_with_sample (); + break; + + } + + locked = FALSE; + + } else os_beep (number); + +}/* z_sound_effect */ diff --git a/stream.c b/stream.c new file mode 100644 index 0000000..c0c6e3b --- /dev/null +++ b/stream.c @@ -0,0 +1,352 @@ +/* + * stream.c + * + * IO stream implementation + * + */ + +#include "frotz.h" + +extern bool handle_hot_key (zchar); + +extern bool validate_click (void); + +extern void replay_open (void); +extern void replay_close (void); +extern void memory_open (zword, zword, bool); +extern void memory_close (void); +extern void record_open (void); +extern void record_close (void); +extern void script_open (void); +extern void script_close (void); + +extern void memory_word (const zchar *); +extern void memory_new_line (void); +extern void record_write_key (zchar); +extern void record_write_input (const zchar *, zchar); +extern void script_char (zchar); +extern void script_word (const zchar *); +extern void script_new_line (void); +extern void script_write_input (const zchar *, zchar); +extern void script_erase_input (const zchar *); +extern void script_mssg_on (void); +extern void script_mssg_off (void); +extern void screen_char (zchar); +extern void screen_word (const zchar *); +extern void screen_new_line (void); +extern void screen_write_input (const zchar *, zchar); +extern void screen_erase_input (const zchar *); +extern void screen_mssg_on (void); +extern void screen_mssg_off (void); + +extern zchar replay_read_key (void); +extern zchar replay_read_input (zchar *); +extern zchar console_read_key (zword); +extern zchar console_read_input (int, zchar *, zword, bool); + +extern int direct_call (zword); + +/* + * stream_mssg_on + * + * Start printing a "debugging" message. + * + */ + +void stream_mssg_on (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_on (); + if (ostream_script && enable_scripting) + script_mssg_on (); + + message = TRUE; + +}/* stream_mssg_on */ + +/* + * stream_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void stream_mssg_off (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_off (); + if (ostream_script && enable_scripting) + script_mssg_off (); + + message = FALSE; + +}/* stream_mssg_off */ + +/* + * z_output_stream, open or close an output stream. + * + * zargs[0] = stream to open (positive) or close (negative) + * zargs[1] = address to redirect output to (stream 3 only) + * zargs[2] = width of redirected output (stream 3 only, optional) + * + */ + +void z_output_stream (void) +{ + + flush_buffer (); + + switch ((short) zargs[0]) { + + case 1: ostream_screen = TRUE; + break; + case -1: ostream_screen = FALSE; + break; + case 2: if (!ostream_script) script_open (); + break; + case -2: if (ostream_script) script_close (); + break; + case 3: memory_open (zargs[1], zargs[2], zargc >= 3); + break; + case -3: memory_close (); + break; + case 4: if (!ostream_record) record_open (); + break; + case -4: if (ostream_record) record_close (); + break; + + } + +}/* z_output_stream */ + +/* + * stream_char + * + * Send a single character to the output stream. + * + */ + +void stream_char (zchar c) +{ + + if (ostream_screen) + screen_char (c); + if (ostream_script && enable_scripting) + script_char (c); + +}/* stream_char */ + +/* + * stream_word + * + * Send a string of characters to the output streams. + * + */ + +void stream_word (const zchar *s) +{ + + if (ostream_memory && !message) + + memory_word (s); + + else { + + if (ostream_screen) + screen_word (s); + if (ostream_script && enable_scripting) + script_word (s); + + } + +}/* stream_word */ + +/* + * stream_new_line + * + * Send a newline to the output streams. + * + */ + +void stream_new_line (void) +{ + + if (ostream_memory && !message) + + memory_new_line (); + + else { + + if (ostream_screen) + screen_new_line (); + if (ostream_script && enable_scripting) + script_new_line (); + + } + +}/* stream_new_line */ + +/* + * z_input_stream, select an input stream. + * + * zargs[0] = input stream to be selected + * + */ + +void z_input_stream (void) +{ + + flush_buffer (); + + if (zargs[0] == 0 && istream_replay) + replay_close (); + if (zargs[0] == 1 && !istream_replay) + replay_open (); + +}/* z_input_stream */ + +/* + * stream_read_key + * + * Read a single keystroke from the current input stream. + * + */ + +zchar stream_read_key ( zword timeout, zword routine, + bool hot_keys ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Read key from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_key (); + else + key = console_read_key (timeout); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy key to the command file */ + + if (ostream_record && !istream_replay) + record_write_key (key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (h_version == V4 && key == ZC_HKEY_UNDO) + goto continue_input; + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Return key */ + + return key; + +}/* stream_read_key */ + +/* + * stream_read_input + * + * Read a line of input from the current input stream. + * + */ + +zchar stream_read_input ( int max, zchar *buf, + zword timeout, zword routine, + bool hot_keys, + bool no_scripting ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Remove initial input from the transscript file or from the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_erase_input (buf); + if (istream_replay) + screen_erase_input (buf); + + /* Read input line from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_input (buf); + else + key = console_read_input (max, buf, timeout, key != ZC_BAD); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy input line to the command file */ + + if (ostream_record && !istream_replay) + record_write_input (buf, key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Copy input line to transscript file or to the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_write_input (buf, key); + if (istream_replay) + screen_write_input (buf, key); + + /* Return terminating key */ + + return key; + +}/* stream_read_input */ diff --git a/table.c b/table.c new file mode 100644 index 0000000..9999b67 --- /dev/null +++ b/table.c @@ -0,0 +1,180 @@ +/* + * table.c + * + * Table handling opcodes + * + */ + +#include "frotz.h" + +/* + * z_copy_table, copy a table or fill it with zeroes. + * + * zargs[0] = address of table + * zargs[1] = destination address or 0 for fill + * zargs[2] = size of table + * + * Note: Copying is safe even when source and destination overlap; but + * if zargs[1] is negative the table _must_ be copied forwards. + * + */ + +void z_copy_table (void) +{ + zword addr; + zword size = zargs[2]; + zbyte value; + int i; + + if (zargs[1] == 0) /* zero table */ + + for (i = 0; i < size; i++) + storeb ((zword) (zargs[0] + i), 0); + + else if ((short) size < 0 || zargs[0] > zargs[1]) /* copy forwards */ + + for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + + else /* copy backwards */ + + for (i = size - 1; i >= 0; i--) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + +}/* z_copy_table */ + +/* + * z_loadb, store a value from a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadb (void) +{ + zword addr = zargs[0] + zargs[1]; + zbyte value; + + LOW_BYTE (addr, value) + + store (value); + +}/* z_loadb */ + +/* + * z_loadw, store a value from a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadw (void) +{ + zword addr = zargs[0] + 2 * zargs[1]; + zword value; + + LOW_WORD (addr, value) + + store (value); + +}/* z_loadw */ + +/* + * z_scan_table, find and store the address of a target within a table. + * + * zargs[0] = target value to be searched for + * zargs[1] = address of table + * zargs[2] = number of table entries to check value against + * zargs[3] = type of table (optional, defaults to 0x82) + * + * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise + * it's a byte array. The lower bits hold the address step. + * + */ + +void z_scan_table (void) +{ + zword addr = zargs[1]; + int i; + + /* Supply default arguments */ + + if (zargc < 4) + zargs[3] = 0x82; + + /* Scan byte or word array */ + + for (i = 0; i < zargs[2]; i++) { + + if (zargs[3] & 0x80) { /* scan word array */ + + zword wvalue; + + LOW_WORD (addr, wvalue) + + if (wvalue == zargs[0]) + goto finished; + + } else { /* scan byte array */ + + zbyte bvalue; + + LOW_BYTE (addr, bvalue) + + if (bvalue == zargs[0]) + goto finished; + + } + + addr += zargs[3] & 0x7f; + + } + + addr = 0; + +finished: + + store (addr); + branch (addr); + +}/* z_scan_table */ + +/* + * z_storeb, write a byte into a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storeb (void) +{ + + storeb ((zword) (zargs[0] + zargs[1]), zargs[2]); + +}/* z_storeb */ + +/* + * z_storew, write a word into a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storew (void) +{ + + storew ((zword) (zargs[0] + 2 * zargs[1]), zargs[2]); + +}/* z_storew */ diff --git a/text.c b/text.c new file mode 100644 index 0000000..c8c3dbe --- /dev/null +++ b/text.c @@ -0,0 +1,1080 @@ +/* + * text.c + * + * Text manipulation functions + * + */ + +#include "frotz.h" + +enum string_type { + LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY +}; + +extern zword object_name (zword); + +static zchar decoded[10]; +static zword encoded[3]; + +static zchar zscii_to_latin1[] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xab, + 0xbb, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 0x00, 0x00, 0xa1, 0xbf +}; + +/* + * translate_from_zscii + * + * Map a ZSCII character onto the ISO Latin-1 alphabet. + * + */ + +zchar translate_from_zscii (zbyte c) +{ + + if (c == 0xfc) + return ZC_MENU_CLICK; + if (c == 0xfd) + return ZC_DOUBLE_CLICK; + if (c == 0xfe) + return ZC_SINGLE_CLICK; + + if (c >= 0x9b && story_id != BEYOND_ZORK) + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + + LOW_BYTE (hx_unicode_table, N) + + if (c - 0x9b < N) { + + zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + return (unicode < 0x100) ? (zchar) unicode : '?'; + + } else return '?'; + + } else /* game uses standard set */ + + if (c <= 0xdf) { + + if (c == 0xdc || c == 0xdd) /* Oe and oe ligatures */ + return '?'; /* are not ISO-Latin 1 */ + + return zscii_to_latin1[c - 0x9b]; + + } else return '?'; + + return c; + +}/* translate_from_zscii */ + +/* + * translate_to_zscii + * + * Map an ISO Latin-1 character onto the ZSCII alphabet. + * + */ + +zbyte translate_to_zscii (zchar c) +{ + int i; + + if (c == ZC_SINGLE_CLICK) + return 0xfe; + if (c == ZC_DOUBLE_CLICK) + return 0xfd; + if (c == ZC_MENU_CLICK) + return 0xfc; + + if (c >= ZC_LATIN1_MIN) + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + int i; + + LOW_BYTE (hx_unicode_table, N) + + for (i = 0x9b; i < 0x9b + N; i++) { + + zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + if (c == unicode) + return (zbyte) i; + + } + + return '?'; + + } else { /* game uses standard set */ + + for (i = 0x9b; i <= 0xdf; i++) + if (c == zscii_to_latin1[i - 0x9b]) + return (zbyte) i; + + return '?'; + + } + + return c; + +}/* translate_to_zscii */ + +/* + * alphabet + * + * Return a character from one of the three character sets. + * + */ + +static zchar alphabet (int set, int index) +{ + + if (h_alphabet != 0) { /* game uses its own alphabet */ + + zbyte c; + + zword addr = h_alphabet + 26 * set + index; + LOW_BYTE (addr, c) + + return translate_from_zscii (c); + + } else /* game uses default alphabet */ + + if (set == 0) + return 'a' + index; + else if (set == 1) + return 'A' + index; + else if (h_version == V1) + return " 0123456789.,!?_#'\"/\\<-:()"[index]; + else + return " ^0123456789.,!?_#'\"/\\-:()"[index]; + +}/* alphabet */ + +/* + * load_string + * + * Copy a ZSCII string from the memory to the global "decoded" string. + * + */ + +static void load_string (zword addr, zword length) +{ + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + while (i < 3 * resolution) + + if (i < length) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + decoded[i++] = translate_from_zscii (c); + + } else decoded[i++] = 0; + +}/* load_string */ + +/* + * encode_text + * + * Encode the Unicode text in the global "decoded" string then write + * the result to the global "encoded" array. (This is used to look up + * words in the dictionary.) Up to V3 the vocabulary resolution is + * two, since V4 it is three words. Because each word contains three + * Z-characters, that makes six or nine Z-characters respectively. + * Longer words are chopped to the proper size, shorter words are are + * padded out with 5's. For word completion we pad with 0s and 31s, + * the minimum and maximum Z-characters. + * + */ + +static void encode_text (int padding) +{ + static zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0 }; + static zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0 }; + static zchar wait[] = { 'w', 'a', 'i', 't', 0 }; + + zbyte zchars[12]; + const zchar *ptr = decoded; + zchar c; + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + /* Expand abbreviations that some old Infocom games lack */ + + if (option_expand_abbreviations) + + if (padding == 0x05 && decoded[1] == 0) + + switch (decoded[0]) { + case 'g': ptr = again; break; + case 'x': ptr = examine; break; + case 'z': ptr = wait; break; + } + + /* Translate string to a sequence of Z-characters */ + + while (i < 3 * resolution) + + if ((c = *ptr++) != 0) { + + int index, set; + zbyte c2; + + /* Search character in the alphabet */ + + for (set = 0; set < 3; set++) + for (index = 0; index < 26; index++) + if (c == alphabet (set, index)) + goto letter_found; + + /* Character not found, store its ZSCII value */ + + c2 = translate_to_zscii (c); + + zchars[i++] = 5; + zchars[i++] = 6; + zchars[i++] = c2 >> 5; + zchars[i++] = c2 & 0x1f; + + continue; + + letter_found: + + /* Character found, store its index */ + + if (set != 0) + zchars[i++] = ((h_version <= V2) ? 1 : 3) + set; + + zchars[i++] = index + 6; + + } else zchars[i++] = padding; + + /* Three Z-characters make a 16bit word */ + + for (i = 0; i < resolution; i++) + + encoded[i] = + (zchars[3 * i + 0] << 10) | + (zchars[3 * i + 1] << 5) | + (zchars[3 * i + 2]); + + encoded[resolution - 1] |= 0x8000; + +}/* encode_text */ + +/* + * z_check_unicode, test if a unicode character can be read and printed. + * + * zargs[0] = Unicode + * + */ + +void z_check_unicode (void) +{ + zword c = zargs[0]; + + if (c >= 0x20 && c <= 0x7e) + store (3); + else if (c == 0xa0) + store (1); + else if (c >= 0xa1 && c <= 0xff) + store (3); + else + store (0); + +}/* z_check_unicode */ + +/* + * z_encode_text, encode a ZSCII string for use in a dictionary. + * + * zargs[0] = address of text buffer + * zargs[1] = length of ASCII string + * zargs[2] = offset of ASCII string within the text buffer + * zargs[3] = address to store encoded text in + * + * This is a V5+ opcode and therefore the dictionary resolution must be + * three 16bit words. + * + */ + +void z_encode_text (void) +{ + int i; + + load_string ((zword) (zargs[0] + zargs[2]), zargs[1]); + + encode_text (0x05); + + for (i = 0; i < 3; i++) + storew ((zword) (zargs[3] + 2 * i), encoded[i]); + +}/* z_encode_text */ + +/* + * decode_text + * + * Convert encoded text to Unicode. The encoded text consists of 16bit + * words. Every word holds 3 Z-characters (5 bits each) plus a spare + * bit to mark the last word. The Z-characters translate to ZSCII by + * looking at the current current character set. Some select another + * character set, others refer to abbreviations. + * + * There are several different string types: + * + * LOW_STRING - from the lower 64KB (byte address) + * ABBREVIATION - from the abbreviations table (word address) + * HIGH_STRING - from the end of the memory map (packed address) + * EMBEDDED_STRING - from the instruction stream (at PC) + * VOCABULARY - from the dictionary (byte address) + * + * The last type is only used for word completion. + * + */ + +#define outchar(c) if (st==VOCABULARY) *ptr++=c; else print_char(c) + +static void decode_text (enum string_type st, zword addr) +{ + zchar *ptr; + long byte_addr; + zchar c2; + zword code; + zbyte c, prev_c = 0; + int shift_state = 0; + int shift_lock = 0; + int status = 0; + + /* Calculate the byte address if necessary */ + + if (st == ABBREVIATION) + + byte_addr = (long) addr << 1; + + else if (st == HIGH_STRING) { + + if (h_version <= V3) + byte_addr = (long) addr << 1; + else if (h_version <= V5) + byte_addr = (long) addr << 2; + else if (h_version <= V7) + byte_addr = ((long) addr << 2) + ((long) h_strings_offset << 3); + else /* h_version == V8 */ + byte_addr = (long) addr << 3; + + if (byte_addr >= story_size) + runtime_error ("Print at illegal address"); + + } + + /* Loop until a 16bit word has the highest bit set */ + + if (st == VOCABULARY) + ptr = decoded; + + do { + + int i; + + /* Fetch the next 16bit word */ + + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD (addr, code) + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD (byte_addr, code) + byte_addr += 2; + } else + CODE_WORD (code) + + /* Read its three Z-characters */ + + for (i = 10; i >= 0; i -= 5) { + + zword abbr_addr; + zword ptr_addr; + + c = (code >> i) & 0x1f; + + switch (status) { + + case 0: /* normal operation */ + + if (shift_state == 2 && c == 6) + status = 2; + + else if (h_version == V1 && c == 1) + new_line (); + + else if (h_version >= V2 && shift_state == 2 && c == 7) + new_line (); + + else if (c >= 6) + outchar (alphabet (shift_state, c - 6)); + + else if (c == 0) + outchar (' '); + + else if (h_version >= V2 && c == 1) + status = 1; + + else if (h_version >= V3 && c <= 3) + status = 1; + + else { + + shift_state = (shift_lock + (c & 1) + 1) % 3; + + if (h_version <= V2 && c >= 4) + shift_lock = shift_state; + + break; + + } + + shift_state = shift_lock; + + break; + + case 1: /* abbreviation */ + + ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c; + + LOW_WORD (ptr_addr, abbr_addr) + decode_text (ABBREVIATION, abbr_addr); + + status = 0; + break; + + case 2: /* ZSCII character - first part */ + + status = 3; + break; + + case 3: /* ZSCII character - second part */ + + c2 = translate_from_zscii ((prev_c << 5) | c); + outchar (c2); + + status = 0; + break; + + } + + prev_c = c; + + } + + } while (!(code & 0x8000)); + + if (st == VOCABULARY) + *ptr = 0; + +}/* decode_text */ + +#undef outchar + +/* + * z_new_line, print a new line. + * + * no zargs used + * + */ + +void z_new_line (void) +{ + + new_line (); + +}/* z_new_line */ + +/* + * z_print, print a string embedded in the instruction stream. + * + * no zargs used + * + */ + +void z_print (void) +{ + + decode_text (EMBEDDED_STRING, 0); + +}/* z_print */ + +/* + * z_print_addr, print a string from the lower 64KB. + * + * zargs[0] = address of string to print + * + */ + +void z_print_addr (void) +{ + + decode_text (LOW_STRING, zargs[0]); + +}/* z_print_addr */ + +/* + * z_print_char print a single ZSCII character. + * + * zargs[0] = ZSCII character to be printed + * + */ + +void z_print_char (void) +{ + + print_char (translate_from_zscii (zargs[0])); + +}/* z_print_char */ + +/* + * z_print_form, print a formatted table. + * + * zargs[0] = address of formatted table to be printed + * + */ + +void z_print_form (void) +{ + zword count; + zword addr = zargs[0]; + + bool first = TRUE; + + for (;;) { + + LOW_WORD (addr, count) + addr += 2; + + if (count == 0) + break; + + if (!first) + new_line (); + + while (count--) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (translate_from_zscii (c)); + + } + + first = FALSE; + + } + +}/* z_print_form */ + +/* + * print_num + * + * Print a signed 16bit number. + * + */ + +void print_num (zword value) +{ + int i; + + /* Print sign */ + + if ((short) value < 0) { + print_char ('-'); + value = - (short) value; + } + + /* Print absolute value */ + + for (i = 10000; i != 0; i /= 10) + if (value >= i || i == 1) + print_char ('0' + (value / i) % 10); + +}/* print_num */ + +/* + * z_print_num, print a signed number. + * + * zargs[0] = number to print + * + */ + +void z_print_num (void) +{ + + print_num (zargs[0]); + +}/* z_print_num */ + +/* + * print_object + * + * Print an object description. + * + */ + +void print_object (zword object) +{ + zword addr = object_name (object); + zword code = 0x94a5; + zbyte length; + + LOW_BYTE (addr, length) + addr++; + + if (length != 0) + LOW_WORD (addr, code) + + if (code == 0x94a5) { /* encoded text 0x94a5 == empty string */ + + print_string ("object#"); /* supply a generic name */ + print_num (object); /* for anonymous objects */ + + } else decode_text (LOW_STRING, addr); + +}/* print_object */ + +/* + * z_print_obj, print an object description. + * + * zargs[0] = number of object to be printed + * + */ + +void z_print_obj (void) +{ + + print_object (zargs[0]); + +}/* z_print_obj */ + +/* + * z_print_paddr, print the string at the given packed address. + * + * zargs[0] = packed address of string to be printed + * + */ + +void z_print_paddr (void) +{ + + decode_text (HIGH_STRING, zargs[0]); + +}/* z_print_paddr */ + +/* + * z_print_ret, print the string at PC, print newline then return true. + * + * no zargs used + * + */ + +void z_print_ret (void) +{ + + decode_text (EMBEDDED_STRING, 0); + new_line (); + ret (1); + +}/* z_print_ret */ + +/* + * print_string + * + * Print a string of ASCII characters. + * + */ + +void print_string (const char *s) +{ + char c; + + while ((c = *s++) != 0) + + if (c == '\n') + new_line (); + else + print_char (c); + +}/* print_string */ + +/* + * z_print_unicode + * + * zargs[0] = Unicode + * + */ + +void z_print_unicode (void) +{ + + print_char ((zargs[0] <= 0xff) ? zargs[0] : '?'); + +}/* z_print_unicode */ + +/* + * lookup_text + * + * Scan a dictionary searching for the given word. The first argument + * can be + * + * 0x00 - find the first word which is >= the given one + * 0x05 - find the word which exactly matches the given one + * 0x1f - find the last word which is <= the given one + * + * The return value is 0 if the search fails. + * + */ + +static zword lookup_text (int padding, zword dct) +{ + zword entry_addr; + zword entry_count; + zword entry; + zword addr; + zbyte entry_len; + zbyte sep_count; + int resolution = (h_version <= V3) ? 2 : 3; + int entry_number; + int lower, upper; + int i; + bool sorted; + + encode_text (padding); + + LOW_BYTE (dct, sep_count) /* skip word separators */ + dct += 1 + sep_count; + LOW_BYTE (dct, entry_len) /* get length of entries */ + dct += 1; + LOW_WORD (dct, entry_count) /* get number of entries */ + dct += 2; + + if ((short) entry_count < 0) { /* bad luck, entries aren't sorted */ + + entry_count = - (short) entry_count; + sorted = FALSE; + + } else sorted = TRUE; /* entries are sorted */ + + lower = 0; + upper = entry_count - 1; + + while (lower <= upper) { + + if (sorted) /* binary search */ + entry_number = (lower + upper) / 2; + else /* linear search */ + entry_number = lower; + + entry_addr = dct + entry_number * entry_len; + + /* Compare word to dictionary entry */ + + addr = entry_addr; + + for (i = 0; i < resolution; i++) { + LOW_WORD (addr, entry) + if (encoded[i] != entry) + goto continuing; + addr += 2; + } + + return entry_addr; /* exact match found, return now */ + + continuing: + + if (sorted) /* binary search */ + + if (encoded[i] > entry) + lower = entry_number + 1; + else + upper = entry_number - 1; + + else lower++; /* linear search */ + + } + + /* No exact match has been found */ + + if (padding == 0x05) + return 0; + + entry_number = (padding == 0x00) ? lower : upper; + + if (entry_number == -1 || entry_number == entry_count) + return 0; + + return dct + entry_number * entry_len; + +}/* lookup_text */ + +/* + * tokenise_text + * + * Translate a single word to a token and append it to the token + * buffer. Every token consists of the address of the dictionary + * entry, the length of the word and the offset of the word from + * the start of the text buffer. Unknown words cause empty slots + * if the flag is set (such that the text can be scanned several + * times with different dictionaries); otherwise they are zero. + * + */ + +static void tokenise_text (zword text, zword length, zword from, zword parse, zword dct, bool flag) +{ + zword addr; + zbyte token_max, token_count; + + LOW_BYTE (parse, token_max) + parse++; + LOW_BYTE (parse, token_count) + + if (token_count < token_max) { /* sufficient space left for token? */ + + storeb (parse++, token_count + 1); + + load_string ((zword) (text + from), length); + + addr = lookup_text (0x05, dct); + + if (addr != 0 || !flag) { + + parse += 4 * token_count; + + storew ((zword) (parse + 0), addr); + storeb ((zword) (parse + 2), length); + storeb ((zword) (parse + 3), from); + + } + + } + +}/* tokenise_text */ + +/* + * tokenise_line + * + * Split an input line into words and translate the words to tokens. + * + */ + +void tokenise_line (zword text, zword token, zword dct, bool flag) +{ + zword addr1; + zword addr2; + zbyte length; + zbyte c; + + /* Use standard dictionary if the given dictionary is zero */ + + if (dct == 0) + dct = h_dictionary; + + /* Remove all tokens before inserting new ones */ + + storeb ((zword) (token + 1), 0); + + /* Move the first pointer across the text buffer searching for the + beginning of a word. If this succeeds, store the position in a + second pointer. Move the first pointer searching for the end of + the word. When it is found, "tokenise" the word. Continue until + the end of the buffer is reached. */ + + addr1 = text; + addr2 = 0; + + if (h_version >= V5) { + addr1++; + LOW_BYTE (addr1, length) + } + + do { + + zword sep_addr; + zbyte sep_count; + zbyte separator; + + /* Fetch next ZSCII character */ + + addr1++; + + if (h_version >= V5 && addr1 == text + 2 + length) + c = 0; + else + LOW_BYTE (addr1, c) + + /* Check for separator */ + + sep_addr = dct; + + LOW_BYTE (sep_addr, sep_count) + sep_addr++; + + do { + + LOW_BYTE (sep_addr, separator) + sep_addr++; + + } while (c != separator && --sep_count != 0); + + /* This could be the start or the end of a word */ + + if (sep_count == 0 && c != ' ' && c != 0) { + + if (addr2 == 0) + addr2 = addr1; + + } else if (addr2 != 0) { + + tokenise_text ( + text, + (zword) (addr1 - addr2), + (zword) (addr2 - text), + token, dct, flag ); + + addr2 = 0; + + } + + /* Translate separator (which is a word in its own right) */ + + if (sep_count != 0) + + tokenise_text ( + text, + (zword) (1), + (zword) (addr1 - text), + token, dct, flag ); + + } while (c != 0); + +}/* tokenise_line */ + +/* + * z_tokenise, make a lexical analysis of a ZSCII string. + * + * zargs[0] = address of string to analyze + * zargs[1] = address of token buffer + * zargs[2] = address of dictionary (optional) + * zargs[3] = set when unknown words cause empty slots (optional) + * + */ + +void z_tokenise (void) +{ + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + if (zargc < 4) + zargs[3] = 0; + + /* Call tokenise_line to do the real work */ + + tokenise_line (zargs[0], zargs[1], zargs[2], zargs[3] != 0); + +}/* z_tokenise */ + +/* + * completion + * + * Scan the vocabulary to complete the last word on the input line + * (similar to "tcsh" under Unix). The return value is + * + * 2 ==> completion is impossible + * 1 ==> completion is ambiguous + * 0 ==> completion is successful + * + * The function also returns a string in its second argument. In case + * of 2, the string is empty; in case of 1, the string is the longest + * extension of the last word on the input line that is common to all + * possible completions (for instance, if the last word on the input + * is "fo" and its only possible completions are "follow" and "folly" + * then the string is "ll"); in case of 0, the string is an extension + * to the last word that results in the only possible completion. + * + */ + +int completion (const zchar *buffer, zchar *result) +{ + zword minaddr; + zword maxaddr; + zchar *ptr; + zchar c; + int len; + int i; + + *result = 0; + + /* Copy last word to "decoded" string */ + + len = 0; + + while ((c = *buffer++) != 0) + + if (c != ' ') { + + if (len < 9) + decoded[len++] = c; + + } else len = 0; + + decoded[len] = 0; + + /* Search the dictionary for first and last possible extensions */ + + minaddr = lookup_text (0x00, h_dictionary); + maxaddr = lookup_text (0x1f, h_dictionary); + + if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr) + return 2; + + /* Copy first extension to "result" string */ + + decode_text (VOCABULARY, minaddr); + + ptr = result; + + for (i = len; (c = decoded[i]) != 0; i++) + *ptr++ = c; + *ptr = 0; + + /* Merge second extension with "result" string */ + + decode_text (VOCABULARY, maxaddr); + + for (i = len, ptr = result; (c = decoded[i]) != 0; i++, ptr++) + if (*ptr != c) break; + *ptr = 0; + + /* Search was ambiguous or successful */ + + return (minaddr == maxaddr) ? 0 : 1; + +}/* completion */ diff --git a/variable.c b/variable.c new file mode 100644 index 0000000..86a663b --- /dev/null +++ b/variable.c @@ -0,0 +1,291 @@ +/* + * variable.c + * + * Variable and stack related opcodes + * + */ + +#include "frotz.h" + +/* + * z_dec, decrement a variable. + * + * zargs[0] = variable to decrement + * + */ + +void z_dec (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)--; + else if (zargs[0] < 16) + (*(fp - zargs[0]))--; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + +}/* z_dec */ + +/* + * z_dec_chk, decrement a variable and branch if now less than value. + * + * zargs[0] = variable to decrement + * zargs[1] = value to check variable against + * + */ + +void z_dec_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = --(*sp); + else if (zargs[0] < 16) + value = --(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + + branch ((short) value < (short) zargs[1]); + +}/* z_dec_chk */ + +/* + * z_inc, increment a variable. + * + * zargs[0] = variable to increment + * + */ + +void z_inc (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)++; + else if (zargs[0] < 16) + (*(fp - zargs[0]))++; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + +}/* z_inc */ + +/* + * z_inc_chk, increment a variable and branch if now greater than value. + * + * zargs[0] = variable to increment + * zargs[1] = value to check variable against + * + */ + +void z_inc_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = ++(*sp); + else if (zargs[0] < 16) + value = ++(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + + branch ((short) value > (short) zargs[1]); + +}/* z_inc_chk */ + +/* + * z_load, store the value of a variable. + * + * zargs[0] = variable to store + * + */ + +void z_load (void) +{ + zword value; + + if (zargs[0] == 0) + value = *sp; + else if (zargs[0] < 16) + value = *(fp - zargs[0]); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + } + + store (value); + +}/* z_load */ + +/* + * z_pop, pop a value off the game stack and discard it. + * + * no zargs used + * + */ + +void z_pop (void) +{ + + sp++; + +}/* z_pop */ + +/* + * z_pop_stack, pop n values off the game or user stack and discard them. + * + * zargs[0] = number of values to discard + * zargs[1] = address of user stack (optional) + * + */ + +void z_pop_stack (void) +{ + + if (zargc == 2) { /* it's a user stack */ + + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + size += zargs[0]; + storew (addr, size); + + } else sp += zargs[0]; /* it's the game stack */ + +}/* z_pop_stack */ + +/* + * z_pull, pop a value off... + * + * a) ...the game or a user stack and store it (V6) + * + * zargs[0] = address of user stack (optional) + * + * b) ...the game stack and write it to a variable (other than V6) + * + * zargs[0] = variable to write value to + * + */ + +void z_pull (void) +{ + zword value; + + if (h_version != V6) { /* not a V6 game, pop stack and write */ + + value = *sp++; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + + } else { /* it's V6, but is there a user stack? */ + + if (zargc == 1) { /* it's a user stack */ + + zword size; + zword addr = zargs[0]; + + LOW_WORD (addr, size) + + size++; + storew (addr, size); + + addr += 2 * size; + LOW_WORD (addr, value) + + } else value = *sp++; /* it's the game stack */ + + store (value); + + } + +}/* z_pull */ + +/* + * z_push, push a value onto the game stack. + * + * zargs[0] = value to push onto the stack + * + */ + +void z_push (void) +{ + + *--sp = zargs[0]; + +}/* z_push */ + +/* + * z_push_stack, push a value onto a user stack then branch if successful. + * + * zargs[0] = value to push onto the stack + * zargs[1] = address of user stack + * + */ + +void z_push_stack (void) +{ + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + if (size != 0) { + + storew ((zword) (addr + 2 * size), zargs[0]); + + size--; + storew (addr, size); + + } + + branch (size); + +}/* z_push_stack */ + +/* + * z_store, write a value to a variable. + * + * zargs[0] = variable to be written to + * zargs[1] = value to write + * + */ + +void z_store (void) +{ + zword value = zargs[1]; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + +}/* z_store */