--- /dev/null
+/*
+ * "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 *);
--- /dev/null
+/*
+ * file "BCinit.c"
+ *
+ * Borland C front end, initialisation
+ *
+ */
+
+#include <conio.h>
+#include <dos.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 */
--- /dev/null
+/*
+ * file "BCinput.c"
+ *
+ * Borland C front end, input functions
+ *
+ */
+
+#include <bios.h>
+#include <string.h>
+#include <stdio.h>
+#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 */
--- /dev/null
+/*
+ * file "BCmouse.c"
+ *
+ * Borland C front end, mouse support
+ *
+ */
+
+#include <dos.h>
+#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 */
--- /dev/null
+/*
+ * "BCpic.c"
+ *
+ * Borland C front end, picture functions
+ *
+ */
+
+#include <alloc.h>
+#include <dos.h>
+#include <stdio.h>
+#include <string.h>
+#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 */
--- /dev/null
+/*
+ * file "BCsample.c"
+ *
+ * Borland C front end, sound support
+ *
+ */
+
+#include <alloc.h>
+#include <dos.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#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 */
--- /dev/null
+/*
+ * file "BCscreen.c"
+ *
+ * Borland C front end, screen manipulation
+ *
+ */
+
+#include <dos.h>
+#include <mem.h>
+#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 */
--- /dev/null
+/*
+ * file "BCtext.c"
+ *
+ * Borland C front end, text functions
+ *
+ */
+
+#include <alloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <conio.h>
+#include <dos.h>
+#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<Y | S '' C a << not- R _ "
+ "^0 +/-^2 ^3 ' my P . , ^1 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * fastmem.c
+ *
+ * Memory related functions (fast version without virtual memory)
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "frotz.h"
+
+#ifdef __MSDOS__
+
+#include <alloc.h>
+
+#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 <stdlib.h>
+
+#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 */
--- /dev/null
+/*
+ * files.c
+ *
+ * Transscription, recording and playback
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#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 */
--- /dev/null
+/*
+ * 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 *);
--- /dev/null
+/*
+ * getopt.c
+ *
+ * Replacement for a Unix style getopt function
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+
+
+
+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!
+
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */