Released Frotz232Src.zip
authorStefan Jokisch <stefan.jokisch@gmx.de>
Wed, 7 Dec 2011 15:46:49 +0000 (15:46 +0000)
committerStefan Jokisch <stefan.jokisch@gmx.de>
Wed, 7 Dec 2011 15:46:49 +0000 (15:46 +0000)
29 files changed:
bcfrotz.h [new file with mode: 0644]
bcinit.c [new file with mode: 0644]
bcinput.c [new file with mode: 0644]
bcmouse.c [new file with mode: 0644]
bcpic.c [new file with mode: 0644]
bcsample.c [new file with mode: 0644]
bcscreen.c [new file with mode: 0644]
bctext.c [new file with mode: 0644]
buffer.c [new file with mode: 0644]
fastmem.c [new file with mode: 0644]
files.c [new file with mode: 0644]
font.dat [new file with mode: 0644]
frotz.h [new file with mode: 0644]
getopt.c [new file with mode: 0644]
hotkey.c [new file with mode: 0644]
input.c [new file with mode: 0644]
main.c [new file with mode: 0644]
math.c [new file with mode: 0644]
object.c [new file with mode: 0644]
process.c [new file with mode: 0644]
random.c [new file with mode: 0644]
readme.txt [new file with mode: 0644]
redirect.c [new file with mode: 0644]
screen.c [new file with mode: 0644]
sound.c [new file with mode: 0644]
stream.c [new file with mode: 0644]
table.c [new file with mode: 0644]
text.c [new file with mode: 0644]
variable.c [new file with mode: 0644]

diff --git a/bcfrotz.h b/bcfrotz.h
new file mode 100644 (file)
index 0000000..70c99a1
--- /dev/null
+++ b/bcfrotz.h
@@ -0,0 +1,85 @@
+/*
+ * "BCfrotz.h"
+ *
+ * Borland C interface, declarations
+ *
+ */
+
+#define byte0(v)       ((byte *)&v)[0]
+#define byte1(v)       ((byte *)&v)[1]
+#define byte2(v)       ((byte *)&v)[2]
+#define byte3(v)       ((byte *)&v)[3]
+#define word0(v)       ((word *)&v)[0]
+#define word1(v)       ((word *)&v)[1]
+
+#ifndef HISTORY_MIN_ENTRY
+#define HISTORY_MIN_ENTRY 1
+#endif
+
+#define SPECIAL_KEY_MIN 256
+#define SPECIAL_KEY_HOME 256
+#define SPECIAL_KEY_END 257
+#define SPECIAL_KEY_WORD_LEFT 258
+#define SPECIAL_KEY_WORD_RIGHT 259
+#define SPECIAL_KEY_DELETE 260
+#define SPECIAL_KEY_INSERT 261
+#define SPECIAL_KEY_PAGE_UP 262
+#define SPECIAL_KEY_PAGE_DOWN 263
+#define SPECIAL_KEY_TAB 264
+#define SPECIAL_KEY_MAX 264
+
+#define _MONO_ 0
+#define _TEXT_ 1
+#define _CGA_  2
+#define _MCGA_ 3
+#define _EGA_  4
+#define _AMIGA_        5
+
+typedef unsigned char byte;
+typedef unsigned short word;
+
+extern display;
+
+extern cursor_x;
+extern cursor_y;
+
+extern char latin1_to_ibm[];
+extern char latin1_to_ascii[];
+
+extern byte text_bg;
+extern byte text_fg;
+
+extern byte scrn_attr;
+
+extern user_background;
+extern user_foreground;
+extern user_emphasis;
+extern user_reverse_bg;
+extern user_reverse_fg;
+extern user_screen_height;
+extern user_screen_width;
+extern user_tandy_bit;
+extern user_bold_typing;
+extern user_random_seed;
+extern user_font;
+
+extern char stripped_story_name[];
+extern char *prog_name;
+
+extern current_bg;
+extern current_fg;
+extern current_style;
+extern current_font;
+
+extern scaler;
+
+/* BCinit  */  int     dectoi (const char *);
+/* BCinit  */  int     hextoi (const char *);
+/* BCmouse */  bool    detect_mouse (void);
+/* BCmouse */  int     read_mouse (void);
+/* BCpic   */  bool    init_pictures (void);
+/* BCpic   */  void    reset_pictures (void);
+/* BCsmpl  */  bool    init_sound (void);
+/* BCsmpl  */  void    reset_sound (void);
+/* BCtext  */  void    switch_scrn_attr (bool);
+/* BCtext  */  void    load_fonts (const char *);
diff --git a/bcinit.c b/bcinit.c
new file mode 100644 (file)
index 0000000..3fba0d9
--- /dev/null
+++ b/bcinit.c
@@ -0,0 +1,755 @@
+/*
+ * 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 */
diff --git a/bcinput.c b/bcinput.c
new file mode 100644 (file)
index 0000000..04c6e0f
--- /dev/null
+++ b/bcinput.c
@@ -0,0 +1,989 @@
+/*
+ * 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 */
diff --git a/bcmouse.c b/bcmouse.c
new file mode 100644 (file)
index 0000000..ea8444b
--- /dev/null
+++ b/bcmouse.c
@@ -0,0 +1,75 @@
+/*
+ * 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 */
diff --git a/bcpic.c b/bcpic.c
new file mode 100644 (file)
index 0000000..59115a9
--- /dev/null
+++ b/bcpic.c
@@ -0,0 +1,677 @@
+/*
+ * "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 */
diff --git a/bcsample.c b/bcsample.c
new file mode 100644 (file)
index 0000000..bb50b23
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * 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 */
diff --git a/bcscreen.c b/bcscreen.c
new file mode 100644 (file)
index 0000000..ed42e2e
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * 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 */
diff --git a/bctext.c b/bctext.c
new file mode 100644 (file)
index 0000000..4ab9100
--- /dev/null
+++ b/bctext.c
@@ -0,0 +1,842 @@
+/*
+ * 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 */
diff --git a/buffer.c b/buffer.c
new file mode 100644 (file)
index 0000000..6aa48a1
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,112 @@
+/*
+ * buffer.c
+ *
+ * Text buffering and word wrapping
+ *
+ */
+
+#include "frotz.h"
+
+extern void stream_char (zchar);
+extern void stream_word (const zchar *);
+extern void stream_new_line (void);
+
+static zchar buffer[TEXT_BUFFER_SIZE];
+static bufpos = 0;
+
+static zchar prev_c = 0;
+
+/*
+ * flush_buffer
+ *
+ * Copy the contents of the text buffer to the output streams.
+ *
+ */
+
+void flush_buffer (void)
+{
+    static bool locked = FALSE;
+
+    /* Make sure we stop when flush_buffer is called from flush_buffer.
+       Note that this is difficult to avoid as we might print a newline
+       during flush_buffer, which might cause a newline interrupt, that
+       might execute any arbitrary opcode, which might flush the buffer. */
+
+    if (locked || bufpos == 0)
+       return;
+
+    /* Send the buffer to the output streams */
+
+    buffer[bufpos] = 0;
+
+    locked = TRUE; stream_word (buffer); locked = FALSE;
+
+    /* Reset the buffer */
+
+    bufpos = 0;
+    prev_c = 0;
+
+}/* flush_buffer */
+
+/*
+ * print_char
+ *
+ * High level output function.
+ *
+ */
+
+void print_char (zchar c)
+{
+    static bool flag = FALSE;
+
+    if (message || ostream_memory || enable_buffering) {
+
+       if (!flag) {
+
+           /* Characters 0 and ZC_RETURN are special cases */
+
+           if (c == ZC_RETURN)
+               { new_line (); return; }
+           if (c == 0)
+               return;
+
+           /* Flush the buffer before a whitespace or after a hyphen */
+
+           if (c == ' ' || c == ZC_INDENT || c == ZC_GAP || prev_c == '-' && c != '-')
+               flush_buffer ();
+
+           /* Set the flag if this is part one of a style or font change */
+
+           if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE)
+               flag = TRUE;
+
+           /* Remember the current character code */
+
+           prev_c = c;
+
+       } else flag = FALSE;
+
+       /* Insert the character into the buffer */
+
+       buffer[bufpos++] = c;
+
+       if (bufpos == TEXT_BUFFER_SIZE)
+           runtime_error ("Text buffer overflow");
+
+    } else stream_char (c);
+
+}/* print_char */
+
+/*
+ * new_line
+ *
+ * High level newline function.
+ *
+ */
+
+void new_line (void)
+{
+
+    flush_buffer (); stream_new_line ();
+
+}/* new_line */
diff --git a/fastmem.c b/fastmem.c
new file mode 100644 (file)
index 0000000..d121f1b
--- /dev/null
+++ b/fastmem.c
@@ -0,0 +1,910 @@
+/*
+ * 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 */
diff --git a/files.c b/files.c
new file mode 100644 (file)
index 0000000..784f04d
--- /dev/null
+++ b/files.c
@@ -0,0 +1,555 @@
+/*
+ * 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 */
diff --git a/font.dat b/font.dat
new file mode 100644 (file)
index 0000000..fe9799c
Binary files /dev/null and b/font.dat differ
diff --git a/frotz.h b/frotz.h
new file mode 100644 (file)
index 0000000..93a2c96
--- /dev/null
+++ b/frotz.h
@@ -0,0 +1,597 @@
+/*
+ * frotz.h
+ *
+ * Global declarations and definitions
+ *
+ */
+
+typedef int bool;
+
+#define TRUE 1
+#define FALSE 0
+
+typedef unsigned char zbyte;
+typedef unsigned short zword;
+
+enum story {
+    BEYOND_ZORK,
+    SHERLOCK,
+    ZORK_ZERO,
+    SHOGUN,
+    ARTHUR,
+    JOURNEY,
+    LURKING_HORROR,
+    UNKNOWN
+};
+
+typedef unsigned char zchar;
+
+/*** Constants that may be set at compile time ***/
+
+#ifndef MAX_UNDO_SLOTS
+#define MAX_UNDO_SLOTS 25
+#endif
+#ifndef MAX_FILE_NAME
+#define MAX_FILE_NAME 80
+#endif
+#ifndef TEXT_BUFFER_SIZE
+#define TEXT_BUFFER_SIZE 200
+#endif
+#ifndef INPUT_BUFFER_SIZE
+#define INPUT_BUFFER_SIZE 200
+#endif
+#ifndef STACK_SIZE
+#define STACK_SIZE 1024
+#endif
+
+#ifndef DEFAULT_SAVE_NAME
+#define DEFAULT_SAVE_NAME "story.sav"
+#endif
+#ifndef DEFAULT_SCRIPT_NAME
+#define DEFAULT_SCRIPT_NAME "story.scr"
+#endif
+#ifndef DEFAULT_COMMAND_NAME
+#define DEFAULT_COMMAND_NAME "story.rec"
+#endif
+#ifndef DEFAULT_AUXILARY_NAME
+#define DEFAULT_AUXILARY_NAME "story.aux"
+#endif
+
+/*** Story file header format ***/
+
+#define H_VERSION 0
+#define H_CONFIG 1
+#define H_RELEASE 2
+#define H_RESIDENT_SIZE 4
+#define H_START_PC 6
+#define H_DICTIONARY 8
+#define H_OBJECTS 10
+#define H_GLOBALS 12
+#define H_DYNAMIC_SIZE 14
+#define H_FLAGS 16
+#define H_SERIAL 18
+#define H_ABBREVIATIONS 24
+#define H_FILE_SIZE 26
+#define H_CHECKSUM 28
+#define H_INTERPRETER_NUMBER 30
+#define H_INTERPRETER_VERSION 31
+#define H_SCREEN_ROWS 32
+#define H_SCREEN_COLS 33
+#define H_SCREEN_WIDTH 34
+#define H_SCREEN_HEIGHT 36
+#define H_FONT_HEIGHT 38 /* this is the font width in V5 */
+#define H_FONT_WIDTH 39 /* this is the font height in V5 */
+#define H_FUNCTIONS_OFFSET 40
+#define H_STRINGS_OFFSET 42
+#define H_DEFAULT_BACKGROUND 44
+#define H_DEFAULT_FOREGROUND 45
+#define H_TERMINATING_KEYS 46
+#define H_LINE_WIDTH 48
+#define H_STANDARD_HIGH 50
+#define H_STANDARD_LOW 51
+#define H_ALPHABET 52
+#define H_EXTENSION_TABLE 54
+#define H_USER_NAME 56
+
+#define HX_TABLE_SIZE 0
+#define HX_MOUSE_X 1
+#define HX_MOUSE_Y 2
+#define HX_UNICODE_TABLE 3
+
+/*** Various Z-machine constants ***/
+
+#define V1 1
+#define V2 2
+#define V3 3
+#define V4 4
+#define V5 5
+#define V6 6
+#define V7 7
+#define V8 8
+
+#define CONFIG_BYTE_SWAPPED 0x01 /* Story file is byte swapped         - V3  */
+#define CONFIG_TIME         0x02 /* Status line displays time          - V3  */
+#define CONFIG_TWODISKS     0x04 /* Story file occupied two disks      - V3  */
+#define CONFIG_TANDY        0x08 /* Tandy licensed game                - V3  */
+#define CONFIG_NOSTATUSLINE 0x10 /* Interpr can't support status lines - V3  */
+#define CONFIG_SPLITSCREEN  0x20 /* Interpr supports split screen mode - V3  */
+#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font     - V3  */
+
+#define CONFIG_COLOUR       0x01 /* Interpr supports colour            - V5+ */
+#define CONFIG_PICTURES            0x02 /* Interpr supports pictures          - V6  */
+#define CONFIG_BOLDFACE     0x04 /* Interpr supports boldface style    - V4+ */
+#define CONFIG_EMPHASIS     0x08 /* Interpr supports emphasis style    - V4+ */
+#define CONFIG_FIXED        0x10 /* Interpr supports fixed width style - V4+ */
+#define CONFIG_TIMEDINPUT   0x80 /* Interpr supports timed input       - V4+ */
+
+#define SCRIPTING_FLAG   0x0001 /* Outputting to transscription file  - V1+ */
+#define FIXED_FONT_FLAG   0x0002 /* Use fixed width font               - V3+ */
+#define REFRESH_FLAG     0x0004 /* Refresh the screen                 - V6  */
+#define GRAPHICS_FLAG    0x0008 /* Game wants to use graphics         - V5+ */
+#define OLD_SOUND_FLAG   0x0010 /* Game wants to use sound effects    - V3  */
+#define UNDO_FLAG        0x0010 /* Game wants to use UNDO feature     - V5+ */
+#define MOUSE_FLAG       0x0020 /* Game wants to use a mouse          - V5+ */
+#define COLOUR_FLAG      0x0040 /* Game wants to use colours          - V5+ */
+#define SOUND_FLAG       0x0080 /* Game wants to use sound effects    - V5+ */
+#define MENU_FLAG        0x0100 /* Game wants to use menus            - V6  */
+
+#define INTERP_DEC_20 1
+#define INTERP_APPLE_IIE 2
+#define INTERP_MACINTOSH 3
+#define INTERP_AMIGA 4
+#define INTERP_ATARI_ST 5
+#define INTERP_MSDOS 6
+#define INTERP_CBM_128 7
+#define INTERP_CBM_64 8
+#define INTERP_APPLE_IIC 9
+#define INTERP_APPLE_IIGS 10
+#define INTERP_TANDY 11
+
+#define BLACK_COLOUR 2
+#define RED_COLOUR 3
+#define GREEN_COLOUR 4
+#define YELLOW_COLOUR 5
+#define BLUE_COLOUR 6
+#define MAGENTA_COLOUR 7
+#define CYAN_COLOUR 8
+#define WHITE_COLOUR 9
+#define GREY_COLOUR 10         /* INTERP_MSDOS only */
+#define LIGHTGREY_COLOUR 10    /* INTERP_AMIGA only */
+#define MEDIUMGREY_COLOUR 11   /* INTERP_AMIGA only */
+#define DARKGREY_COLOUR 12     /* INTERP_AMIGA only */
+
+#define REVERSE_STYLE 1
+#define BOLDFACE_STYLE 2
+#define EMPHASIS_STYLE 4
+#define FIXED_WIDTH_STYLE 8
+
+#define TEXT_FONT 1
+#define PICTURE_FONT 2
+#define GRAPHICS_FONT 3
+#define FIXED_WIDTH_FONT 4
+
+/*** Constants for os_restart_game */
+
+#define RESTART_BEGIN 0
+#define RESTART_WPROP_SET 1
+#define RESTART_END 2
+
+/*** Character codes ***/
+
+#define ZC_TIME_OUT 0x00
+#define ZC_NEW_STYLE 0x01
+#define ZC_NEW_FONT 0x02
+#define ZC_BACKSPACE 0x08
+#define ZC_INDENT 0x09
+#define ZC_GAP 0x0b
+#define ZC_RETURN 0x0d
+#define ZC_HKEY_MIN 0x0e
+#define ZC_HKEY_RECORD 0x0e
+#define ZC_HKEY_PLAYBACK 0x0f
+#define ZC_HKEY_SEED 0x10
+#define ZC_HKEY_UNDO 0x11
+#define ZC_HKEY_RESTART 0x12
+#define ZC_HKEY_QUIT 0x13
+#define ZC_HKEY_DEBUG 0x14
+#define ZC_HKEY_HELP 0x15
+#define ZC_HKEY_MAX 0x15
+#define ZC_ESCAPE 0x1b
+#define ZC_ASCII_MIN 0x20
+#define ZC_ASCII_MAX 0x7e
+#define ZC_BAD 0x7f
+#define ZC_ARROW_MIN 0x81
+#define ZC_ARROW_UP 0x81
+#define ZC_ARROW_DOWN 0x82
+#define ZC_ARROW_LEFT 0x83
+#define ZC_ARROW_RIGHT 0x84
+#define ZC_ARROW_MAX 0x84
+#define ZC_FKEY_MIN 0x85
+#define ZC_FKEY_MAX 0x90
+#define ZC_NUMPAD_MIN 0x91
+#define ZC_NUMPAD_MAX 0x9a
+#define ZC_SINGLE_CLICK 0x9b
+#define ZC_DOUBLE_CLICK 0x9c
+#define ZC_MENU_CLICK 0x9d
+#define ZC_LATIN1_MIN 0xa0
+#define ZC_LATIN1_MAX 0xff
+
+/*** File types ***/
+
+#define FILE_RESTORE 0
+#define FILE_SAVE 1
+#define FILE_SCRIPT 2
+#define FILE_PLAYBACK 3
+#define FILE_RECORD 4
+#define FILE_LOAD_AUX 5
+#define FILE_SAVE_AUX 6
+
+/*** Data access macros ***/
+
+#define SET_BYTE(addr,v)  { zmp[addr] = v; }
+#define LOW_BYTE(addr,v)  { v = zmp[addr]; }
+#define CODE_BYTE(v)     { v = *pcp++;    }
+
+#if defined (AMIGA)
+
+extern zbyte *pcp;
+extern zbyte *zmp;
+
+#define lo(v)  ((zbyte *)&v)[1]
+#define hi(v)  ((zbyte *)&v)[0]
+
+#define SET_WORD(addr,v)  { zmp[addr] = hi(v); zmp[addr+1] = lo(v); }
+#define LOW_WORD(addr,v)  { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; }
+#define HIGH_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; }
+#define CODE_WORD(v)      { hi(v) = *pcp++; lo(v) = *pcp++; }
+#define GET_PC(v)         { v = pcp - zmp; }
+#define SET_PC(v)         { pcp = zmp + v; }
+
+#endif
+
+#if defined (__MSDOS__)
+
+extern zbyte far *pcp;
+extern zbyte far *zmp;
+
+#define lo(v)  ((zbyte *)&v)[0]
+#define hi(v)  ((zbyte *)&v)[1]
+
+#define SET_WORD(addr,v) asm {\
+    les bx,zmp;\
+    add bx,addr;\
+    mov ax,v;\
+    xchg al,ah;\
+    mov es:[bx],ax }
+
+#define LOW_WORD(addr,v) asm {\
+    les bx,zmp;\
+    add bx,addr;\
+    mov ax,es:[bx];\
+    xchg al,ah;\
+    mov v,ax }
+
+#define HIGH_WORD(addr,v) asm {\
+    mov bx,word ptr zmp;\
+    add bx,word ptr addr;\
+    mov al,bh;\
+    mov bh,0;\
+    mov ah,0;\
+    adc ah,byte ptr addr+2;\
+    mov cl,4;\
+    shl ax,cl;\
+    add ax,word ptr zmp+2;\
+    mov es,ax;\
+    mov ax,es:[bx];\
+    xchg al,ah;\
+    mov v,ax }
+
+#define CODE_WORD(v) asm {\
+    les bx,pcp;\
+    mov ax,es:[bx];\
+    xchg al,ah;\
+    mov v,ax;\
+    add word ptr pcp,2 }
+
+#define GET_PC(v) asm {\
+    mov bx,word ptr pcp+2;\
+    sub bx,word ptr zmp+2;\
+    mov ax,bx;\
+    mov cl,4;\
+    shl bx,cl;\
+    mov cl,12;\
+    shr ax,cl;\
+    add bx,word ptr pcp;\
+    adc al,0;\
+    sub bx,word ptr zmp;\
+    sbb al,0;\
+    mov word ptr v,bx;\
+    mov word ptr v+2,ax }
+
+#define SET_PC(v) asm {\
+    mov bx,word ptr zmp;\
+    add bx,word ptr v;\
+    mov al,bh;\
+    mov bh,0;\
+    mov ah,0;\
+    adc ah,byte ptr v+2;\
+    mov cl,4;\
+    shl ax,cl;\
+    add ax,word ptr zmp+2;\
+    mov word ptr pcp,bx;\
+    mov word ptr pcp+2,ax }
+
+#endif
+
+#if !defined (AMIGA) && !defined (__MSDOS__)
+
+extern zbyte *pcp;
+extern zbyte *zmp;
+
+#define lo(v)  (v & 0xff)
+#define hi(v)  (v >> 8)
+
+#define SET_WORD(addr,v)  { zmp[addr] = hi(v); zmp[addr+1] = lo(v); }
+#define LOW_WORD(addr,v)  { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; }
+#define HIGH_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; }
+#define CODE_WORD(v)      { v = ((zword) pcp[0] << 8) | pcp[1]; pcp += 2; }
+#define GET_PC(v)         { v = pcp - zmp; }
+#define SET_PC(v)         { pcp = zmp + v; }
+
+#endif
+
+/*** Story file header data ***/
+
+extern zbyte h_version;
+extern zbyte h_config;
+extern zword h_release;
+extern zword h_resident_size;
+extern zword h_start_pc;
+extern zword h_dictionary;
+extern zword h_objects;
+extern zword h_globals;
+extern zword h_dynamic_size;
+extern zword h_flags;
+extern zbyte h_serial[6];
+extern zword h_abbreviations;
+extern zword h_file_size;
+extern zword h_checksum;
+extern zbyte h_interpreter_number;
+extern zbyte h_interpreter_version;
+extern zbyte h_screen_rows;
+extern zbyte h_screen_cols;
+extern zword h_screen_width;
+extern zword h_screen_height;
+extern zbyte h_font_height;
+extern zbyte h_font_width;
+extern zword h_functions_offset;
+extern zword h_strings_offset;
+extern zbyte h_default_background;
+extern zbyte h_default_foreground;
+extern zword h_terminating_keys;
+extern zword h_line_width;
+extern zbyte h_standard_high;
+extern zbyte h_standard_low;
+extern zword h_alphabet;
+extern zword h_extension_table;
+extern zbyte h_user_name[8];
+
+extern zword hx_table_size;
+extern zword hx_mouse_x;
+extern zword hx_mouse_y;
+extern zword hx_unicode_table;
+
+/*** Various data ***/
+
+extern const char *story_name;
+
+extern enum story story_id;
+extern long story_size;
+
+extern zword stack[STACK_SIZE];
+extern zword *sp;
+extern zword *fp;
+
+extern zword zargs[8];
+extern zargc;
+
+extern bool ostream_screen;
+extern bool ostream_script;
+extern bool ostream_memory;
+extern bool ostream_record;
+extern bool istream_replay;
+extern bool message;
+
+extern cwin;
+extern mwin;
+
+extern mouse_x;
+extern mouse_y;
+
+extern bool enable_wrapping;
+extern bool enable_scripting;
+extern bool enable_scrolling;
+extern bool enable_buffering;
+
+extern option_attribute_assignment;
+extern option_attribute_testing;
+extern option_object_locating;
+extern option_object_movement;
+extern option_context_lines;
+extern option_left_margin;
+extern option_right_margin;
+extern option_ignore_errors;
+extern option_piracy;
+extern option_undo_slots;
+extern option_expand_abbreviations;
+extern option_script_cols;
+
+extern long reserve_mem;
+
+/*** Z-machine opcodes ***/
+
+void   z_add (void);
+void   z_and (void);
+void   z_art_shift (void);
+void   z_buffer_mode (void);
+void   z_call_n (void);
+void   z_call_s (void);
+void   z_catch (void);
+void   z_check_arg_count (void);
+void   z_check_unicode (void);
+void   z_clear_attr (void);
+void   z_copy_table (void);
+void   z_dec (void);
+void   z_dec_chk (void);
+void   z_div (void);
+void   z_draw_picture (void);
+void   z_encode_text (void);
+void   z_erase_line (void);
+void   z_erase_picture (void);
+void   z_erase_window (void);
+void   z_get_child (void);
+void   z_get_cursor (void);
+void   z_get_next_prop (void);
+void   z_get_parent (void);
+void   z_get_prop (void);
+void   z_get_prop_addr (void);
+void   z_get_prop_len (void);
+void   z_get_sibling (void);
+void   z_get_wind_prop (void);
+void   z_inc (void);
+void   z_inc_chk (void);
+void   z_input_stream (void);
+void   z_insert_obj (void);
+void   z_je (void);
+void   z_jg (void);
+void   z_jin (void);
+void   z_jl (void);
+void   z_jump (void);
+void   z_jz (void);
+void   z_load (void);
+void   z_loadb (void);
+void   z_loadw (void);
+void   z_log_shift (void);
+void   z_make_menu (void);
+void   z_mod (void);
+void   z_mouse_window (void);
+void   z_move_window (void);
+void   z_mul (void);
+void   z_new_line (void);
+void   z_nop (void);
+void   z_not (void);
+void   z_or (void);
+void   z_output_stream (void);
+void   z_picture_data (void);
+void   z_picture_table (void);
+void   z_piracy (void);
+void   z_pop (void);
+void   z_pop_stack (void);
+void   z_print (void);
+void   z_print_addr (void);
+void   z_print_char (void);
+void   z_print_form (void);
+void   z_print_num (void);
+void   z_print_obj (void);
+void   z_print_paddr (void);
+void   z_print_ret (void);
+void   z_print_table (void);
+void   z_print_unicode (void);
+void   z_pull (void);
+void   z_push (void);
+void   z_push_stack (void);
+void   z_put_prop (void);
+void   z_put_wind_prop (void);
+void   z_quit (void);
+void   z_random (void);
+void   z_read (void);
+void   z_read_char (void);
+void   z_read_mouse (void);
+void   z_remove_obj (void);
+void   z_restart (void);
+void   z_restore (void);
+void   z_restore_undo (void);
+void   z_ret (void);
+void   z_ret_popped (void);
+void   z_rfalse (void);
+void   z_rtrue (void);
+void   z_save (void);
+void   z_save_undo (void);
+void   z_scan_table (void);
+void   z_scroll_window (void);
+void   z_set_attr (void);
+void   z_set_font (void);
+void   z_set_colour (void);
+void   z_set_cursor (void);
+void   z_set_margins (void);
+void   z_set_window (void);
+void   z_set_text_style (void);
+void   z_show_status (void);
+void   z_sound_effect (void);
+void   z_split_window (void);
+void   z_store (void);
+void   z_storeb (void);
+void   z_storew (void);
+void   z_sub (void);
+void   z_test (void);
+void   z_test_attr (void);
+void   z_throw (void);
+void   z_tokenise (void);
+void   z_verify (void);
+void   z_window_size (void);
+void   z_window_style (void);
+
+/*** Various global functions ***/
+
+zchar  translate_from_zscii (zbyte);
+zbyte  translate_to_zscii (zchar);
+
+void   flush_buffer (void);
+void   new_line (void);
+void   print_char (zchar);
+void   print_num (zword);
+void   print_object (zword);
+void   print_string (const char *);
+
+void   stream_mssg_on (void);
+void   stream_mssg_off (void);
+
+void   runtime_error (const char *);
+
+void   ret (zword);
+void   store (zword);
+void   branch (bool);
+
+void   storeb (zword, zbyte);
+void   storew (zword, zword);
+
+/*** Interface functions ***/
+
+void   os_beep (int);
+int    os_char_width (zchar);
+void   os_display_char (zchar);
+void   os_display_string (const zchar *);
+void   os_draw_picture (int, int, int);
+void   os_erase_area (int, int, int, int);
+void   os_fatal (const char *);
+void   os_finish_with_sample (void);
+int    os_font_data (int, int *, int *);
+void   os_init_screen (void);
+void   os_more_prompt (void);
+int    os_peek_colour (void);
+int    os_picture_data (int, int *, int *);
+void   os_prepare_sample (int);
+void   os_process_arguments (int, char *[]);
+int    os_random_seed (void);
+int    os_read_file_name (char *, const char *, int);
+zchar  os_read_key (int, int);
+zchar  os_read_line (int, zchar *, int, int, int);
+void   os_reset_screen (void);
+void   os_restart_game (int);
+void   os_scroll_area (int, int, int, int, int);
+void   os_set_colour (int, int);
+void   os_set_cursor (int, int);
+void   os_set_font (int);
+void   os_set_text_style (int);
+void   os_start_sample (int, int, int);
+void   os_stop_sample (void);
+int    os_string_width (const zchar *);
diff --git a/getopt.c b/getopt.c
new file mode 100644 (file)
index 0000000..c1e40e1
--- /dev/null
+++ b/getopt.c
@@ -0,0 +1,69 @@
+/*
+ * 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 */
diff --git a/hotkey.c b/hotkey.c
new file mode 100644 (file)
index 0000000..56bc413
--- /dev/null
+++ b/hotkey.c
@@ -0,0 +1,242 @@
+/*
+ * hotkey.c
+ *
+ * Hot key functions
+ *
+ */
+
+#include "frotz.h"
+
+extern int restore_undo (void);
+
+extern int read_number (void);
+
+extern bool read_yes_or_no (const char *);
+
+extern void replay_open (void);
+extern void replay_close (void);
+extern void record_open (void);
+extern void record_close (void);
+
+extern void seed_random (int);
+
+/*
+ * hot_key_debugging
+ *
+ * ...allows user to toggle cheating options on/off.
+ *
+ */
+
+static bool hot_key_debugging (void)
+{
+
+    print_string ("Debugging options\n");
+
+    option_attribute_assignment = read_yes_or_no ("Watch attribute assignment");
+    option_attribute_testing = read_yes_or_no ("Watch attribute testing");
+    option_object_movement = read_yes_or_no ("Watch object movement");
+    option_object_locating = read_yes_or_no ("Watch object locating");
+
+    return FALSE;
+
+}/* hot_key_debugging */
+
+/*
+ * hot_key_help
+ *
+ * ...displays a list of all hot keys.
+ *
+ */
+
+static bool hot_key_help (void) {
+
+    print_string ("Help\n");
+
+    print_string (
+       "\n"
+       "Alt-D  debugging options\n"
+       "Alt-H  help\n"
+       "Alt-N  new game\n"
+       "Alt-P  playback on\n"
+       "Alt-R  recording on/off\n"
+       "Alt-S  seed random numbers\n"
+       "Alt-U  undo one turn\n"
+       "Alt-X  exit game\n");
+
+    return FALSE;
+
+}/* hot_key_help */
+
+/*
+ * hot_key_playback
+ *
+ * ...allows user to turn playback on.
+ *
+ */
+
+static bool hot_key_playback (void)
+{
+
+    print_string ("Playback on\n");
+
+    if (!istream_replay)
+       replay_open ();
+
+    return FALSE;
+
+}/* hot_key_playback */
+
+/*
+ * hot_key_recording
+ *
+ * ...allows user to turn recording on/off.
+ *
+ */
+
+static bool hot_key_recording (void)
+{
+
+    if (istream_replay) {
+       print_string ("Playback off\n");
+       replay_close ();
+    } else if (ostream_record) {
+       print_string ("Recording off\n");
+       record_close ();
+    } else {
+       print_string ("Recording on\n");
+       record_open ();
+    }
+
+    return FALSE;
+
+}/* hot_key_recording */
+
+/*
+ * hot_key_seed
+ *
+ * ...allows user to seed the random number seed.
+ *
+ */
+
+static bool hot_key_seed (void)
+{
+
+    print_string ("Seed random numbers\n");
+
+    print_string ("Enter seed value (or return to randomize): ");
+    seed_random (read_number ());
+
+    return FALSE;
+
+}/* hot_key_seed */
+
+/*
+ * hot_key_undo
+ *
+ * ...allows user to undo the previous turn.
+ *
+ */
+
+static bool hot_key_undo (void)
+{
+
+    print_string ("Undo one turn\n");
+
+    if (restore_undo ()) {
+
+       if (h_version >= V5) {          /* for V5+ games we must */
+           store (2);                  /* store 2 (for success) */
+           return TRUE;                /* and abort the input   */
+       }
+
+       if (h_version <= V3) {          /* for V3- games we must */
+           z_show_status ();           /* draw the status line  */
+           return FALSE;               /* and continue input    */
+       }
+
+    } else print_string ("No more undo information available.\n");
+
+    return FALSE;
+
+}/* hot_key_undo */
+
+/*
+ * hot_key_restart
+ *
+ * ...allows user to start a new game.
+ *
+ */
+
+static bool hot_key_restart (void)
+{
+
+    print_string ("New game\n");
+
+    if (read_yes_or_no ("Do you wish to restart")) {
+
+       z_restart ();
+       return TRUE;
+
+    } else return FALSE;
+
+}/* hot_key_restart */
+
+/*
+ * hot_key_quit
+ *
+ * ...allows user to exit the game.
+ *
+ */
+
+static bool hot_key_quit (void)
+{
+
+    print_string ("Exit game\n");
+
+    if (read_yes_or_no ("Do you wish to quit")) {
+
+       z_quit ();
+       return TRUE;
+
+    } else return FALSE;
+
+}/* hot_key_quit */
+
+/*
+ * handle_hot_key
+ *
+ * Perform the action associated with a so-called hot key. Return
+ * true to abort the current input action.
+ *
+ */
+
+bool handle_hot_key (zchar key)
+{
+
+    if (cwin == 0) {
+
+       bool aborting;
+
+       print_string ("\nHot key -- ");
+
+       switch (key) {
+           case ZC_HKEY_RECORD: aborting = hot_key_recording (); break;
+           case ZC_HKEY_PLAYBACK: aborting = hot_key_playback (); break;
+           case ZC_HKEY_SEED: aborting = hot_key_seed (); break;
+           case ZC_HKEY_UNDO: aborting = hot_key_undo (); break;
+           case ZC_HKEY_RESTART: aborting = hot_key_restart (); break;
+           case ZC_HKEY_QUIT: aborting = hot_key_quit (); break;
+           case ZC_HKEY_DEBUG: aborting = hot_key_debugging (); break;
+           case ZC_HKEY_HELP: aborting = hot_key_help (); break;
+       }
+
+       if (aborting)
+           return TRUE;
+
+       print_string ("\nContinue input...\n");
+
+    }
+
+    return FALSE;
+
+}/* handle_hot_key */
diff --git a/input.c b/input.c
new file mode 100644 (file)
index 0000000..0642aef
--- /dev/null
+++ b/input.c
@@ -0,0 +1,303 @@
+/*
+ * input.c
+ *
+ * High level input functions
+ *
+ */
+
+#include "frotz.h"
+
+extern int save_undo (void);
+
+extern zchar stream_read_key (zword, zword, bool);
+extern zchar stream_read_input (int, zchar *, zword, zword, bool, bool);
+
+extern void tokenise_line (zword, zword, zword, bool);
+
+/*
+ * is_terminator
+ *
+ * Check if the given key is an input terminator.
+ *
+ */
+
+bool is_terminator (zchar key)
+{
+
+    if (key == ZC_TIME_OUT)
+       return TRUE;
+    if (key == ZC_RETURN)
+       return TRUE;
+    if (key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX)
+       return TRUE;
+
+    if (h_terminating_keys != 0)
+
+       if (key >= ZC_ARROW_MIN && key <= ZC_MENU_CLICK) {
+
+           zword addr = h_terminating_keys;
+           zbyte c;
+
+           do {
+               LOW_BYTE (addr, c)
+               if (c == 255 || key == translate_from_zscii (c))
+                   return TRUE;
+               addr++;
+           } while (c != 0);
+
+       }
+
+    return FALSE;
+
+}/* is_terminator */
+
+/*
+ * z_make_menu, add or remove a menu and branch if successful.
+ *
+ *     zargs[0] = number of menu
+ *     zargs[1] = table of menu entries or 0 to remove menu
+ *
+ */
+
+void z_make_menu (void)
+{
+
+    /* This opcode was only used for the Macintosh version of Journey.
+       It controls menus with numbers greater than 2 (menus 0, 1 and 2
+       are system menus). Frotz doesn't implement menus yet. */
+
+    branch (FALSE);
+
+}/* z_make_menu */
+
+/*
+ * read_yes_or_no
+ *
+ * Ask the user a question; return true if the answer is yes.
+ *
+ */
+
+bool read_yes_or_no (const char *s)
+{
+    zchar key;
+
+    print_string (s);
+    print_string ("? (y/n) >");
+
+    key = stream_read_key (0, 0, FALSE);
+
+    if (key == 'y' || key == 'Y') {
+       print_string ("y\n");
+       return TRUE;
+    } else {
+       print_string ("n\n");
+       return FALSE;
+    }
+
+}/* read_yes_or_no */
+
+/*
+ * read_string
+ *
+ * Read a string from the current input stream.
+ *
+ */
+
+void read_string (int max, zchar *buffer)
+{
+    zchar key;
+
+    buffer[0] = 0;
+
+    do {
+
+       key = stream_read_input (max, buffer, 0, 0, FALSE, FALSE);
+
+    } while (key != ZC_RETURN);
+
+}/* read_string */
+
+/*
+ * read_number
+ *
+ * Ask the user to type in a number and return it.
+ *
+ */
+
+int read_number (void)
+{
+    zchar buffer[6];
+    int value = 0;
+    int i;
+
+    read_string (5, buffer);
+
+    for (i = 0; buffer[i] != 0; i++)
+       if (buffer[i] >= '0' && buffer[i] <= '9')
+           value = 10 * value + buffer[i] - '0';
+
+    return value;
+
+}/* read_number */
+
+/*
+ * z_read, read a line of input and (in V5+) store the terminating key.
+ *
+ *     zargs[0] = address of text buffer
+ *     zargs[1] = address of token buffer
+ *     zargs[2] = timeout in tenths of a second (optional)
+ *     zargs[3] = packed address of routine to be called on timeout
+ *
+ */
+
+void z_read (void)
+{
+    zchar buffer[INPUT_BUFFER_SIZE];
+    zword addr;
+    zchar key;
+    zbyte max, size;
+    zbyte c;
+    int i;
+
+    /* Supply default arguments */
+
+    if (zargc < 3)
+       zargs[2] = 0;
+
+    /* Get maximum input size */
+
+    addr = zargs[0];
+
+    LOW_BYTE (addr, max)
+
+    if (h_version <= V4)
+       max--;
+
+    if (max >= INPUT_BUFFER_SIZE)
+       max = INPUT_BUFFER_SIZE - 1;
+
+    /* Get initial input size */
+
+    if (h_version >= V5) {
+       addr++;
+       LOW_BYTE (addr, size)
+    } else size = 0;
+
+    /* Copy initial input to local buffer */
+
+    for (i = 0; i < size; i++) {
+       addr++;
+       LOW_BYTE (addr, c)
+       buffer[i] = translate_from_zscii (c);
+    }
+
+    buffer[i] = 0;
+
+    /* Draw status line for V1 to V3 games */
+
+    if (h_version <= V3)
+       z_show_status ();
+
+    /* Read input from current input stream */
+
+    key = stream_read_input (
+       max, buffer,            /* buffer and size */
+       zargs[2],               /* timeout value   */
+       zargs[3],               /* timeout routine */
+       TRUE,                   /* enable hot keys */
+       h_version == V6);       /* no script in V6 */
+
+    if (key == ZC_BAD)
+       return;
+
+    /* Perform save_undo for V1 to V4 games */
+
+    if (h_version <= V4)
+       save_undo ();
+
+    /* Copy local buffer back to dynamic memory */
+
+    for (i = 0; buffer[i] != 0; i++) {
+
+       if (key == ZC_RETURN) {
+
+           if (buffer[i] >= 'A' && buffer[i] <= 'Z')
+               buffer[i] += 'a' - 'A';
+           if (buffer[i] >= 0xc0 && buffer[i] <= 0xde && buffer[i] != 0xd7)
+               buffer[i] += 0x20;
+
+       }
+
+       storeb ((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i]));
+
+    }
+
+    /* Add null character (V1-V4) or write input length into 2nd byte */
+
+    if (h_version <= V4)
+       storeb ((zword) (zargs[0] + 1 + i), 0);
+    else
+       storeb ((zword) (zargs[0] + 1), i);
+
+    /* Tokenise line if a token buffer is present */
+
+    if (key == ZC_RETURN && zargs[1] != 0)
+       tokenise_line (zargs[0], zargs[1], 0, FALSE);
+
+    /* Store key */
+
+    if (h_version >= V5)
+       store (translate_to_zscii (key));
+
+}/* z_read */
+
+/*
+ * z_read_char, read and store a key.
+ *
+ *     zargs[0] = input device (must be 1)
+ *     zargs[1] = timeout in tenths of a second (optional)
+ *     zargs[2] = packed address of routine to be called on timeout
+ *
+ */
+
+void z_read_char (void)
+{
+    zchar key;
+
+    /* Supply default arguments */
+
+    if (zargc < 2)
+       zargs[1] = 0;
+
+    /* Read input from the current input stream */
+
+    key = stream_read_key (
+       zargs[1],       /* timeout value   */
+       zargs[2],       /* timeout routine */
+       TRUE);          /* enable hot keys */
+
+    if (key == ZC_BAD)
+       return;
+
+    /* Store key */
+
+    store (translate_to_zscii (key));
+
+}/* z_read_char */
+
+/*
+ * z_read_mouse, write the current mouse status into a table.
+ *
+ *     zargs[0] = address of table
+ *
+ */
+
+void z_read_mouse (void)
+{
+
+    storew ((zword) (zargs[0] + 0), hx_mouse_y);
+    storew ((zword) (zargs[0] + 2), hx_mouse_x);
+    storew ((zword) (zargs[0] + 4), 1);                /* mouse button bits */
+    storew ((zword) (zargs[0] + 6), 0);                /* menu selection */
+
+}/* z_read_mouse */
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..9a8e9ba
--- /dev/null
+++ b/main.c
@@ -0,0 +1,181 @@
+/*
+ * main.c
+ *
+ * Frotz V2.32 main function
+ *
+ * This is an interpreter for Infocom V1 to V6 games. It also supports
+ * the recently defined V7 and V8 games. Please report bugs to
+ *
+ *    s.jokisch@avu.de
+ *
+ * Frotz is freeware. It may be used and distributed freely provided
+ * no commercial profit is involved. (c) 1995-1997 Stefan Jokisch
+ *
+ */
+
+#include "frotz.h"
+
+#ifndef __MSDOS__
+#define cdecl
+#endif
+
+extern void interpret (void);
+extern void init_memory (void);
+extern void init_undo (void);
+extern void reset_memory (void);
+
+/* Story file name, id number and size */
+
+const char *story_name = 0;
+
+enum story story_id = UNKNOWN;
+long story_size = 0;
+
+/* Story file header data */
+
+zbyte h_version = 0;
+zbyte h_config = 0;
+zword h_release = 0;
+zword h_resident_size = 0;
+zword h_start_pc = 0;
+zword h_dictionary = 0;
+zword h_objects = 0;
+zword h_globals = 0;
+zword h_dynamic_size = 0;
+zword h_flags = 0;
+zbyte h_serial[6] = { 0, 0, 0, 0, 0, 0 };
+zword h_abbreviations = 0;
+zword h_file_size = 0;
+zword h_checksum = 0;
+zbyte h_interpreter_number = 0;
+zbyte h_interpreter_version = 0;
+zbyte h_screen_rows = 0;
+zbyte h_screen_cols = 0;
+zword h_screen_width = 0;
+zword h_screen_height = 0;
+zbyte h_font_height = 1;
+zbyte h_font_width = 1;
+zword h_functions_offset = 0;
+zword h_strings_offset = 0;
+zbyte h_default_background = 0;
+zbyte h_default_foreground = 0;
+zword h_terminating_keys = 0;
+zword h_line_width = 0;
+zbyte h_standard_high = 1;
+zbyte h_standard_low = 0;
+zword h_alphabet = 0;
+zword h_extension_table = 0;
+zbyte h_user_name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+zword hx_table_size = 0;
+zword hx_mouse_x = 0;
+zword hx_mouse_y = 0;
+zword hx_unicode_table = 0;
+
+/* Stack data */
+
+zword stack[STACK_SIZE];
+zword *sp = 0;
+zword *fp = 0;
+
+/* IO streams */
+
+bool ostream_screen = TRUE;
+bool ostream_script = FALSE;
+bool ostream_memory = FALSE;
+bool ostream_record = FALSE;
+bool istream_replay = FALSE;
+bool message = FALSE;
+
+/* Current window and mouse data */
+
+int cwin = 0;
+int mwin = 0;
+
+int mouse_y = 0;
+int mouse_x = 0;
+
+/* Window attributes */
+
+bool enable_wrapping = FALSE;
+bool enable_scripting = FALSE;
+bool enable_scrolling = FALSE;
+bool enable_buffering = FALSE;
+
+/* User options */
+
+int option_attribute_assignment = 0;
+int option_attribute_testing = 0;
+int option_context_lines = 0;
+int option_object_locating = 0;
+int option_object_movement = 0;
+int option_left_margin = 0;
+int option_right_margin = 0;
+int option_ignore_errors = 0;
+int option_piracy = 0;
+int option_undo_slots = MAX_UNDO_SLOTS;
+int option_expand_abbreviations = 0;
+int option_script_cols = 80;
+
+/* Size of memory to reserve (in bytes) */
+
+long reserve_mem = 0;
+
+/*
+ * runtime_error
+ *
+ * An error has occured. Ignore it or pass it to os_fatal.
+ *
+ */
+
+void runtime_error (const char *s)
+{
+
+    if (!option_ignore_errors)
+       { flush_buffer (); os_fatal (s); }
+
+}/* runtime_error */
+
+/*
+ * z_piracy, branch if the story file is a legal copy.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_piracy (void)
+{
+
+    branch (!option_piracy);
+
+}/* z_piracy */
+
+/*
+ * main
+ *
+ * Prepare and run the game.
+ *
+ */
+
+int cdecl main (int argc, char *argv[])
+{
+
+    os_process_arguments (argc, argv);
+
+    init_memory ();
+
+    os_init_screen ();
+
+    init_undo ();
+
+    z_restart ();
+
+    interpret ();
+
+    reset_memory ();
+
+    os_reset_screen ();
+
+    return 0;
+
+}/* main */
diff --git a/math.c b/math.c
new file mode 100644 (file)
index 0000000..20ae247
--- /dev/null
+++ b/math.c
@@ -0,0 +1,248 @@
+/*
+ * math.c
+ *
+ * Arithmetic, compare and logical opcodes
+ *
+ */
+
+#include "frotz.h"
+
+/*
+ * z_add, 16bit addition.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_add (void)
+{
+
+    store ((zword) ((short) zargs[0] + (short) zargs[1]));
+
+}/* z_add */
+
+/*
+ * z_and, bitwise AND operation.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_and (void)
+{
+
+    store ((zword) (zargs[0] & zargs[1]));
+
+}/* z_and */
+
+/*
+ * z_art_shift, arithmetic SHIFT operation.
+ *
+ *     zargs[0] = value
+ *     zargs[1] = #positions to shift left (positive) or right
+ *
+ */
+
+void z_art_shift (void)
+{
+
+    if ((short) zargs[1] > 0)
+       store ((zword) ((short) zargs[0] << (short) zargs[1]));
+    else
+       store ((zword) ((short) zargs[0] >> - (short) zargs[1]));
+
+}/* z_art_shift */
+
+/*
+ * z_div, signed 16bit division.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_div (void)
+{
+
+    if (zargs[1] == 0)
+       runtime_error ("Division by zero");
+
+    store ((zword) ((short) zargs[0] / (short) zargs[1]));
+
+}/* z_div */
+
+/*
+ * z_je, branch if the first value equals any of the following.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value (optional)
+ *     ...
+ *     zargs[3] = fourth value (optional)
+ *
+ */
+
+void z_je (void)
+{
+
+    branch (
+       zargc > 1 && (zargs[0] == zargs[1] || (
+       zargc > 2 && (zargs[0] == zargs[2] || (
+       zargc > 3 && (zargs[0] == zargs[3]))))));
+
+}/* z_je */
+
+/*
+ * z_jg, branch if the first value is greater than the second.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_jg (void)
+{
+
+    branch ((short) zargs[0] > (short) zargs[1]);
+
+}/* z_jg */
+
+/*
+ * z_jl, branch if the first value is less than the second.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_jl (void)
+{
+
+    branch ((short) zargs[0] < (short) zargs[1]);
+
+}/* z_jl */
+
+/*
+ * z_jz, branch if value is zero.
+ *
+ *     zargs[0] = value
+ *
+ */
+
+void z_jz (void)
+{
+
+    branch ((short) zargs[0] == 0);
+
+}/* z_jz */
+
+/*
+ * z_log_shift, logical SHIFT operation.
+ *
+ *     zargs[0] = value
+ *     zargs[1] = #positions to shift left (positive) or right (negative)
+ *
+ */
+
+void z_log_shift (void)
+{
+
+    if ((short) zargs[1] > 0)
+       store ((zword) (zargs[0] << (short) zargs[1]));
+    else
+       store ((zword) (zargs[0] >> - (short) zargs[1]));
+
+}/* z_log_shift */
+
+/*
+ * z_mod, remainder after signed 16bit division.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_mod (void)
+{
+
+    if (zargs[1] == 0)
+       runtime_error ("Division by zero");
+
+    store ((zword) ((short) zargs[0] % (short) zargs[1]));
+
+}/* z_mod */
+
+/*
+ * z_mul, 16bit multiplication.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_mul (void)
+{
+
+    store ((zword) ((short) zargs[0] * (short) zargs[1]));
+
+}/* z_mul */
+
+/*
+ * z_not, bitwise NOT operation.
+ *
+ *     zargs[0] = value
+ *
+ */
+
+void z_not (void)
+{
+
+    store ((zword) ~zargs[0]);
+
+}/* z_not */
+
+/*
+ * z_or, bitwise OR operation.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_or (void)
+{
+
+    store ((zword) (zargs[0] | zargs[1]));
+
+}/* z_or */
+
+/*
+ * z_sub, 16bit substraction.
+ *
+ *     zargs[0] = first value
+ *     zargs[1] = second value
+ *
+ */
+
+void z_sub (void)
+{
+
+    store ((zword) ((short) zargs[0] - (short) zargs[1]));
+
+}/* z_sub */
+
+/*
+ * z_test, branch if all the flags of a bit mask are set in a value.
+ *
+ *     zargs[0] = value to be examined
+ *     zargs[1] = bit mask
+ *
+ */
+
+void z_test (void)
+{
+
+    branch ((zargs[0] & zargs[1]) == zargs[1]);
+
+}/* z_test */
diff --git a/object.c b/object.c
new file mode 100644 (file)
index 0000000..c6c79e5
--- /dev/null
+++ b/object.c
@@ -0,0 +1,904 @@
+/*
+ * object.c
+ *
+ * Object manipulation opcodes
+ *
+ */
+
+#include "frotz.h"
+
+#define MAX_OBJECT 2000
+
+#define O1_PARENT 4
+#define O1_SIBLING 5
+#define O1_CHILD 6
+#define O1_PROPERTY_OFFSET 7
+#define O1_SIZE 9
+
+#define O4_PARENT 6
+#define O4_SIBLING 8
+#define O4_CHILD 10
+#define O4_PROPERTY_OFFSET 12
+#define O4_SIZE 14
+
+/*
+ * object_address
+ *
+ * Calculate the address of an object.
+ *
+ */
+
+static zword object_address (zword obj)
+{
+
+    /* Check object number */
+
+    if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT))
+       runtime_error ("Illegal object");
+
+    /* Return object address */
+
+    if (h_version <= V3)
+       return h_objects + ((obj - 1) * O1_SIZE + 62);
+    else
+       return h_objects + ((obj - 1) * O4_SIZE + 126);
+
+}/* object_address */
+
+/*
+ * object_name
+ *
+ * Return the address of the given object's name.
+ *
+ */
+
+zword object_name (zword object)
+{
+    zword obj_addr;
+    zword name_addr;
+
+    obj_addr = object_address (object);
+
+    /* The object name address is found at the start of the properties */
+
+    if (h_version <= V3)
+       obj_addr += O1_PROPERTY_OFFSET;
+    else
+       obj_addr += O4_PROPERTY_OFFSET;
+
+    LOW_WORD (obj_addr, name_addr)
+
+    return name_addr;
+
+}/* object_name */
+
+/*
+ * first_property
+ *
+ * Calculate the start address of the property list associated with
+ * an object.
+ *
+ */
+
+static zword first_property (zword obj)
+{
+    zword prop_addr;
+    zbyte size;
+
+    /* Fetch address of object name */
+
+    prop_addr = object_name (obj);
+
+    /* Get length of object name */
+
+    LOW_BYTE (prop_addr, size)
+
+    /* Add name length to pointer */
+
+    return prop_addr + 1 + 2 * size;
+
+}/* first_property */
+
+/*
+ * next_property
+ *
+ * Calculate the address of the next property in a property list.
+ *
+ */
+
+static zword next_property (zword prop_addr)
+{
+    zbyte value;
+
+    /* Load the current property id */
+
+    LOW_BYTE (prop_addr, value)
+    prop_addr++;
+
+    /* Calculate the length of this property */
+
+    if (h_version <= V3)
+       value >>= 5;
+    else if (!(value & 0x80))
+       value >>= 6;
+    else {
+
+       LOW_BYTE (prop_addr, value)
+       value &= 0x3f;
+
+       if (value == 0) value = 64;     /* demanded by Spec 1.0 */
+
+    }
+
+    /* Add property length to current property pointer */
+
+    return prop_addr + value + 1;
+
+}/* next_property */
+
+/*
+ * unlink_object
+ *
+ * Unlink an object from its parent and siblings.
+ *
+ */
+
+static void unlink_object (zword object)
+{
+    zword obj_addr;
+    zword parent_addr;
+    zword sibling_addr;
+
+    obj_addr = object_address (object);
+
+    if (h_version <= V3) {
+
+       zbyte parent;
+       zbyte younger_sibling;
+       zbyte older_sibling;
+       zbyte zero = 0;
+
+       /* Get parent of object, and return if no parent */
+
+       obj_addr += O1_PARENT;
+       LOW_BYTE (obj_addr, parent)
+       if (!parent)
+           return;
+
+       /* Get (older) sibling of object and set both parent and sibling
+          pointers to 0 */
+
+       SET_BYTE (obj_addr, zero)
+       obj_addr += O1_SIBLING - O1_PARENT;
+       LOW_BYTE (obj_addr, older_sibling)
+       SET_BYTE (obj_addr, zero)
+
+       /* Get first child of parent (the youngest sibling of the object) */
+
+       parent_addr = object_address (parent) + O1_CHILD;
+       LOW_BYTE (parent_addr, younger_sibling)
+
+       /* Remove object from the list of siblings */
+
+       if (younger_sibling == object)
+           SET_BYTE (parent_addr, older_sibling)
+       else {
+           do {
+               sibling_addr = object_address (younger_sibling) + O1_SIBLING;
+               LOW_BYTE (sibling_addr, younger_sibling)
+           } while (younger_sibling != object);
+           SET_BYTE (sibling_addr, older_sibling)
+       }
+
+    } else {
+
+       zword parent;
+       zword younger_sibling;
+       zword older_sibling;
+       zword zero = 0;
+
+       /* Get parent of object, and return if no parent */
+
+       obj_addr += O4_PARENT;
+       LOW_WORD (obj_addr, parent)
+       if (!parent)
+           return;
+
+       /* Get (older) sibling of object and set both parent and sibling
+          pointers to 0 */
+
+       SET_WORD (obj_addr, zero)
+       obj_addr += O4_SIBLING - O4_PARENT;
+       LOW_WORD (obj_addr, older_sibling)
+       SET_WORD (obj_addr, zero)
+
+       /* Get first child of parent (the youngest sibling of the object) */
+
+       parent_addr = object_address (parent) + O4_CHILD;
+       LOW_WORD (parent_addr, younger_sibling)
+
+       /* Remove object from the list of siblings */
+
+       if (younger_sibling == object)
+           SET_WORD (parent_addr, older_sibling)
+       else {
+           do {
+               sibling_addr = object_address (younger_sibling) + O4_SIBLING;
+               LOW_WORD (sibling_addr, younger_sibling)
+           } while (younger_sibling != object);
+           SET_WORD (sibling_addr, older_sibling)
+       }
+
+    }
+
+}/* unlink_object */
+
+/*
+ * z_clear_attr, clear an object attribute.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of attribute to be cleared
+ *
+ */
+
+void z_clear_attr (void)
+{
+    zword obj_addr;
+    zbyte value;
+
+    if (story_id == SHERLOCK)
+       if (zargs[1] == 48)
+           return;
+
+    if (zargs[1] > ((h_version <= V3) ? 31 : 47))
+       runtime_error ("Illegal attribute");
+
+    /* If we are monitoring attribute assignment display a short note */
+
+    if (option_attribute_assignment) {
+       stream_mssg_on ();
+       print_string ("@clear_attr ");
+       print_object (zargs[0]);
+       print_string (" ");
+       print_num (zargs[1]);
+       stream_mssg_off ();
+    }
+
+    /* Get attribute address */
+
+    obj_addr = object_address (zargs[0]) + zargs[1] / 8;
+
+    /* Clear attribute bit */
+
+    LOW_BYTE (obj_addr, value)
+    value &= ~(0x80 >> (zargs[1] & 7));
+    SET_BYTE (obj_addr, value)
+
+}/* z_clear_attr */
+
+/*
+ * z_jin, branch if the first object is inside the second.
+ *
+ *     zargs[0] = first object
+ *     zargs[1] = second object
+ *
+ */
+
+void z_jin (void)
+{
+    zword obj_addr;
+
+    /* If we are monitoring object locating display a short note */
+
+    if (option_object_locating) {
+       stream_mssg_on ();
+       print_string ("@jin ");
+       print_object (zargs[0]);
+       print_string (" ");
+       print_object (zargs[1]);
+       stream_mssg_off ();
+    }
+
+    obj_addr = object_address (zargs[0]);
+
+    if (h_version <= V3) {
+
+       zbyte parent;
+
+       /* Get parent id from object */
+
+       obj_addr += O1_PARENT;
+       LOW_BYTE (obj_addr, parent)
+
+       /* Branch if the parent is obj2 */
+
+       branch (parent == zargs[1]);
+
+    } else {
+
+       zword parent;
+
+       /* Get parent id from object */
+
+       obj_addr += O4_PARENT;
+       LOW_WORD (obj_addr, parent)
+
+       /* Branch if the parent is obj2 */
+
+       branch (parent == zargs[1]);
+
+    }
+
+}/* z_jin */
+
+/*
+ * z_get_child, store the child of an object.
+ *
+ *     zargs[0] = object
+ *
+ */
+
+void z_get_child (void)
+{
+    zword obj_addr;
+
+    /* If we are monitoring object locating display a short note */
+
+    if (option_object_locating) {
+       stream_mssg_on ();
+       print_string ("@get_child ");
+       print_object (zargs[0]);
+       stream_mssg_off ();
+    }
+
+    obj_addr = object_address (zargs[0]);
+
+    if (h_version <= V3) {
+
+       zbyte child;
+
+       /* Get child id from object */
+
+       obj_addr += O1_CHILD;
+       LOW_BYTE (obj_addr, child)
+
+       /* Store child id and branch */
+
+       store (child);
+       branch (child);
+
+    } else {
+
+       zword child;
+
+       /* Get child id from object */
+
+       obj_addr += O4_CHILD;
+       LOW_WORD (obj_addr, child)
+
+       /* Store child id and branch */
+
+       store (child);
+       branch (child);
+
+    }
+
+}/* z_get_child */
+
+/*
+ * z_get_next_prop, store the number of the first or next property.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = address of current property (0 gets the first property)
+ *
+ */
+
+void z_get_next_prop (void)
+{
+    zword prop_addr;
+    zbyte value;
+    zbyte mask;
+
+    /* Property id is in bottom five (six) bits */
+
+    mask = (h_version <= V3) ? 0x1f : 0x3f;
+
+    /* Load address of first property */
+
+    prop_addr = first_property (zargs[0]);
+
+    if (zargs[1] != 0) {
+
+       /* Scan down the property list */
+
+       do {
+           LOW_BYTE (prop_addr, value)
+           prop_addr = next_property (prop_addr);
+       } while ((value & mask) > zargs[1]);
+
+       /* Exit if the property does not exist */
+
+       if ((value & mask) != zargs[1])
+           runtime_error ("No such property");
+
+    }
+
+    /* Return the property id */
+
+    LOW_BYTE (prop_addr, value)
+    store ((zword) (value & mask));
+
+}/* z_get_next_prop */
+
+/*
+ * z_get_parent, store the parent of an object.
+ *
+ *     zargs[0] = object
+ *
+ */
+
+void z_get_parent (void)
+{
+    zword obj_addr;
+
+    /* If we are monitoring object locating display a short note */
+
+    if (option_object_locating) {
+       stream_mssg_on ();
+       print_string ("@get_parent ");
+       print_object (zargs[0]);
+       stream_mssg_off ();
+    }
+
+    obj_addr = object_address (zargs[0]);
+
+    if (h_version <= V3) {
+
+       zbyte parent;
+
+       /* Get parent id from object */
+
+       obj_addr += O1_PARENT;
+       LOW_BYTE (obj_addr, parent)
+
+       /* Store parent */
+
+       store (parent);
+
+    } else {
+
+       zword parent;
+
+       /* Get parent id from object */
+
+       obj_addr += O4_PARENT;
+       LOW_WORD (obj_addr, parent)
+
+       /* Store parent */
+
+       store (parent);
+
+    }
+
+}/* z_get_parent */
+
+/*
+ * z_get_prop, store the value of an object property.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of property to be examined
+ *
+ */
+
+void z_get_prop (void)
+{
+    zword prop_addr;
+    zword wprop_val;
+    zbyte bprop_val;
+    zbyte value;
+    zbyte mask;
+
+    /* Property id is in bottom five (six) bits */
+
+    mask = (h_version <= V3) ? 0x1f : 0x3f;
+
+    /* Load address of first property */
+
+    prop_addr = first_property (zargs[0]);
+
+    /* Scan down the property list */
+
+    for (;;) {
+       LOW_BYTE (prop_addr, value)
+       if ((value & mask) <= zargs[1])
+           break;
+       prop_addr = next_property (prop_addr);
+    }
+
+    if ((value & mask) == zargs[1]) {  /* property found */
+
+       /* Load property (byte or word sized) */
+
+       prop_addr++;
+
+       if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) {
+
+           LOW_BYTE (prop_addr, bprop_val)
+           wprop_val = bprop_val;
+
+       } else LOW_WORD (prop_addr, wprop_val)
+
+    } else {   /* property not found */
+
+       /* Load default value */
+
+       prop_addr = h_objects + 2 * (zargs[1] - 1);
+       LOW_WORD (prop_addr, wprop_val)
+
+    }
+
+    /* Store the property value */
+
+    store (wprop_val);
+
+}/* z_get_prop */
+
+/*
+ * z_get_prop_addr, store the address of an object property.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of property to be examined
+ *
+ */
+
+void z_get_prop_addr (void)
+{
+    zword prop_addr;
+    zbyte value;
+    zbyte mask;
+
+    if (story_id == BEYOND_ZORK)
+       if (zargs[0] > MAX_OBJECT)
+           { store (0); return; }
+
+    /* Property id is in bottom five (six) bits */
+
+    mask = (h_version <= V3) ? 0x1f : 0x3f;
+
+    /* Load address of first property */
+
+    prop_addr = first_property (zargs[0]);
+
+    /* Scan down the property list */
+
+    for (;;) {
+       LOW_BYTE (prop_addr, value)
+       if ((value & mask) <= zargs[1])
+           break;
+       prop_addr = next_property (prop_addr);
+    }
+
+    /* Calculate the property address or return zero */
+
+    if ((value & mask) == zargs[1]) {
+
+       if (h_version >= V4 && (value & 0x80))
+           prop_addr++;
+       store ((zword) (prop_addr + 1));
+
+    } else store (0);
+
+}/* z_get_prop_addr */
+
+/*
+ * z_get_prop_len, store the length of an object property.
+ *
+ *     zargs[0] = address of property to be examined
+ *
+ */
+
+void z_get_prop_len (void)
+{
+    zword addr;
+    zbyte value;
+
+    /* Back up the property pointer to the property id */
+
+    addr = zargs[0] - 1;
+    LOW_BYTE (addr, value)
+
+    /* Calculate length of property */
+
+    if (h_version <= V3)
+       value = (value >> 5) + 1;
+    else if (!(value & 0x80))
+       value = (value >> 6) + 1;
+    else {
+
+       value &= 0x3f;
+
+       if (value == 0) value = 64;     /* demanded by Spec 1.0 */
+
+    }
+
+    /* Store length of property */
+
+    store (value);
+
+}/* z_get_prop_len */
+
+/*
+ * z_get_sibling, store the sibling of an object.
+ *
+ *     zargs[0] = object
+ *
+ */
+
+void z_get_sibling (void)
+{
+    zword obj_addr;
+
+    obj_addr = object_address (zargs[0]);
+
+    if (h_version <= V3) {
+
+       zbyte sibling;
+
+       /* Get sibling id from object */
+
+       obj_addr += O1_SIBLING;
+       LOW_BYTE (obj_addr, sibling)
+
+       /* Store sibling and branch */
+
+       store (sibling);
+       branch (sibling);
+
+    } else {
+
+       zword sibling;
+
+       /* Get sibling id from object */
+
+       obj_addr += O4_SIBLING;
+       LOW_WORD (obj_addr, sibling)
+
+       /* Store sibling and branch */
+
+       store (sibling);
+       branch (sibling);
+
+    }
+
+}/* z_get_sibling */
+
+/*
+ * z_insert_obj, make an object the first child of another object.
+ *
+ *     zargs[0] = object to be moved
+ *     zargs[1] = destination object
+ *
+ */
+
+void z_insert_obj (void)
+{
+    zword obj1 = zargs[0];
+    zword obj2 = zargs[1];
+    zword obj1_addr;
+    zword obj2_addr;
+
+    /* If we are monitoring object movements display a short note */
+
+    if (option_object_movement) {
+       stream_mssg_on ();
+       print_string ("@move_obj ");
+       print_object (obj1);
+       print_string (" ");
+       print_object (obj2);
+       stream_mssg_off ();
+    }
+
+    /* Get addresses of both objects */
+
+    obj1_addr = object_address (obj1);
+    obj2_addr = object_address (obj2);
+
+    /* Remove object 1 from current parent */
+
+    unlink_object (obj1);
+
+    /* Make object 1 first child of object 2 */
+
+    if (h_version <= V3) {
+
+       zbyte child;
+
+       obj1_addr += O1_PARENT;
+       SET_BYTE (obj1_addr, obj2)
+       obj2_addr += O1_CHILD;
+       LOW_BYTE (obj2_addr, child)
+       SET_BYTE (obj2_addr, obj1)
+       obj1_addr += O1_SIBLING - O1_PARENT;
+       SET_BYTE (obj1_addr, child)
+
+    } else {
+
+       zword child;
+
+       obj1_addr += O4_PARENT;
+       SET_WORD (obj1_addr, obj2)
+       obj2_addr += O4_CHILD;
+       LOW_WORD (obj2_addr, child)
+       SET_WORD (obj2_addr, obj1)
+       obj1_addr += O4_SIBLING - O4_PARENT;
+       SET_WORD (obj1_addr, child)
+
+    }
+
+}/* z_insert_obj */
+
+/*
+ * z_put_prop, set the value of an object property.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of property to set
+ *     zargs[2] = value to set property to
+ *
+ */
+
+void z_put_prop (void)
+{
+    zword prop_addr;
+    zword value;
+    zbyte mask;
+
+    /* Property id is in bottom five or six bits */
+
+    mask = (h_version <= V3) ? 0x1f : 0x3f;
+
+    /* Load address of first property */
+
+    prop_addr = first_property (zargs[0]);
+
+    /* Scan down the property list */
+
+    for (;;) {
+       LOW_BYTE (prop_addr, value)
+       if ((value & mask) <= zargs[1])
+           break;
+       prop_addr = next_property (prop_addr);
+    }
+
+    /* Exit if the property does not exist */
+
+    if ((value & mask) != zargs[1])
+       runtime_error ("No such property");
+
+    /* Store the new property value (byte or word sized) */
+
+    prop_addr++;
+
+    if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) {
+       zbyte v = zargs[2];
+       SET_BYTE (prop_addr, v)
+    } else {
+       zword v = zargs[2];
+       SET_WORD (prop_addr, v)
+    }
+
+}/* z_put_prop */
+
+/*
+ * z_remove_obj, unlink an object from its parent and siblings.
+ *
+ *     zargs[0] = object
+ *
+ */
+
+void z_remove_obj (void)
+{
+
+    /* If we are monitoring object movements display a short note */
+
+    if (option_object_movement) {
+       stream_mssg_on ();
+       print_string ("@remove_obj ");
+       print_object (zargs[0]);
+       stream_mssg_off ();
+    }
+
+    /* Call unlink_object to do the job */
+
+    unlink_object (zargs[0]);
+
+}/* z_remove_obj */
+
+/*
+ * z_set_attr, set an object attribute.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of attribute to set
+ *
+ */
+
+void z_set_attr (void)
+{
+    zword obj_addr;
+    zbyte value;
+
+    if (story_id == SHERLOCK)
+       if (zargs[1] == 48)
+           return;
+
+    if (zargs[1] > ((h_version <= V3) ? 31 : 47))
+       runtime_error ("Illegal attribute");
+
+    /* If we are monitoring attribute assignment display a short note */
+
+    if (option_attribute_assignment) {
+       stream_mssg_on ();
+       print_string ("@set_attr ");
+       print_object (zargs[0]);
+       print_string (" ");
+       print_num (zargs[1]);
+       stream_mssg_off ();
+    }
+
+    /* Get attribute address */
+
+    obj_addr = object_address (zargs[0]) + zargs[1] / 8;
+
+    /* Load attribute byte */
+
+    LOW_BYTE (obj_addr, value)
+
+    /* Set attribute bit */
+
+    value |= 0x80 >> (zargs[1] & 7);
+
+    /* Store attribute byte */
+
+    SET_BYTE (obj_addr, value)
+
+}/* z_set_attr */
+
+/*
+ * z_test_attr, branch if an object attribute is set.
+ *
+ *     zargs[0] = object
+ *     zargs[1] = number of attribute to test
+ *
+ */
+
+void z_test_attr (void)
+{
+    zword obj_addr;
+    zbyte value;
+
+    if (zargs[1] > ((h_version <= V3) ? 31 : 47))
+       runtime_error ("Illegal attribute");
+
+    /* If we are monitoring attribute testing display a short note */
+
+    if (option_attribute_testing) {
+       stream_mssg_on ();
+       print_string ("@test_attr ");
+       print_object (zargs[0]);
+       print_string (" ");
+       print_num (zargs[1]);
+       stream_mssg_off ();
+    }
+
+    /* Get attribute address */
+
+    obj_addr = object_address (zargs[0]) + zargs[1] / 8;
+
+    /* Load attribute byte */
+
+    LOW_BYTE (obj_addr, value)
+
+    /* Test attribute */
+
+    branch (value & (0x80 >> (zargs[1] & 7)));
+
+}/* z_test_attr */
diff --git a/process.c b/process.c
new file mode 100644 (file)
index 0000000..dd42baa
--- /dev/null
+++ b/process.c
@@ -0,0 +1,746 @@
+/*
+ * process.c
+ *
+ * Interpreter loop and program control
+ *
+ */
+
+#include "frotz.h"
+
+zword zargs[8];
+int zargc;
+
+static finished = 0;
+
+static void __extended__ (void);
+static void __illegal__ (void);
+
+void (*op0_opcodes[0x10]) (void) = {
+    z_rtrue,
+    z_rfalse,
+    z_print,
+    z_print_ret,
+    z_nop,
+    z_save,
+    z_restore,
+    z_restart,
+    z_ret_popped,
+    z_catch,
+    z_quit,
+    z_new_line,
+    z_show_status,
+    z_verify,
+    __extended__,
+    z_piracy
+};
+
+void (*op1_opcodes[0x10]) (void) = {
+    z_jz,
+    z_get_sibling,
+    z_get_child,
+    z_get_parent,
+    z_get_prop_len,
+    z_inc,
+    z_dec,
+    z_print_addr,
+    z_call_s,
+    z_remove_obj,
+    z_print_obj,
+    z_ret,
+    z_jump,
+    z_print_paddr,
+    z_load,
+    z_call_n
+};
+
+void (*var_opcodes[0x40]) (void) = {
+    __illegal__,
+    z_je,
+    z_jl,
+    z_jg,
+    z_dec_chk,
+    z_inc_chk,
+    z_jin,
+    z_test,
+    z_or,
+    z_and,
+    z_test_attr,
+    z_set_attr,
+    z_clear_attr,
+    z_store,
+    z_insert_obj,
+    z_loadw,
+    z_loadb,
+    z_get_prop,
+    z_get_prop_addr,
+    z_get_next_prop,
+    z_add,
+    z_sub,
+    z_mul,
+    z_div,
+    z_mod,
+    z_call_s,
+    z_call_n,
+    z_set_colour,
+    z_throw,
+    __illegal__,
+    __illegal__,
+    __illegal__,
+    z_call_s,
+    z_storew,
+    z_storeb,
+    z_put_prop,
+    z_read,
+    z_print_char,
+    z_print_num,
+    z_random,
+    z_push,
+    z_pull,
+    z_split_window,
+    z_set_window,
+    z_call_s,
+    z_erase_window,
+    z_erase_line,
+    z_set_cursor,
+    z_get_cursor,
+    z_set_text_style,
+    z_buffer_mode,
+    z_output_stream,
+    z_input_stream,
+    z_sound_effect,
+    z_read_char,
+    z_scan_table,
+    z_not,
+    z_call_n,
+    z_call_n,
+    z_tokenise,
+    z_encode_text,
+    z_copy_table,
+    z_print_table,
+    z_check_arg_count
+};
+
+void (*ext_opcodes[0x1d]) (void) = {
+    z_save,
+    z_restore,
+    z_log_shift,
+    z_art_shift,
+    z_set_font,
+    z_draw_picture,
+    z_picture_data,
+    z_erase_picture,
+    z_set_margins,
+    z_save_undo,
+    z_restore_undo,
+    z_print_unicode,
+    z_check_unicode,
+    __illegal__,
+    __illegal__,
+    __illegal__,
+    z_move_window,
+    z_window_size,
+    z_window_style,
+    z_get_wind_prop,
+    z_scroll_window,
+    z_pop_stack,
+    z_read_mouse,
+    z_mouse_window,
+    z_push_stack,
+    z_put_wind_prop,
+    z_print_form,
+    z_make_menu,
+    z_picture_table
+};
+
+/*
+ * load_operand
+ *
+ * Load an operand, either a variable or a constant.
+ *
+ */
+
+static void load_operand (zbyte type)
+{
+    zword value;
+
+    if (type & 2) {                    /* variable */
+
+       zbyte variable;
+
+       CODE_BYTE (variable)
+
+       if (variable == 0)
+           value = *sp++;
+       else if (variable < 16)
+           value = *(fp - variable);
+       else {
+           zword addr = h_globals + 2 * (variable - 16);
+           LOW_WORD (addr, value)
+       }
+
+    } else if (type & 1) {             /* small constant */
+
+       zbyte bvalue;
+
+       CODE_BYTE (bvalue)
+       value = bvalue;
+
+    } else CODE_WORD (value)           /* large constant */
+
+    zargs[zargc++] = value;
+
+}/* load_operand */
+
+/*
+ * load_all_operands
+ *
+ * Given the operand specifier byte, load all (up to four) operands
+ * for a VAR or EXT opcode.
+ *
+ */
+
+static void load_all_operands (zbyte specifier)
+{
+    int i;
+
+    for (i = 6; i >= 0; i -= 2) {
+
+       zbyte type = (specifier >> i) & 0x03;
+
+       if (type == 3)
+           break;
+
+       load_operand (type);
+
+    }
+
+}/* load_all_operands */
+
+/*
+ * interpret
+ *
+ * Z-code interpreter main loop
+ *
+ */
+
+void interpret (void)
+{
+
+    do {
+
+       zbyte opcode;
+
+       CODE_BYTE (opcode)
+
+       zargc = 0;
+
+       if (opcode < 0x80) {                    /* 2OP opcodes */
+
+           load_operand ((zbyte) (opcode & 0x40) ? 2 : 1);
+           load_operand ((zbyte) (opcode & 0x20) ? 2 : 1);
+
+           var_opcodes[opcode & 0x1f] ();
+
+       } else if (opcode < 0xb0) {             /* 1OP opcodes */
+
+           load_operand ((zbyte) (opcode >> 4));
+
+           op1_opcodes[opcode & 0x0f] ();
+
+       } else if (opcode < 0xc0) {             /* 0OP opcodes */
+
+           op0_opcodes[opcode - 0xb0] ();
+
+       } else {                                /* VAR opcodes */
+
+           zbyte specifier1;
+           zbyte specifier2;
+
+           if (opcode == 0xec || opcode == 0xfa) {     /* opcodes 0xec */
+               CODE_BYTE (specifier1)                  /* and 0xfa are */
+               CODE_BYTE (specifier2)                  /* call opcodes */
+               load_all_operands (specifier1);         /* with up to 8 */
+               load_all_operands (specifier2);         /* arguments    */
+           } else {
+               CODE_BYTE (specifier1)
+               load_all_operands (specifier1);
+           }
+
+           var_opcodes[opcode - 0xc0] ();
+
+       }
+
+    } while (finished == 0);
+
+    finished--;
+
+}/* interpret */
+
+/*
+ * call
+ *
+ * Call a subroutine. Save PC and FP then load new PC and initialise
+ * new stack frame. Note that the caller may legally provide less or
+ * more arguments than the function actually has. The call type "ct"
+ * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call).
+ *
+ */
+
+void call (zword routine, int argc, zword *args, int ct)
+{
+    long pc;
+    zword value;
+    zbyte count;
+    int i;
+
+    if (sp - stack < 4)
+       runtime_error ("Stack overflow");
+
+    GET_PC (pc)
+
+    *--sp = (zword) (pc >> 9);         /* for historical reasons */
+    *--sp = (zword) (pc & 0x1ff);      /* Frotz keeps its stack  */
+    *--sp = (zword) (fp - stack - 1);  /* format compatible with */
+    *--sp = (zword) (argc | (ct << 8));        /* Mark Howell's Zip      */
+
+    fp = sp;
+
+    /* Calculate byte address of routine */
+
+    if (h_version <= V3)
+       pc = (long) routine << 1;
+    else if (h_version <= V5)
+       pc = (long) routine << 2;
+    else if (h_version <= V7)
+       pc = ((long) routine << 2) + ((long) h_functions_offset << 3);
+    else /* h_version == V8 */
+       pc = (long) routine << 3;
+
+    if (pc >= story_size)
+       runtime_error ("Call to illegal address");
+
+    SET_PC (pc)
+
+    /* Initialise local variables */
+
+    CODE_BYTE (count)
+
+    if (count > 15)
+       runtime_error ("Call to non-routine");
+    if (sp - stack < count)
+       runtime_error ("Stack overflow");
+
+    value = 0;
+
+    for (i = 0; i < count; i++) {
+
+       if (h_version <= V4)            /* V1 to V4 games provide default */
+           CODE_WORD (value)           /* values for all local variables */
+
+       *--sp = (zword) ((argc-- > 0) ? args[i] : value);
+
+    }
+
+    /* Start main loop for direct calls */
+
+    if (ct == 2)
+       interpret ();
+
+}/* call */
+
+/*
+ * ret
+ *
+ * Return from the current subroutine and restore the previous stack
+ * frame. The result may be stored (0), thrown away (1) or pushed on
+ * the stack (2). In the latter case a direct call has been finished
+ * and we must exit the interpreter loop.
+ *
+ */
+
+void ret (zword value)
+{
+    long pc;
+    int ct;
+
+    if (sp > fp)
+       runtime_error ("Stack underflow");
+
+    sp = fp;
+
+    ct = *sp++ >> 8;
+    fp = stack + 1 + *sp++;
+    pc = *sp++;
+    pc = ((long) *sp++ << 9) | pc;
+
+    SET_PC (pc)
+
+    /* Handle resulting value */
+
+    if (ct == 0)
+       store (value);
+    if (ct == 2)
+       *--sp = value;
+
+    /* Stop main loop for direct calls */
+
+    if (ct == 2)
+       finished++;
+
+}/* ret */
+
+/*
+ * branch
+ *
+ * Take a jump after an instruction based on the flag, either true or
+ * false. The branch can be short or long; it is encoded in one or two
+ * bytes respectively. When bit 7 of the first byte is set, the jump
+ * takes place if the flag is true; otherwise it is taken if the flag
+ * is false. When bit 6 of the first byte is set, the branch is short;
+ * otherwise it is long. The offset occupies the bottom 6 bits of the
+ * first byte plus all the bits in the second byte for long branches.
+ * Uniquely, an offset of 0 means return false, and an offset of 1 is
+ * return true.
+ *
+ */
+
+void branch (bool flag)
+{
+    long pc;
+    zword offset;
+    zbyte specifier;
+    zbyte off1;
+    zbyte off2;
+
+    CODE_BYTE (specifier)
+
+    off1 = specifier & 0x3f;
+
+    if (!flag)
+       specifier ^= 0x80;
+
+    if (!(specifier & 0x40)) {         /* it's a long branch */
+
+       if (off1 & 0x20)                /* propagate sign bit */
+           off1 |= 0xc0;
+
+       CODE_BYTE (off2)
+
+       offset = (off1 << 8) | off2;
+
+    } else offset = off1;              /* it's a short branch */
+
+    if (specifier & 0x80)
+
+       if (offset > 1) {               /* normal branch */
+
+           GET_PC (pc)
+           pc += (short) offset - 2;
+           SET_PC (pc)
+
+       } else ret (offset);            /* special case, return 0 or 1 */
+
+}/* branch */
+
+/*
+ * store
+ *
+ * Store an operand, either as a variable or pushed on the stack.
+ *
+ */
+
+void store (zword value)
+{
+    zbyte variable;
+
+    CODE_BYTE (variable)
+
+    if (variable == 0)
+       *--sp = value;
+    else if (variable < 16)
+       *(fp - variable) = value;
+    else {
+       zword addr = h_globals + 2 * (variable - 16);
+       SET_WORD (addr, value)
+    }
+
+}/* store */
+
+/*
+ * direct_call
+ *
+ * Call the interpreter loop directly. This is necessary when
+ *
+ * - a sound effect has been finished
+ * - a read instruction has timed out
+ * - a newline countdown has hit zero
+ *
+ * The interpreter returns the result value on the stack.
+ *
+ */
+
+int direct_call (zword addr)
+{
+    zword saved_zargs[8];
+    int saved_zargc;
+    int i;
+
+    /* Calls to address 0 return false */
+
+    if (addr == 0)
+       return 0;
+
+    /* Save operands and operand count */
+
+    for (i = 0; i < 8; i++)
+       saved_zargs[i] = zargs[i];
+
+    saved_zargc = zargc;
+
+    /* Call routine directly */
+
+    call (addr, 0, 0, 2);
+
+    /* Restore operands and operand count */
+
+    for (i = 0; i < 8; i++)
+       zargs[i] = saved_zargs[i];
+
+    zargc = saved_zargc;
+
+    /* Resulting value lies on top of the stack */
+
+    return (short) *sp++;
+
+}/* direct_call */
+
+/*
+ * __extended__
+ *
+ * Load and execute an extended opcode.
+ *
+ */
+
+static void __extended__ (void)
+{
+    zbyte opcode;
+    zbyte specifier;
+
+    CODE_BYTE (opcode)
+    CODE_BYTE (specifier)
+
+    load_all_operands (specifier);
+
+    if (opcode < 0x1d)                 /* extended opcodes from 0x1d on */
+       ext_opcodes[opcode] ();         /* are reserved for future spec' */
+
+}/* __extended__ */
+
+/*
+ * __illegal__
+ *
+ * Exit game because an unknown opcode has been hit.
+ *
+ */
+
+static void __illegal__ (void)
+{
+
+    runtime_error ("Illegal opcode");
+
+}/* __illegal__ */
+
+/*
+ * z_catch, store the current stack frame for later use with z_throw.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_catch (void)
+{
+
+    store ((zword) (fp - stack));
+
+}/* z_catch */
+
+/*
+ * z_throw, go back to the given stack frame and return the given value.
+ *
+ *     zargs[0] = value to return
+ *     zargs[1] = stack frame
+ *
+ */
+
+void z_throw (void)
+{
+
+    if (zargs[1] > STACK_SIZE)
+       runtime_error ("Bad stack frame");
+
+    fp = stack + zargs[1];
+
+    ret (zargs[0]);
+
+}/* z_throw */
+
+/*
+ * z_call_n, call a subroutine and discard its result.
+ *
+ *     zargs[0] = packed address of subroutine
+ *     zargs[1] = first argument (optional)
+ *     ...
+ *     zargs[7] = seventh argument (optional)
+ *
+ */
+
+void z_call_n (void)
+{
+
+    if (zargs[0] != 0)
+       call (zargs[0], zargc - 1, zargs + 1, 1);
+
+}/* z_call_n */
+
+/*
+ * z_call_s, call a subroutine and store its result.
+ *
+ *     zargs[0] = packed address of subroutine
+ *     zargs[1] = first argument (optional)
+ *     ...
+ *     zargs[7] = seventh argument (optional)
+ *
+ */
+
+void z_call_s (void)
+{
+
+    if (zargs[0] != 0)
+       call (zargs[0], zargc - 1, zargs + 1, 0);
+    else
+       store (0);
+
+}/* z_call_s */
+
+/*
+ * z_check_arg_count, branch if subroutine was called with >= n arg's.
+ *
+ *     zargs[0] = number of arguments
+ *
+ */
+
+void z_check_arg_count (void)
+{
+
+    if (fp == stack + STACK_SIZE)
+       branch (zargs[0] == 0);
+    else
+       branch (zargs[0] <= (*fp & 0xff));
+
+}/* z_check_arg_count */
+
+/*
+ * z_jump, jump unconditionally to the given address.
+ *
+ *     zargs[0] = PC relative address
+ *
+ */
+
+void z_jump (void)
+{
+    long pc;
+
+    GET_PC (pc)
+
+    pc += (short) zargs[0] - 2;
+
+    if (pc >= story_size)
+       runtime_error ("Jump to illegal address");
+
+    SET_PC (pc)
+
+}/* z_jump */
+
+/*
+ * z_nop, no operation.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_nop (void)
+{
+
+    /* Do nothing */
+
+}/* z_nop */
+
+/*
+ * z_quit, stop game and exit interpreter.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_quit (void)
+{
+
+    finished = 9999;
+
+}/* z_quit */
+
+/*
+ * z_ret, return from a subroutine with the given value.
+ *
+ *     zargs[0] = value to return
+ *
+ */
+
+void z_ret (void)
+{
+
+    ret (zargs[0]);
+
+}/* z_ret */
+
+/*
+ * z_ret_popped, return from a subroutine with a value popped off the stack.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_ret_popped (void)
+{
+
+    ret (*sp++);
+
+}/* z_ret_popped */
+
+/*
+ * z_rfalse, return from a subroutine with false (0).
+ *
+ *     no zargs used
+ *
+ */
+
+void z_rfalse (void)
+{
+
+    ret (0);
+
+}/* z_rfalse */
+
+/*
+ * z_rtrue, return from a subroutine with true (1).
+ *
+ *     no zargs used
+ *
+ */
+
+void z_rtrue (void)
+{
+
+    ret (1);
+
+}/* z_rtrue */
diff --git a/random.c b/random.c
new file mode 100644 (file)
index 0000000..c2df1d1
--- /dev/null
+++ b/random.c
@@ -0,0 +1,69 @@
+/*
+ * random.c
+ *
+ * Z-machine random number generator
+ *
+ */
+
+#include "frotz.h"
+
+static long A = 1;
+
+static interval = 0;
+static counter = 0;
+
+/*
+ * seed_random
+ *
+ * Set the seed value for the random number generator.
+ *
+ */
+
+void seed_random (int value)
+{
+
+    if (value == 0) {          /* ask interface for seed value */
+       A = os_random_seed ();
+       interval = 0;
+    } else if (value < 1000) { /* special seed value */
+       counter = 0;
+       interval = value;
+    } else {                   /* standard seed value */
+       A = value;
+       interval = 0;
+    }
+
+}/* seed_random */
+
+/*
+ * z_random, store a random number or set the random number seed.
+ *
+ *     zargs[0] = range (positive) or seed value (negative)
+ *
+ */
+
+void z_random ()
+{
+
+    if ((short) zargs[0] <= 0) {       /* set random seed */
+
+       seed_random (- (short) zargs[0]);
+       store (0);
+
+    } else {                           /* generate random number */
+
+       zword result;
+
+       if (interval != 0) {            /* ...in special mode */
+           result = counter++;
+           if (counter == interval) counter = 0;
+       } else {                        /* ...in standard mode */
+           A = 0x015a4e35L * A + 1;
+           result = (A >> 16) & 0x7fff;
+       }
+
+       store ((zword) (result % zargs[0] + 1));
+
+    }
+
+}/* z_random */
diff --git a/readme.txt b/readme.txt
new file mode 100644 (file)
index 0000000..bba204f
--- /dev/null
@@ -0,0 +1,103 @@
+
+
+
+This is the source distribution of DOS Frotz 2.32. Sorry,
+documentation is not complete yet, and this small readme
+file is all you get at the moment...
+
+
+
+The latest changes in the 2.32 interface are:
+
+-> Text and keys (except for file names) are now presented
+   using the new "zchar" type. This is a 1-byte value that
+   contains an ISO Latin-1 character or a special Infocom
+   character like code $09 (paragraph indentation). See
+   FROTZ.H for possible character codes. Some Infocom codes
+   have changed, too, since they collided with ISO Latin-1
+   codes. I strongly recommend using the constants defined
+   in FROTZ.H.
+
+   If you want to support different character sets, look at
+
+   * zchar definition (FROTZ.H),
+   * z_print_unicode (TEXT.C),
+   * z_check_unicode (TEXT.C),
+   * translate_from_zscii (TEXT.C),
+   * translate_to_zscii (TEXT.C),
+   * conversion to lower case in z_read (INPUT.C),
+   * script_char (FILES.C).
+
+   If you can't read accented characters from the keyboard,
+   make sure z_check_unicode stores 1 for codes from $0a to
+   $ff. If you can't write accented characters, prints some
+   suitable ASCII representation instead.
+
+-> There are some differences between Amiga and DOS picture
+   files, and an interpreter must take some extra care to
+   combine Amiga story files with DOS picture files. So far
+   the Amiga and DOS front-ends contained the necessary
+   code to make this work. Since Frotz 2.32 this problem is
+   dealt with in SCREEN.C, and interfaces no longer have to
+   worry about this.
+
+-> os_cursor_on and os_cursor_off are no more. Instead, an
+   additional argument tells os_read_key to make the cursor
+   visible or not.
+
+   (I believe a game would not want to turn off the cursor
+   for reading a string.)
+
+-> os_wait_sample is no longer needed!
+
+   SOUND.C uses the end_of_sound call to solve all problems
+   with 'The Lurking Horror' more effectively than before.
+
+-> os_start_sample should now ignore the play-once-or-loop-
+   forever information in the sound files and rely on its
+   arguments instead. The necessary information for 'The
+   Lurking Horror' has been wired into SOUND.C. This should
+   make it easier to use a more common sound format in the
+   future.
+
+-> 'Journey' asked for font #4 (fixed font) on most systems
+   since it simply expected the interpreter to be unable to
+   print a proportional font.  This unpleasant behaviour is
+   now overwritten by Frotz.
+
+-> Many extern declarations in FROTZ.H have been removed. In
+   particular, this includes
+
+   * end_of_sound,
+   * restart_header,
+   * resize_screen,
+   * completion,
+   * is_terminator,
+   * read_yes_or_no,
+   * read_string.
+
+   It's fine to call these functions from your front-end,
+   though. Just add their prototypes to your code.
+
+-> For different reasons, several front-ends felt the need
+   to interfere with the process of restarting a game.
+
+   There is now an interface function os_restart_game that
+   gives the front-end a chance to do its business while
+   the game restarts. The function is called several times
+   during the process, its sole argument indicating the
+   current "stage" of the restart:
+
+   * RESTART_BEGIN -- restart has just begun
+   * RESTART_WPROP_SET -- window properties have been set
+   * RESTART_END -- restart completed
+
+   os_restart_game is also useful to display the 'Beyond
+   Zork' title picture that is now available in MG1 format.
+
+
+
+Final note: The FONT.DAT file in this distribution must be
+appended to the DOS executable -- sorry about this!
+
+
diff --git a/redirect.c b/redirect.c
new file mode 100644 (file)
index 0000000..fc92b41
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * redirect.c
+ *
+ * Output redirection to Z-machine memory
+ *
+ */
+
+#include "frotz.h"
+
+#define MAX_NESTING 16
+
+extern zword get_max_width (zword);
+
+static depth = -1;
+
+static struct {
+    zword xsize;
+    zword table;
+    zword width;
+    zword total;
+} redirect[MAX_NESTING];
+
+/*
+ * memory_open
+ *
+ * Begin output redirection to the memory of the Z-machine.
+ *
+ */
+
+void memory_open (zword table, zword xsize, bool buffering)
+{
+
+    if (++depth < MAX_NESTING) {
+
+       if (!buffering)
+           xsize = 0xffff;
+       if (buffering && (short) xsize <= 0)
+           xsize = get_max_width ((zword) (- (short) xsize));
+
+       storew (table, 0);
+
+       redirect[depth].table = table;
+       redirect[depth].width = 0;
+       redirect[depth].total = 0;
+       redirect[depth].xsize = xsize;
+
+       ostream_memory = TRUE;
+
+   } else runtime_error ("Nesting stream #3 too deep");
+
+}/* memory_open */
+
+/*
+ * memory_new_line
+ *
+ * Redirect a newline to the memory of the Z-machine.
+ *
+ */
+
+void memory_new_line (void)
+{
+    zword size;
+    zword addr;
+
+    redirect[depth].total += redirect[depth].width;
+    redirect[depth].width = 0;
+
+    addr = redirect[depth].table;
+
+    LOW_WORD (addr, size)
+    addr += 2;
+
+    if (redirect[depth].xsize != 0xffff) {
+
+       redirect[depth].table = addr + size;
+       size = 0;
+
+    } else storeb ((zword) (addr + (size++)), 13);
+
+    storew (redirect[depth].table, size);
+
+}/* memory_new_line */
+
+/*
+ * memory_word
+ *
+ * Redirect a string of characters to the memory of the Z-machine.
+ *
+ */
+
+void memory_word (const zchar *s)
+{
+    zword size;
+    zword addr;
+    zchar c;
+
+    if (h_version == V6) {
+
+       int width = os_string_width (s);
+
+       if (redirect[depth].xsize != 0xffff)
+
+           if (redirect[depth].width + width > redirect[depth].xsize) {
+
+               if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP)
+                   width = os_string_width (++s);
+
+               memory_new_line ();
+
+           }
+
+       redirect[depth].width += width;
+
+    }
+
+    addr = redirect[depth].table;
+
+    LOW_WORD (addr, size)
+    addr += 2;
+
+    while ((c = *s++) != 0)
+       storeb ((zword) (addr + (size++)), translate_to_zscii (c));
+
+    storew (redirect[depth].table, size);
+
+}/* memory_word */
+
+/*
+ * memory_close
+ *
+ * End of output redirection.
+ *
+ */
+
+void memory_close (void)
+{
+
+    if (depth >= 0) {
+
+       if (redirect[depth].xsize != 0xffff)
+           memory_new_line ();
+
+       if (h_version == V6) {
+
+           h_line_width = (redirect[depth].xsize != 0xffff) ?
+               redirect[depth].total : redirect[depth].width;
+
+           SET_WORD (H_LINE_WIDTH, h_line_width)
+
+       }
+
+       if (depth == 0)
+           ostream_memory = FALSE;
+
+       depth--;
+
+    }
+
+}/* memory_close */
diff --git a/screen.c b/screen.c
new file mode 100644 (file)
index 0000000..851451e
--- /dev/null
+++ b/screen.c
@@ -0,0 +1,1725 @@
+/*
+ * screen.c
+ *
+ * Generic screen manipulation
+ *
+ */
+
+#include "frotz.h"
+
+extern void set_header_extension (int, zword);
+
+extern int direct_call (zword);
+
+static struct {
+    enum story story_id;
+    int pic;
+    int pic1;
+    int pic2;
+} mapper[] = {
+    { ZORK_ZERO,  5, 497, 498 },
+    { ZORK_ZERO,  6, 501, 502 },
+    { ZORK_ZERO,  7, 499, 500 },
+    { ZORK_ZERO,  8, 503, 504 },
+    {    ARTHUR, 54, 170, 171 },
+    {    SHOGUN, 50,  61,  62 },
+    {   UNKNOWN,  0,   0,   0 }
+};
+
+static font_height = 1;
+static font_width = 1;
+
+static bool input_redraw = FALSE;
+static bool more_prompts = TRUE;
+static bool discarding = FALSE;
+static bool cursor = TRUE;
+
+static input_window = 0;
+
+static struct {
+    zword y_pos;
+    zword x_pos;
+    zword y_size;
+    zword x_size;
+    zword y_cursor;
+    zword x_cursor;
+    zword left;
+    zword right;
+    zword nl_routine;
+    zword nl_countdown;
+    zword style;
+    zword colour;
+    zword font;
+    zword font_size;
+    zword attribute;
+    zword line_count;
+} wp[8], *cwp;
+
+/*
+ * winarg0
+ *
+ * Return the window number in zargs[0]. In V6 only, -3 refers to the
+ * current window.
+ *
+ */
+
+static zword winarg0 (void)
+{
+
+    if (h_version == V6 && (short) zargs[0] == -3)
+       return cwin;
+
+    if (zargs[0] >= ((h_version == V6) ? 8 : 2))
+       runtime_error ("Illegal window");
+
+    return zargs[0];
+
+}/* winarg0 */
+
+/*
+ * winarg2
+ *
+ * Return the (optional) window number in zargs[2]. -3 refers to the
+ * current window. This optional window number was only used by some
+ * V6 opcodes: set_cursor, set_margins, set_colour.
+ *
+ */
+
+static zword winarg2 (void)
+{
+
+    if (zargc < 3 || (short) zargs[2] == -3)
+       return cwin;
+
+    if (zargs[2] >= 8)
+       runtime_error ("Illegal window");
+
+    return zargs[2];
+
+}/* winarg2 */
+
+/*
+ * update_cursor
+ *
+ * Move the hardware cursor to make it match the window properties.
+ *
+ */
+
+static void update_cursor (void)
+{
+
+    os_set_cursor (
+       cwp->y_pos + cwp->y_cursor - 1,
+       cwp->x_pos + cwp->x_cursor - 1);
+
+}/* update_cursor */
+
+/*
+ * reset_cursor
+ *
+ * Reset the cursor of a given window to its initial position.
+ *
+ */
+
+static void reset_cursor (zword win)
+{
+    int lines = 0;
+
+    if (h_version <= V4 && win == 0)
+       lines = wp[0].y_size / hi (wp[0].font_size) - 1;
+
+    wp[win].y_cursor = hi (wp[0].font_size) * lines + 1;
+    wp[win].x_cursor = wp[win].left + 1;
+
+    if (win == cwin)
+       update_cursor ();
+
+}/* reset_cursor */
+
+/*
+ * set_more_prompts
+ *
+ * Turn more prompts on/off.
+ *
+ */
+
+void set_more_prompts (bool flag)
+{
+
+    if (flag && !more_prompts)
+       cwp->line_count = 0;
+
+    more_prompts = flag;
+
+}/* set_more_prompts */
+
+/*
+ * units_left
+ *
+ * Return the #screen units from the cursor to the end of the line.
+ *
+ */
+
+static int units_left (void)
+{
+
+    return cwp->x_size - cwp->right - cwp->x_cursor + 1;
+
+}/* units_left */
+
+/*
+ * get_max_width
+ *
+ * Return maximum width of a line in the given window. This is used in
+ * connection with the extended output stream #3 call in V6.
+ *
+ */
+
+zword get_max_width (zword win)
+{
+
+    if (h_version == V6) {
+
+       if (win >= 8)
+           runtime_error ("Illegal window");
+
+       return wp[win].x_size - wp[win].left - wp[win].right;
+
+    } else return 0xffff;
+
+}/* get_max_width */
+
+/*
+ * countdown
+ *
+ * Decrement the newline counter. Call the newline interrupt when the
+ * counter hits zero. This is a helper function for screen_new_line.
+ *
+ */
+
+static void countdown (void)
+{
+
+    if (cwp->nl_countdown != 0)
+       if (--cwp->nl_countdown == 0)
+           direct_call (cwp->nl_routine);
+
+}/* countdown */
+
+/*
+ * screen_new_line
+ *
+ * Print a newline to the screen.
+ *
+ */
+
+void screen_new_line (void)
+{
+
+    if (discarding) return;
+
+    /* Handle newline interrupts at the start (for most cases) */
+
+    if (h_interpreter_number != INTERP_MSDOS || story_id != ZORK_ZERO || h_release != 393)
+       countdown ();
+
+    /* Check whether the last input line gets destroyed */
+
+    if (input_window == cwin)
+       input_redraw = TRUE;
+
+    /* If the cursor has not reached the bottom line, then move it to
+       the next line; otherwise scroll the window or reset the cursor
+       to the top left. */
+
+    cwp->x_cursor = cwp->left + 1;
+
+    if (cwp->y_cursor + 2 * font_height - 1 > cwp->y_size)
+
+       if (enable_scrolling) {
+
+           zword y = cwp->y_pos;
+           zword x = cwp->x_pos;
+
+           os_scroll_area (y,
+                           x,
+                           y + cwp->y_size - 1,
+                           x + cwp->x_size - 1,
+                           font_height);
+
+       } else cwp->y_cursor = 1;
+
+    else cwp->y_cursor += font_height;
+
+    update_cursor ();
+
+    /* See if we need to print a more prompt (unless the game has set
+       the line counter to -999 in order to suppress more prompts). */
+
+    if (enable_scrolling && (short) cwp->line_count != -999) {
+
+       zword above = (cwp->y_cursor - 1) / font_height;
+       zword below = (cwp->y_size - cwp->y_cursor + 1) / font_height;
+
+       cwp->line_count++;
+
+       if ((short) cwp->line_count >= (short) above + below - 1) {
+
+           if (more_prompts)
+               os_more_prompt ();
+
+           cwp->line_count = option_context_lines;
+
+       }
+
+    }
+
+    /* Handle newline interrupts at the end for Zork Zero under DOS */
+
+    if (h_interpreter_number == INTERP_MSDOS && story_id == ZORK_ZERO && h_release == 393)
+       countdown ();
+
+}/* screen_new_line */
+
+/*
+ * screen_char
+ *
+ * Display a single character on the screen.
+ *
+ */
+
+void screen_char (zchar c)
+{
+    int width;
+
+    if (discarding) return;
+
+    if (c == ZC_INDENT && cwp->x_cursor != cwp->left + 1)
+       c = ' ';
+
+    if (units_left () < (width = os_char_width (c))) {
+
+       if (!enable_wrapping)
+           { cwp->x_cursor = cwp->x_size - cwp->right; return; }
+
+       screen_new_line ();
+
+    }
+
+    os_display_char (c); cwp->x_cursor += width;
+
+}/* screen_char */
+
+/*
+ * screen_word
+ *
+ * Display a string of characters on the screen. If the word doesn't fit
+ * then use wrapping or clipping depending on the current setting of the
+ * enable_wrapping flag.
+ *
+ */
+
+void screen_word (const zchar *s)
+{
+    int width;
+
+    if (discarding) return;
+
+    if (*s == ZC_INDENT && cwp->x_cursor != cwp->left + 1)
+       screen_char (*s++);
+
+    if (units_left () < (width = os_string_width (s))) {
+
+       if (!enable_wrapping) {
+
+           zchar c;
+
+           while ((c = *s++) != 0)
+
+               if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) {
+
+                   int arg = (int) *s++;
+
+                   if (c == ZC_NEW_FONT)
+                       os_set_font (arg);
+                   if (c == ZC_NEW_STYLE)
+                       os_set_text_style (arg);
+
+               } else screen_char (c);
+
+           return;
+
+       }
+
+       if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP)
+           width = os_string_width (++s);
+
+#ifdef AMIGA
+       if (cwin == 0) Justifiable ();
+#endif
+
+       screen_new_line ();
+
+    }
+
+    os_display_string (s); cwp->x_cursor += width;
+
+}/* screen_word */
+
+/*
+ * screen_write_input
+ *
+ * Display an input line on the screen. This is required during playback.
+ *
+ */
+
+void screen_write_input (const zchar *buf, zchar key)
+{
+    int width;
+
+    if (units_left () < (width = os_string_width (buf)))
+       screen_new_line ();
+
+    os_display_string (buf); cwp->x_cursor += width;
+
+    if (key == ZC_RETURN)
+       screen_new_line ();
+
+}/* screen_write_input */
+
+/*
+ * screen_erase_input
+ *
+ * Remove an input line that has already been printed from the screen
+ * as if it was deleted by the player. This could be necessary during
+ * playback.
+ *
+ */
+
+void screen_erase_input (const zchar *buf)
+{
+
+    if (buf[0] != 0) {
+
+       int width = os_string_width (buf);
+
+       zword y;
+       zword x;
+
+       cwp->x_cursor -= width;
+
+       y = cwp->y_pos + cwp->y_cursor - 1;
+       x = cwp->x_pos + cwp->x_cursor - 1;
+
+       os_erase_area (y, x, y + font_height - 1, x + width - 1);
+       os_set_cursor (y, x);
+
+    }
+
+}/* screen_erase_input */
+
+/*
+ * console_read_input
+ *
+ * Read an input line from the keyboard and return the terminating key.
+ *
+ */
+
+zchar console_read_input (int max, zchar *buf, zword timeout, bool continued)
+{
+    zchar key;
+    int i;
+
+    /* Make sure there is some space for input */
+
+    if (cwin == 0 && units_left () + os_string_width (buf) < 10 * font_width)
+       screen_new_line ();
+
+    /* Make sure the input line is visible */
+
+    if (continued && input_redraw)
+       screen_write_input (buf, -1);
+
+    input_window = cwin;
+    input_redraw = FALSE;
+
+    /* Get input line from IO interface */
+
+    cwp->x_cursor -= os_string_width (buf);
+    key = os_read_line (max, buf, timeout, units_left (), continued);
+    cwp->x_cursor += os_string_width (buf);
+
+    if (key != ZC_TIME_OUT)
+       for (i = 0; i < 8; i++)
+           wp[i].line_count = 0;
+
+    /* Add a newline if the input was terminated normally */
+
+    if (key == ZC_RETURN)
+       screen_new_line ();
+
+    return key;
+
+}/* console_read_input */
+
+/*
+ * console_read_key
+ *
+ * Read a single keystroke and return it.
+ *
+ */
+
+zchar console_read_key (zword timeout)
+{
+    zchar key;
+    int i;
+
+    key = os_read_key (timeout, cursor);
+
+    if (key != ZC_TIME_OUT)
+       for (i = 0; i < 8; i++)
+           wp[i].line_count = 0;
+
+    return key;
+
+}/* console_read_key */
+
+/*
+ * update_attributes
+ *
+ * Set the three enable_*** variables to make them match the attributes
+ * of the current window.
+ *
+ */
+
+static void update_attributes (void)
+{
+    zword attr = cwp->attribute;
+
+    enable_wrapping = attr & 1;
+    enable_scrolling = attr & 2;
+    enable_scripting = attr & 4;
+    enable_buffering = attr & 8;
+
+    /* Some story files forget to select wrapping for printing hints */
+
+    if (story_id == ZORK_ZERO && h_release == 366)
+       if (cwin == 0)
+           enable_wrapping = TRUE;
+    if (story_id == SHOGUN && h_release <= 295)
+       if (cwin == 0)
+           enable_wrapping = TRUE;
+
+}/* update_attributes */
+
+/*
+ * refresh_text_style
+ *
+ * Set the right text style. This can be necessary when the fixed font
+ * flag is changed, or when a new window is selected, or when the game
+ * uses the set_text_style opcode.
+ *
+ */
+
+void refresh_text_style (void)
+{
+    zword style;
+
+    if (h_version != V6) {
+
+       style = wp[0].style;
+
+       if (cwin != 0 || h_flags & FIXED_FONT_FLAG)
+           style |= FIXED_WIDTH_STYLE;
+
+    } else style = cwp->style;
+
+    if (!ostream_memory && ostream_screen && enable_buffering) {
+
+       print_char (ZC_NEW_STYLE);
+       print_char (style);
+
+    } else os_set_text_style (style);
+
+}/* refresh_text_style */
+
+/*
+ * set_window
+ *
+ * Set the current window. In V6 every window has its own set of window
+ * properties such as colours, text style, cursor position and size.
+ *
+ */
+
+static void set_window (zword win)
+{
+
+    flush_buffer ();
+
+    cwin = win; cwp = wp + win;
+
+    update_attributes ();
+
+    if (h_version == V6) {
+
+       os_set_colour (lo (cwp->colour), hi (cwp->colour));
+
+       if (os_font_data (cwp->font, &font_height, &font_width))
+           os_set_font (cwp->font);
+
+       os_set_text_style (cwp->style);
+
+    } else refresh_text_style ();
+
+    if (h_version != V6 && win != 0) {
+       wp[win].y_cursor = 1;
+       wp[win].x_cursor = 1;
+    }
+
+    update_cursor ();
+
+}/* set_window */
+
+/*
+ * erase_window
+ *
+ * Erase a window to background colour.
+ *
+ */
+
+static void erase_window (zword win)
+{
+    zword y = wp[win].y_pos;
+    zword x = wp[win].x_pos;
+
+    if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA)
+       os_set_colour (lo (wp[win].colour), hi (wp[win].colour));
+
+    os_erase_area (y,
+                  x,
+                  y + wp[win].y_size - 1,
+                  x + wp[win].x_size - 1);
+
+    if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA)
+       os_set_colour (lo (cwp->colour), hi (cwp->colour));
+
+    reset_cursor (win);
+
+    wp[win].line_count = 0;
+
+}/* erase_window */
+
+/*
+ * split_window
+ *
+ * Divide the screen into upper (1) and lower (0) windows. In V3 the upper
+ * window appears below the status line.
+ *
+ */
+
+void split_window (zword height)
+{
+    zword stat_height = 0;
+
+    flush_buffer ();
+
+    /* Calculate height of status line and upper window */
+
+    if (h_version != V6)
+       height *= hi (wp[1].font_size);
+
+    if (h_version <= V3)
+       stat_height = hi (wp[7].font_size);
+
+    /* Cursor of upper window mustn't be swallowed by the lower window */
+
+    wp[1].y_cursor += wp[1].y_pos - 1 - stat_height;
+
+    wp[1].y_pos = 1 + stat_height;
+    wp[1].y_size = height;
+
+    if ((short) wp[1].y_cursor > (short) wp[1].y_size)
+       reset_cursor (1);
+
+    /* Cursor of lower window mustn't be swallowed by the upper window */
+
+    wp[0].y_cursor += wp[0].y_pos - 1 - stat_height - height;
+
+    wp[0].y_pos = 1 + stat_height + height;
+    wp[0].y_size = h_screen_height - stat_height - height;
+
+    if ((short) wp[0].y_cursor < 1)
+       reset_cursor (0);
+
+    /* Erase the upper window in V3 only */
+
+    if (h_version == V3 && height != 0)
+       erase_window (1);
+
+}/* split_window */
+
+/*
+ * erase_screen
+ *
+ * Erase the entire screen to background colour.
+ *
+ */
+
+static void erase_screen (zword win)
+{
+    int i;
+
+    os_erase_area (1, 1, h_screen_height, h_screen_width);
+
+    if ((short) win == -1) {
+       split_window (0);
+       set_window (0);
+       reset_cursor (0);
+    }
+
+    for (i = 0; i < 8; i++)
+       wp[i].line_count = 0;
+
+}/* erase_screen */
+
+#ifdef AMIGA
+
+/*
+ * resize_screen
+ *
+ * Try to adapt the window properties to a new screen size.
+ *
+ */
+
+void resize_screen (void)
+{
+
+    if (h_version != V6) {
+
+       wp[0].x_size = h_screen_width;
+       wp[1].x_size = h_screen_width;
+       wp[7].x_size = h_screen_width;
+
+       wp[0].y_size = h_screen_height - wp[1].y_size - wp[7].y_size;
+
+    }
+
+}/* resize_screen */
+
+#endif
+
+/*
+ * restart_screen
+ *
+ * Prepare the screen for a new game.
+ *
+ */
+
+void restart_screen (void)
+{
+
+    /* Use default settings */
+
+    os_set_colour (h_default_foreground, h_default_background);
+
+    if (os_font_data (TEXT_FONT, &font_height, &font_width))
+       os_set_font (TEXT_FONT);
+
+    os_set_text_style (0);
+
+    cursor = TRUE;
+
+    /* Initialise window properties */
+
+    mwin = 1;
+
+    for (cwp = wp; cwp < wp + 8; cwp++) {
+       cwp->y_pos = 1;
+       cwp->x_pos = 1;
+       cwp->y_size = 0;
+       cwp->x_size = 0;
+       cwp->y_cursor = 1;
+       cwp->x_cursor = 1;
+       cwp->left = 0;
+       cwp->right = 0;
+       cwp->nl_routine = 0;
+       cwp->nl_countdown = 0;
+       cwp->style = 0;
+       cwp->colour = (h_default_background << 8) | h_default_foreground;
+       cwp->font = TEXT_FONT;
+       cwp->font_size = (font_height << 8) | font_width;
+       cwp->attribute = 8;
+    }
+
+    /* Prepare lower/upper windows and status line */
+
+    wp[0].attribute = 15;
+
+    wp[0].left = option_left_margin;
+    wp[0].right = option_right_margin;
+
+    wp[0].x_size = h_screen_width;
+    wp[1].x_size = h_screen_width;
+
+    if (h_version <= V3)
+       wp[7].x_size = h_screen_width;
+
+    os_restart_game (RESTART_WPROP_SET);
+
+    /* Clear the screen, unsplit it and select window 0 */
+
+    erase_screen ((zword) (-1));
+
+}/* restart_screen */
+
+/*
+ * validate_click
+ *
+ * Return false if the last mouse click occured outside the current
+ * mouse window; otherwise write the mouse arrow coordinates to the
+ * memory of the header extension table and return true.
+ *
+ */
+
+bool validate_click (void)
+{
+
+    if (mwin >= 0) {
+
+       if (mouse_y < wp[mwin].y_pos || mouse_y >= wp[mwin].y_pos + wp[mwin].y_size)
+           return FALSE;
+       if (mouse_x < wp[mwin].x_pos || mouse_x >= wp[mwin].x_pos + wp[mwin].x_size)
+           return FALSE;
+
+       hx_mouse_y = mouse_y - wp[mwin].y_pos + 1;
+       hx_mouse_x = mouse_x - wp[mwin].x_pos + 1;
+
+    } else {
+
+       if (mouse_y < 1 || mouse_y > h_screen_height)
+           return FALSE;
+       if (mouse_x < 1 || mouse_x > h_screen_width)
+           return FALSE;
+
+       hx_mouse_y = mouse_y;
+       hx_mouse_x = mouse_x;
+
+    }
+
+    if (h_version != V6) {
+       hx_mouse_y = (hx_mouse_y - 1) / h_font_height + 1;
+       hx_mouse_x = (hx_mouse_x - 1) / h_font_width + 1;
+    }
+
+    set_header_extension (HX_MOUSE_Y, hx_mouse_y);
+    set_header_extension (HX_MOUSE_X, hx_mouse_x);
+
+    return TRUE;
+
+}/* validate_click */
+
+/*
+ * screen_mssg_on
+ *
+ * Start printing a so-called debugging message. The contents of the
+ * message are passed to the message stream, a Frotz specific output
+ * stream with maximum priority.
+ *
+ */
+
+void screen_mssg_on (void)
+{
+
+    if (cwin == 0) {           /* messages in window 0 only */
+
+       os_set_text_style (0);
+
+       if (cwp->x_cursor != cwp->left + 1)
+           screen_new_line ();
+
+       screen_char (ZC_INDENT);
+
+    } else discarding = TRUE;  /* discard messages in other windows */
+
+}/* screen_mssg_on */
+
+/*
+ * screen_mssg_off
+ *
+ * Stop printing a "debugging" message.
+ *
+ */
+
+void screen_mssg_off (void)
+{
+
+    if (cwin == 0) {           /* messages in window 0 only */
+
+       screen_new_line ();
+
+       refresh_text_style ();
+
+    } else discarding = FALSE;         /* message has been discarded */
+
+}/* screen_mssg_off */
+
+/*
+ * z_buffer_mode, turn text buffering on/off.
+ *
+ *     zargs[0] = new text buffering flag (0 or 1)
+ *
+ */
+
+void z_buffer_mode (void)
+{
+
+    /* Infocom's V6 games rarely use the buffer_mode opcode. If they do
+       then only to print text immediately, without any delay. This was
+       used to give the player some sign of life while the game was
+       spending much time on parsing a complicated input line. (To turn
+       off word wrapping, V6 games use the window_style opcode instead.)
+       Today we can afford to ignore buffer_mode in V6. */
+
+    if (h_version != V6) {
+
+       flush_buffer ();
+
+       wp[0].attribute &= ~8;
+
+       if (zargs[0] != 0)
+           wp[0].attribute |= 8;
+
+       update_attributes ();
+
+    }
+
+}/* z_buffer_mode */
+
+/*
+ * z_draw_picture, draw a picture.
+ *
+ *     zargs[0] = number of picture to draw
+ *     zargs[1] = y-coordinate of top left corner
+ *     zargs[2] = x-coordinate of top left corner
+ *
+ */
+
+void z_draw_picture (void)
+{
+    zword pic = zargs[0];
+
+    zword y = zargs[1];
+    zword x = zargs[2];
+
+    int i;
+
+    flush_buffer ();
+
+    if (y == 0)                        /* use cursor line if y-coordinate is 0 */
+       y = cwp->y_cursor;
+    if (x == 0)                /* use cursor column if x-coordinate is 0 */
+       x = cwp->x_cursor;
+
+    y += cwp->y_pos - 1;
+    x += cwp->x_pos - 1;
+
+    /* The following is necessary to make Amiga and Macintosh story
+       files work with MCGA graphics files.  Some screen-filling
+       pictures of the original Amiga release like the borders of
+       Zork Zero were split into several MCGA pictures (left, right
+       and top borders).  We pretend this has not happened. */
+
+    for (i = 0; mapper[i].story_id != UNKNOWN; i++)
+
+       if (story_id == mapper[i].story_id && pic == mapper[i].pic) {
+
+           int height1, width1;
+           int height2, width2;
+
+           int delta = 0;
+
+           os_picture_data (pic, &height1, &width1);
+           os_picture_data (mapper[i].pic2, &height2, &width2);
+
+           if (story_id == ARTHUR && pic == 54)
+               delta = h_screen_width / 160;
+
+           os_draw_picture (mapper[i].pic1, y + height1, x + delta);
+           os_draw_picture (mapper[i].pic2, y + height1, x + width1 - width2 - delta);
+
+       }
+
+    os_draw_picture (pic, y, x);
+
+    if (story_id == SHOGUN)
+
+       if (pic == 3) {
+
+           int height, width;
+
+           os_picture_data (59, &height, &width);
+           os_draw_picture (59, y, h_screen_width - width + 1);
+
+       }
+
+}/* z_draw_picture */
+
+/*
+ * z_erase_line, erase the line starting at the cursor position.
+ *
+ *     zargs[0] = 1 + #units to erase (1 clears to the end of the line)
+ *
+ */
+
+void z_erase_line (void)
+{
+    zword pixels = zargs[0];
+    zword y, x;
+
+    flush_buffer ();
+
+    /* Clipping at the right margin of the current window */
+
+    if (--pixels == 0 || pixels > units_left ())
+       pixels = units_left ();
+
+    /* Erase from cursor position */
+
+    y = cwp->y_pos + cwp->y_cursor - 1;
+    x = cwp->x_pos + cwp->x_cursor - 1;
+
+    os_erase_area (y, x, y + font_height - 1, x + pixels - 1);
+
+}/* z_erase_line */
+
+/*
+ * z_erase_picture, erase a picture with background colour.
+ *
+ *     zargs[0] = number of picture to erase
+ *     zargs[1] = y-coordinate of top left corner (optional)
+ *     zargs[2] = x-coordinate of top left corner (optional)
+ *
+ */
+
+void z_erase_picture (void)
+{
+    int height, width;
+
+    zword y = zargs[1];
+    zword x = zargs[2];
+
+    flush_buffer ();
+
+    if (y == 0)                /* use cursor line if y-coordinate is 0 */
+       y = cwp->y_cursor;
+    if (x == 0)        /* use cursor column if x-coordinate is 0 */
+       x = cwp->x_cursor;
+
+    os_picture_data (zargs[0], &height, &width);
+
+    y += cwp->y_pos - 1;
+    x += cwp->x_pos - 1;
+
+    os_erase_area (y, x, y + height - 1, x + width - 1);
+
+}/* z_erase_picture */
+
+/*
+ * z_erase_window, erase a window or the screen to background colour.
+ *
+ *     zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit)
+ *
+ */
+
+void z_erase_window (void)
+{
+
+    flush_buffer ();
+
+    if ((short) zargs[0] == -1 || (short) zargs[0] == -2)
+       erase_screen (zargs[0]);
+    else
+       erase_window (winarg0 ());
+
+}/* z_erase_window */
+
+/*
+ * z_get_cursor, write the cursor coordinates into a table.
+ *
+ *     zargs[0] = address to write information to
+ *
+ */
+
+void z_get_cursor (void)
+{
+    zword y, x;
+
+    flush_buffer ();
+
+    y = cwp->y_cursor;
+    x = cwp->x_cursor;
+
+    if (h_version != V6) {     /* convert to grid positions */
+       y = (y - 1) / h_font_height + 1;
+       x = (x - 1) / h_font_width + 1;
+    }
+
+    storew ((zword) (zargs[0] + 0), y);
+    storew ((zword) (zargs[0] + 2), x);
+
+}/* z_get_cursor */
+
+/*
+ * z_get_wind_prop, store the value of a window property.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = number of window property to be stored
+ *
+ */
+
+void z_get_wind_prop (void)
+{
+
+    flush_buffer ();
+
+    if (zargs[1] >= 16)
+       runtime_error ("Illegal window property");
+
+    store (((zword *) (wp + winarg0 ())) [zargs[1]]);
+
+}/* z_get_wind_prop */
+
+/*
+ * z_mouse_window, select a window as mouse window.
+ *
+ *     zargs[0] = window number (-3 is the current) or -1 for the screen
+ *
+ */
+
+void z_mouse_window (void)
+{
+
+    mwin = ((short) zargs[0] == -1) ? -1 : winarg0 ();
+
+}/* z_mouse_window */
+
+/*
+ * z_move_window, place a window on the screen.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = y-coordinate
+ *     zargs[2] = x-coordinate
+ *
+ */
+
+void z_move_window (void)
+{
+    zword win = winarg0 ();
+
+    flush_buffer ();
+
+    wp[win].y_pos = zargs[1];
+    wp[win].x_pos = zargs[2];
+
+    if (win == cwin)
+       update_cursor ();
+
+}/* z_move_window */
+
+/*
+ * z_picture_data, get information on a picture or the graphics file.
+ *
+ *     zargs[0] = number of picture or 0 for the graphics file
+ *     zargs[1] = address to write information to
+ *
+ */
+
+void z_picture_data (void)
+{
+    zword pic = zargs[0];
+    zword table = zargs[1];
+
+    int height, width;
+    int i;
+
+    bool avail = os_picture_data (pic, &height, &width);
+
+    for (i = 0; mapper[i].story_id != UNKNOWN; i++)
+
+       if (story_id == mapper[i].story_id)
+
+           if (pic == mapper[i].pic) {
+
+               int height2, width2;
+
+               avail &= os_picture_data (mapper[i].pic1, &height2, &width2);
+               avail &= os_picture_data (mapper[i].pic2, &height2, &width2);
+
+               height += height2;
+
+           } else if (pic == mapper[i].pic1 || pic == mapper[i].pic2)
+
+               avail = FALSE;
+
+    storew ((zword) (table + 0), (zword) (height));
+    storew ((zword) (table + 2), (zword) (width));
+
+    branch (avail);
+
+}/* z_picture_data */
+
+/*
+ * z_picture_table, prepare a group of pictures for faster display.
+ *
+ *     zargs[0] = address of table holding the picture numbers
+ *
+ */
+
+void z_picture_table (void)
+{
+
+    /* This opcode is used by Shogun and Zork Zero when the player
+       encounters built-in games such as Peggleboz. Nowadays it is
+       not very helpful to hold the picture data in memory because
+       even a small disk cache avoids re-loading of data. */
+
+}/* z_picture_table */
+
+/*
+ * z_print_table, print ASCII text in a rectangular area.
+ *
+ *     zargs[0] = address of text to be printed
+ *     zargs[1] = width of rectangular area
+ *     zargs[2] = height of rectangular area (optional)
+ *     zargs[3] = number of char's to skip between lines (optional)
+ *
+ */
+
+void z_print_table (void)
+{
+    zword addr = zargs[0];
+    zword x;
+    int i, j;
+
+    flush_buffer ();
+
+    /* Supply default arguments */
+
+    if (zargc < 3)
+       zargs[2] = 1;
+    if (zargc < 4)
+       zargs[3] = 0;
+
+    /* Write text in width x height rectangle */
+
+    x = cwp->x_cursor;
+
+    for (i = 0; i < zargs[2]; i++) {
+
+       if (i != 0) {
+
+           flush_buffer ();
+
+           cwp->y_cursor += font_height;
+           cwp->x_cursor = x;
+
+           update_cursor ();
+
+       }
+
+       for (j = 0; j < zargs[1]; j++) {
+
+           zbyte c;
+
+           LOW_BYTE (addr, c)
+           addr++;
+
+           print_char (c);
+
+       }
+
+       addr += zargs[3];
+
+    }
+
+}/* z_print_table */
+
+/*
+ * z_put_wind_prop, set the value of a window property.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = number of window property to set
+ *     zargs[2] = value to set window property to
+ *
+ */
+
+void z_put_wind_prop (void)
+{
+
+    flush_buffer ();
+
+    if (zargs[1] >= 16)
+       runtime_error ("Illegal window property");
+
+    ((zword *) (wp + winarg0 ())) [zargs[1]] = zargs[2];
+
+}/* z_put_wind_prop */
+
+/*
+ * z_scroll_window, scroll a window up or down.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = #screen units to scroll up (positive) or down (negative)
+ *
+ */
+
+void z_scroll_window (void)
+{
+    zword win = winarg0 ();
+    zword y, x;
+
+    flush_buffer ();
+
+    /* Use the correct set of colours when scrolling the window */
+
+    if (win != cwin && h_interpreter_number != INTERP_AMIGA)
+       os_set_colour (lo (wp[win].colour), hi (wp[win].colour));
+
+    y = wp[win].y_pos;
+    x = wp[win].x_pos;
+
+    os_scroll_area (y,
+                   x,
+                   y + wp[win].y_size - 1,
+                   x + wp[win].x_size - 1,
+                   (short) zargs[1]);
+
+    if (win != cwin && h_interpreter_number != INTERP_AMIGA)
+       os_set_colour (lo (cwp->colour), hi (cwp->colour));
+
+}/* z_scroll_window */
+
+/*
+ * z_set_colour, set the foreground and background colours.
+ *
+ *     zargs[0] = foreground colour
+ *     zargs[1] = background colour
+ *     zargs[2] = window (-3 is the current one, optional)
+ *
+ */
+
+void z_set_colour (void)
+{
+    zword win = (h_version == V6) ? winarg2 () : 0;
+
+    zword fg = zargs[0];
+    zword bg = zargs[1];
+
+    flush_buffer ();
+
+    if ((short) fg == -1)      /* colour -1 is the colour at the cursor */
+       fg = os_peek_colour ();
+    if ((short) bg == -1)
+       bg = os_peek_colour ();
+
+    if (fg == 0)               /* colour 0 means keep current colour */
+       fg = lo (wp[win].colour);
+    if (bg == 0)
+       bg = hi (wp[win].colour);
+
+    if (fg == 1)               /* colour 1 is the system default colour */
+       fg = h_default_foreground;
+    if (bg == 1)
+       bg = h_default_background;
+
+    if (h_version == V6 && h_interpreter_number == INTERP_AMIGA)
+
+       /* Changing colours of window 0 affects the entire screen */
+
+       if (win == 0) {
+
+           int i;
+
+           for (i = 1; i < 8; i++) {
+
+               zword bg2 = hi (wp[i].colour);
+               zword fg2 = lo (wp[i].colour);
+
+               if (bg2 < 16)
+                   bg2 = (bg2 == lo (wp[0].colour)) ? fg : bg;
+               if (fg2 < 16)
+                   fg2 = (fg2 == lo (wp[0].colour)) ? fg : bg;
+
+               wp[i].colour = (bg2 << 8) | fg2;
+
+           }
+
+       }
+
+    wp[win].colour = (bg << 8) | fg;
+
+    if (win == cwin || h_version != V6)
+       os_set_colour (fg, bg);
+
+}/* z_set_colour */
+
+/*
+ * z_set_font, set the font for text output and store the previous font.
+ *
+ *     zargs[0] = number of font or 0 to keep current font
+ *
+ */
+
+void z_set_font (void)
+{
+    zword win = (h_version == V6) ? cwin : 0;
+    zword font = zargs[0];
+
+    if (font != 0) {
+
+       if (story_id == JOURNEY && font == 4)   /* Journey uses fixed fonts */
+           font = 1;                           /* for most interpreter #'s */
+
+       if (os_font_data (font, &font_height, &font_width)) {
+
+           store (wp[win].font);
+
+           wp[win].font = font;
+           wp[win].font_size = (font_height << 8) | font_width;
+
+           if (!ostream_memory && ostream_screen && enable_buffering) {
+
+               print_char (ZC_NEW_FONT);
+               print_char (font);
+
+           } else os_set_font (font);
+
+       } else store (0);
+
+    } else store (wp[win].font);
+
+}/* z_set_font */
+
+/*
+ * z_set_cursor, set the cursor position or turn the cursor on/off.
+ *
+ *     zargs[0] = y-coordinate or -2/-1 for cursor on/off
+ *     zargs[1] = x-coordinate
+ *     zargs[2] = window (-3 is the current one, optional)
+ *
+ */
+
+void z_set_cursor (void)
+{
+    zword win = (h_version == V6) ? winarg2 () : 1;
+
+    zword y = zargs[0];
+    zword x = zargs[1];
+
+    flush_buffer ();
+
+    /* Supply default arguments */
+
+    if (zargc < 3)
+       zargs[2] = -3;
+
+    /* Handle cursor on/off */
+
+    if ((short) y < 0) {
+
+       if ((short) y == -2)
+           cursor = TRUE;
+       if ((short) y == -1)
+           cursor = FALSE;
+
+       return;
+
+    }
+
+    /* Convert grid positions to screen units if this is not V6 */
+
+    if (h_version != V6) {
+
+       if (cwin == 0)
+           return;
+
+       y = (y - 1) * h_font_height + 1;
+       x = (x - 1) * h_font_width + 1;
+
+    }
+
+    /* Protect the margins */
+
+    if (x <= wp[win].left || x > wp[win].x_size - wp[win].right)
+       x = wp[win].left + 1;
+
+    /* Move the cursor */
+
+    wp[win].y_cursor = y;
+    wp[win].x_cursor = x;
+
+    if (win == cwin)
+       update_cursor ();
+
+}/* z_set_cursor */
+
+/*
+ * z_set_margins, set the left and right margins of a window.
+ *
+ *     zargs[0] = left margin in pixels
+ *     zargs[1] = right margin in pixels
+ *     zargs[2] = window (-3 is the current one, optional)
+ *
+ */
+
+void z_set_margins (void)
+{
+    zword win = winarg2 ();
+
+    flush_buffer ();
+
+    wp[win].left = zargs[0];
+    wp[win].right = zargs[1];
+
+    /* Protect the margins */
+
+    if (wp[win].x_cursor <= zargs[0] || wp[win].x_cursor > wp[win].x_size - zargs[1]) {
+
+       wp[win].x_cursor = zargs[0] + 1;
+
+       if (win == cwin)
+           update_cursor ();
+
+    }
+
+}/* z_set_margins */
+
+/*
+ * z_set_text_style, set the style for text output.
+ *
+ *     zargs[0] = style flags to set or 0 to reset text style
+ *
+ */
+
+void z_set_text_style (void)
+{
+    zword win = (h_version == V6) ? cwin : 0;
+    zword style = zargs[0];
+
+    wp[win].style |= style;
+
+    if (style == 0)
+       wp[win].style = 0;
+
+    refresh_text_style ();
+
+}/* z_set_text_style */
+
+/*
+ * z_set_window, select the current window.
+ *
+ *     zargs[0] = window to be selected (-3 is the current one)
+ *
+ */
+
+void z_set_window (void)
+{
+
+    set_window (winarg0 ());
+
+}/* z_set_window */
+
+/*
+ * pad_status_line
+ *
+ * Pad the status line with spaces up to the given position.
+ *
+ */
+
+static void pad_status_line (int column)
+{
+    int spaces;
+
+    flush_buffer ();
+
+    spaces = units_left () / os_char_width (' ') - column;
+
+    while (spaces--)
+       screen_char (' ');
+
+}/* pad_status_line */
+
+/*
+ * z_show_status, display the status line for V1 to V3 games.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_show_status (void)
+{
+    zword global0;
+    zword global1;
+    zword global2;
+    zword addr;
+
+    bool brief = FALSE;
+
+    /* One V5 game (Wishbringer Solid Gold) contains this opcode by
+       accident, so just return if the version number does not fit */
+
+    if (h_version >= V4)
+       return;
+
+    /* Read all relevant global variables from the memory of the
+       Z-machine into local variables */
+
+    addr = h_globals;
+    LOW_WORD (addr, global0)
+    addr += 2;
+    LOW_WORD (addr, global1)
+    addr += 2;
+    LOW_WORD (addr, global2)
+
+    /* Frotz uses window 7 for the status line. Don't forget to select
+       reverse and fixed width text style */
+
+    set_window (7);
+
+    print_char (ZC_NEW_STYLE);
+    print_char (REVERSE_STYLE | FIXED_WIDTH_STYLE);
+
+    /* If the screen width is below 55 characters then we have to use
+       the brief status line format */
+
+    if (h_screen_cols < 55)
+       brief = TRUE;
+
+    /* Print the object description for the global variable 0 */
+
+    print_char (' ');
+    print_object (global0);
+
+    /* A header flag tells us whether we have to display the current
+       time or the score/moves information */
+
+    if (h_config & CONFIG_TIME) {      /* print hours and minutes */
+
+       zword hours = (global1 + 11) % 12 + 1;
+
+       pad_status_line (brief ? 15 : 20);
+
+       print_string ("Time: ");
+
+       if (hours < 10)
+           print_char (' ');
+       print_num (hours);
+
+       print_char (':');
+
+       if (global2 < 10)
+           print_char ('0');
+       print_num (global2);
+
+       print_char (' ');
+
+       print_char ((global1 >= 12) ? 'p' : 'a');
+       print_char ('m');
+
+    } else {                           /* print score and moves */
+
+       pad_status_line (brief ? 15 : 30);
+
+       print_string (brief ? "S: " : "Score: ");
+       print_num (global1);
+
+       pad_status_line (brief ? 8 : 14);
+
+       print_string (brief ? "M: " : "Moves: ");
+       print_num (global2);
+
+    }
+
+    /* Pad the end of the status line with spaces */
+
+    pad_status_line (0);
+
+    /* Return to the lower window */
+
+    set_window (0);
+
+}/* z_show_status */
+
+/*
+ * z_split_window, split the screen into an upper (1) and lower (0) window.
+ *
+ *     zargs[0] = height of upper window in screen units (V6) or #lines
+ *
+ */
+
+void z_split_window (void)
+{
+
+    split_window (zargs[0]);
+
+}/* z_split_window */
+
+/*
+ * z_window_size, change the width and height of a window.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = new height in screen units
+ *     zargs[2] = new width in screen units
+ *
+ */
+
+void z_window_size (void)
+{
+    zword win = winarg0 ();
+
+    flush_buffer ();
+
+    wp[win].y_size = zargs[1];
+    wp[win].x_size = zargs[2];
+
+    /* Keep the cursor within the window */
+
+    if (wp[win].y_cursor > zargs[1] || wp[win].x_cursor > zargs[2])
+       reset_cursor (win);
+
+}/* z_window_size */
+
+/*
+ * z_window_style, set / clear / toggle window attributes.
+ *
+ *     zargs[0] = window (-3 is the current one)
+ *     zargs[1] = window attribute flags
+ *     zargs[2] = operation to perform (optional, defaults to 0)
+ *
+ */
+
+void z_window_style (void)
+{
+    zword win = winarg0 ();
+    zword flags = zargs[1];
+
+    flush_buffer ();
+
+    /* Supply default arguments */
+
+    if (zargc < 3)
+       zargs[2] = 0;
+
+    /* Set window style */
+
+    switch (zargs[2]) {
+       case 0: wp[win].attribute = flags; break;
+       case 1: wp[win].attribute |= flags; break;
+       case 2: wp[win].attribute &= ~flags; break;
+       case 3: wp[win].attribute ^= flags; break;
+    }
+
+    if (cwin == win)
+       update_attributes ();
+
+}/* z_window_style */
diff --git a/sound.c b/sound.c
new file mode 100644 (file)
index 0000000..bfaf156
--- /dev/null
+++ b/sound.c
@@ -0,0 +1,161 @@
+/*
+ * sound.c
+ *
+ * Sound effect function
+ *
+ */
+
+#include "frotz.h"
+
+#define EFFECT_PREPARE 1
+#define EFFECT_PLAY 2
+#define EFFECT_STOP 3
+#define EFFECT_FINISH_WITH 4
+
+extern int direct_call (zword);
+
+static zword routine = 0;
+
+static next_sample = 0;
+static next_volume = 0;
+
+static bool locked = FALSE;
+static bool playing = FALSE;
+
+/*
+ * start_sample
+ *
+ * Call the IO interface to play a sample.
+ *
+ */
+
+static void start_sample (int number, int volume, int repeats, zword eos)
+{
+
+    static zbyte lh_repeats[] = {
+       0x00, 0x00, 0x00, 0x01, 0xff,
+       0x00, 0x01, 0x01, 0x01, 0x01,
+       0xff, 0x01, 0x01, 0xff, 0x00,
+       0xff, 0xff, 0xff, 0xff, 0xff
+    };
+
+    if (story_id == LURKING_HORROR)
+       repeats = lh_repeats[number];
+
+    os_start_sample (number, volume, repeats);
+
+    routine = eos;
+    playing = TRUE;
+
+}/* start_sample */
+
+/*
+ * start_next_sample
+ *
+ * Play a sample that has been delayed until the previous sound effect has
+ * finished.  This is necessary for two samples in The Lurking Horror that
+ * immediately follow other samples.
+ *
+ */
+
+static void start_next_sample (void)
+{
+
+    if (next_sample != 0)
+       start_sample (next_sample, next_volume, 0, 0);
+
+    next_sample = 0;
+    next_volume = 0;
+
+}/* start_next_sample */
+
+/*
+ * end_of_sound
+ *
+ * Call the Z-code routine which was given as the last parameter of
+ * a sound_effect call. This function may be called from a hardware
+ * interrupt (which requires extremely careful programming).
+ *
+ */
+
+void end_of_sound (void)
+{
+
+    playing = FALSE;
+
+    if (!locked) {
+
+       if (story_id == LURKING_HORROR)
+           start_next_sample ();
+
+       direct_call (routine);
+
+    }
+
+}/* end_of_sound */
+
+/*
+ * z_sound_effect, load / play / stop / discard a sound effect.
+ *
+ *     zargs[0] = number of bleep (1 or 2) or sample
+ *     zargs[1] = operation to perform (samples only)
+ *     zargs[2] = repeats and volume (play sample only)
+ *     zargs[3] = end-of-sound routine (play sample only, optional)
+ *
+ * Note: Volumes range from 1 to 8, volume 255 is the default volume.
+ *      Repeats are stored in the high byte, 255 is infinite loop.
+ *
+ */
+
+void z_sound_effect (void)
+{
+    zword number = zargs[0];
+    zword effect = zargs[1];
+    zword volume = zargs[2];
+
+    if (number >= 3) {
+
+       locked = TRUE;
+
+       if (story_id == LURKING_HORROR && (number == 9 || number == 16)) {
+
+           if (effect == EFFECT_PLAY) {
+
+               next_sample = number;
+               next_volume = volume;
+
+               locked = FALSE;
+
+               if (!playing)
+                   start_next_sample ();
+
+           } else locked = FALSE;
+
+           return;
+
+       }
+
+       playing = FALSE;
+
+       switch (effect) {
+
+       case EFFECT_PREPARE:
+           os_prepare_sample (number);
+           break;
+       case EFFECT_PLAY:
+           start_sample (number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0);
+           break;
+       case EFFECT_STOP:
+           os_stop_sample ();
+           break;
+       case EFFECT_FINISH_WITH:
+           os_finish_with_sample ();
+           break;
+
+       }
+
+       locked = FALSE;
+
+    } else os_beep (number);
+
+}/* z_sound_effect */
diff --git a/stream.c b/stream.c
new file mode 100644 (file)
index 0000000..c0c6e3b
--- /dev/null
+++ b/stream.c
@@ -0,0 +1,352 @@
+/*
+ * stream.c
+ *
+ * IO stream implementation
+ *
+ */
+
+#include "frotz.h"
+
+extern bool handle_hot_key (zchar);
+
+extern bool validate_click (void);
+
+extern void replay_open (void);
+extern void replay_close (void);
+extern void memory_open (zword, zword, bool);
+extern void memory_close (void);
+extern void record_open (void);
+extern void record_close (void);
+extern void script_open (void);
+extern void script_close (void);
+
+extern void memory_word (const zchar *);
+extern void memory_new_line (void);
+extern void record_write_key (zchar);
+extern void record_write_input (const zchar *, zchar);
+extern void script_char (zchar);
+extern void script_word (const zchar *);
+extern void script_new_line (void);
+extern void script_write_input (const zchar *, zchar);
+extern void script_erase_input (const zchar *);
+extern void script_mssg_on (void);
+extern void script_mssg_off (void);
+extern void screen_char (zchar);
+extern void screen_word (const zchar *);
+extern void screen_new_line (void);
+extern void screen_write_input (const zchar *, zchar);
+extern void screen_erase_input (const zchar *);
+extern void screen_mssg_on (void);
+extern void screen_mssg_off (void);
+
+extern zchar replay_read_key (void);
+extern zchar replay_read_input (zchar *);
+extern zchar console_read_key (zword);
+extern zchar console_read_input (int, zchar *, zword, bool);
+
+extern int direct_call (zword);
+
+/*
+ * stream_mssg_on
+ *
+ * Start printing a "debugging" message.
+ *
+ */
+
+void stream_mssg_on (void)
+{
+
+    flush_buffer ();
+
+    if (ostream_screen)
+       screen_mssg_on ();
+    if (ostream_script && enable_scripting)
+       script_mssg_on ();
+
+    message = TRUE;
+
+}/* stream_mssg_on */
+
+/*
+ * stream_mssg_off
+ *
+ * Stop printing a "debugging" message.
+ *
+ */
+
+void stream_mssg_off (void)
+{
+
+    flush_buffer ();
+
+    if (ostream_screen)
+       screen_mssg_off ();
+    if (ostream_script && enable_scripting)
+       script_mssg_off ();
+
+    message = FALSE;
+
+}/* stream_mssg_off */
+
+/*
+ * z_output_stream, open or close an output stream.
+ *
+ *     zargs[0] = stream to open (positive) or close (negative)
+ *     zargs[1] = address to redirect output to (stream 3 only)
+ *     zargs[2] = width of redirected output (stream 3 only, optional)
+ *
+ */
+
+void z_output_stream (void)
+{
+
+    flush_buffer ();
+
+    switch ((short) zargs[0]) {
+
+    case  1: ostream_screen = TRUE;
+            break;
+    case -1: ostream_screen = FALSE;
+            break;
+    case  2: if (!ostream_script) script_open ();
+            break;
+    case -2: if (ostream_script) script_close ();
+            break;
+    case  3: memory_open (zargs[1], zargs[2], zargc >= 3);
+            break;
+    case -3: memory_close ();
+            break;
+    case  4: if (!ostream_record) record_open ();
+            break;
+    case -4: if (ostream_record) record_close ();
+            break;
+
+    }
+
+}/* z_output_stream */
+
+/*
+ * stream_char
+ *
+ * Send a single character to the output stream.
+ *
+ */
+
+void stream_char (zchar c)
+{
+
+    if (ostream_screen)
+       screen_char (c);
+    if (ostream_script && enable_scripting)
+       script_char (c);
+
+}/* stream_char */
+
+/*
+ * stream_word
+ *
+ * Send a string of characters to the output streams.
+ *
+ */
+
+void stream_word (const zchar *s)
+{
+
+    if (ostream_memory && !message)
+
+       memory_word (s);
+
+    else {
+
+       if (ostream_screen)
+           screen_word (s);
+       if (ostream_script && enable_scripting)
+           script_word (s);
+
+    }
+
+}/* stream_word */
+
+/*
+ * stream_new_line
+ *
+ * Send a newline to the output streams.
+ *
+ */
+
+void stream_new_line (void)
+{
+
+    if (ostream_memory && !message)
+
+       memory_new_line ();
+
+    else {
+
+       if (ostream_screen)
+           screen_new_line ();
+       if (ostream_script && enable_scripting)
+           script_new_line ();
+
+    }
+
+}/* stream_new_line */
+
+/*
+ * z_input_stream, select an input stream.
+ *
+ *     zargs[0] = input stream to be selected
+ *
+ */
+
+void z_input_stream (void)
+{
+
+    flush_buffer ();
+
+    if (zargs[0] == 0 && istream_replay)
+       replay_close ();
+    if (zargs[0] == 1 && !istream_replay)
+       replay_open ();
+
+}/* z_input_stream */
+
+/*
+ * stream_read_key
+ *
+ * Read a single keystroke from the current input stream.
+ *
+ */
+
+zchar stream_read_key ( zword timeout, zword routine,
+                       bool hot_keys )
+{
+    zchar key = ZC_BAD;
+
+    flush_buffer ();
+
+    /* Read key from current input stream */
+
+continue_input:
+
+    do {
+
+       if (istream_replay)
+           key = replay_read_key ();
+       else
+           key = console_read_key (timeout);
+
+    } while (key == ZC_BAD);
+
+    /* Verify mouse clicks */
+
+    if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK)
+       if (!validate_click ())
+           goto continue_input;
+
+    /* Copy key to the command file */
+
+    if (ostream_record && !istream_replay)
+       record_write_key (key);
+
+    /* Handle timeouts */
+
+    if (key == ZC_TIME_OUT)
+       if (direct_call (routine) == 0)
+           goto continue_input;
+
+    /* Handle hot keys */
+
+    if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) {
+
+       if (h_version == V4 && key == ZC_HKEY_UNDO)
+           goto continue_input;
+       if (!handle_hot_key (key))
+           goto continue_input;
+
+       return ZC_BAD;
+
+    }
+
+    /* Return key */
+
+    return key;
+
+}/* stream_read_key */
+
+/*
+ * stream_read_input
+ *
+ * Read a line of input from the current input stream.
+ *
+ */
+
+zchar stream_read_input ( int max, zchar *buf,
+                         zword timeout, zword routine,
+                         bool hot_keys,
+                         bool no_scripting )
+{
+    zchar key = ZC_BAD;
+
+    flush_buffer ();
+
+    /* Remove initial input from the transscript file or from the screen */
+
+    if (ostream_script && enable_scripting && !no_scripting)
+       script_erase_input (buf);
+    if (istream_replay)
+       screen_erase_input (buf);
+
+    /* Read input line from current input stream */
+
+continue_input:
+
+    do {
+
+       if (istream_replay)
+           key = replay_read_input (buf);
+       else
+           key = console_read_input (max, buf, timeout, key != ZC_BAD);
+
+    } while (key == ZC_BAD);
+
+    /* Verify mouse clicks */
+
+    if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK)
+       if (!validate_click ())
+           goto continue_input;
+
+    /* Copy input line to the command file */
+
+    if (ostream_record && !istream_replay)
+       record_write_input (buf, key);
+
+    /* Handle timeouts */
+
+    if (key == ZC_TIME_OUT)
+       if (direct_call (routine) == 0)
+           goto continue_input;
+
+    /* Handle hot keys */
+
+    if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) {
+
+       if (!handle_hot_key (key))
+           goto continue_input;
+
+       return ZC_BAD;
+
+    }
+
+    /* Copy input line to transscript file or to the screen */
+
+    if (ostream_script && enable_scripting && !no_scripting)
+       script_write_input (buf, key);
+    if (istream_replay)
+       screen_write_input (buf, key);
+
+    /* Return terminating key */
+
+    return key;
+
+}/* stream_read_input */
diff --git a/table.c b/table.c
new file mode 100644 (file)
index 0000000..9999b67
--- /dev/null
+++ b/table.c
@@ -0,0 +1,180 @@
+/*
+ * table.c
+ *
+ * Table handling opcodes
+ *
+ */
+
+#include "frotz.h"
+
+/*
+ * z_copy_table, copy a table or fill it with zeroes.
+ *
+ *     zargs[0] = address of table
+ *     zargs[1] = destination address or 0 for fill
+ *     zargs[2] = size of table
+ *
+ * Note: Copying is safe even when source and destination overlap; but
+ *       if zargs[1] is negative the table _must_ be copied forwards.
+ *
+ */
+
+void z_copy_table (void)
+{
+    zword addr;
+    zword size = zargs[2];
+    zbyte value;
+    int i;
+
+    if (zargs[1] == 0)                                 /* zero table */
+
+       for (i = 0; i < size; i++)
+           storeb ((zword) (zargs[0] + i), 0);
+
+    else if ((short) size < 0 || zargs[0] > zargs[1])  /* copy forwards */
+
+       for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) {
+           addr = zargs[0] + i;
+           LOW_BYTE (addr, value)
+           storeb ((zword) (zargs[1] + i), value);
+       }
+
+    else                                               /* copy backwards */
+
+       for (i = size - 1; i >= 0; i--) {
+           addr = zargs[0] + i;
+           LOW_BYTE (addr, value)
+           storeb ((zword) (zargs[1] + i), value);
+       }
+
+}/* z_copy_table */
+
+/*
+ * z_loadb, store a value from a table of bytes.
+ *
+ *     zargs[0] = address of table
+ *     zargs[1] = index of table entry to store
+ *
+ */
+
+void z_loadb (void)
+{
+    zword addr = zargs[0] + zargs[1];
+    zbyte value;
+
+    LOW_BYTE (addr, value)
+
+    store (value);
+
+}/* z_loadb */
+
+/*
+ * z_loadw, store a value from a table of words.
+ *
+ *     zargs[0] = address of table
+ *     zargs[1] = index of table entry to store
+ *
+ */
+
+void z_loadw (void)
+{
+    zword addr = zargs[0] + 2 * zargs[1];
+    zword value;
+
+    LOW_WORD (addr, value)
+
+    store (value);
+
+}/* z_loadw */
+
+/*
+ * z_scan_table, find and store the address of a target within a table.
+ *
+ *     zargs[0] = target value to be searched for
+ *     zargs[1] = address of table
+ *     zargs[2] = number of table entries to check value against
+ *     zargs[3] = type of table (optional, defaults to 0x82)
+ *
+ * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise
+ *       it's a byte array. The lower bits hold the address step.
+ *
+ */
+
+void z_scan_table (void)
+{
+    zword addr = zargs[1];
+    int i;
+
+    /* Supply default arguments */
+
+    if (zargc < 4)
+       zargs[3] = 0x82;
+
+    /* Scan byte or word array */
+
+    for (i = 0; i < zargs[2]; i++) {
+
+       if (zargs[3] & 0x80) {  /* scan word array */
+
+           zword wvalue;
+
+           LOW_WORD (addr, wvalue)
+
+           if (wvalue == zargs[0])
+               goto finished;
+
+       } else {                /* scan byte array */
+
+           zbyte bvalue;
+
+           LOW_BYTE (addr, bvalue)
+
+           if (bvalue == zargs[0])
+               goto finished;
+
+       }
+
+       addr += zargs[3] & 0x7f;
+
+    }
+
+    addr = 0;
+
+finished:
+
+    store (addr);
+    branch (addr);
+
+}/* z_scan_table */
+
+/*
+ * z_storeb, write a byte into a table of bytes.
+ *
+ *     zargs[0] = address of table
+ *     zargs[1] = index of table entry
+ *     zargs[2] = value to be written
+ *
+ */
+
+void z_storeb (void)
+{
+
+    storeb ((zword) (zargs[0] + zargs[1]), zargs[2]);
+
+}/* z_storeb */
+
+/*
+ * z_storew, write a word into a table of words.
+ *
+ *     zargs[0] = address of table
+ *     zargs[1] = index of table entry
+ *     zargs[2] = value to be written
+ *
+ */
+
+void z_storew (void)
+{
+
+    storew ((zword) (zargs[0] + 2 * zargs[1]), zargs[2]);
+
+}/* z_storew */
diff --git a/text.c b/text.c
new file mode 100644 (file)
index 0000000..c8c3dbe
--- /dev/null
+++ b/text.c
@@ -0,0 +1,1080 @@
+/*
+ * text.c
+ *
+ * Text manipulation functions
+ *
+ */
+
+#include "frotz.h"
+
+enum string_type {
+    LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY
+};
+
+extern zword object_name (zword);
+
+static zchar decoded[10];
+static zword encoded[3];
+
+static zchar zscii_to_latin1[] = {
+    0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xab,
+    0xbb, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9,
+    0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3,
+    0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0,
+    0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4,
+    0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5,
+    0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5,
+    0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0,
+    0xa3, 0x00, 0x00, 0xa1, 0xbf
+};
+
+/*
+ * translate_from_zscii
+ *
+ * Map a ZSCII character onto the ISO Latin-1 alphabet.
+ *
+ */
+
+zchar translate_from_zscii (zbyte c)
+{
+
+    if (c == 0xfc)
+       return ZC_MENU_CLICK;
+    if (c == 0xfd)
+       return ZC_DOUBLE_CLICK;
+    if (c == 0xfe)
+       return ZC_SINGLE_CLICK;
+
+    if (c >= 0x9b && story_id != BEYOND_ZORK)
+
+       if (hx_unicode_table != 0) {    /* game has its own Unicode table */
+
+           zbyte N;
+
+           LOW_BYTE (hx_unicode_table, N)
+
+           if (c - 0x9b < N) {
+
+               zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b);
+               zword unicode;
+
+               LOW_WORD (addr, unicode)
+
+               return (unicode < 0x100) ? (zchar) unicode : '?';
+
+           } else return '?';
+
+       } else                          /* game uses standard set */
+
+           if (c <= 0xdf) {
+
+               if (c == 0xdc || c == 0xdd)     /* Oe and oe ligatures */
+                   return '?';                 /* are not ISO-Latin 1 */
+
+               return zscii_to_latin1[c - 0x9b];
+
+           } else return '?';
+
+    return c;
+
+}/* translate_from_zscii */
+
+/*
+ * translate_to_zscii
+ *
+ * Map an ISO Latin-1 character onto the ZSCII alphabet.
+ *
+ */
+
+zbyte translate_to_zscii (zchar c)
+{
+    int i;
+
+    if (c == ZC_SINGLE_CLICK)
+       return 0xfe;
+    if (c == ZC_DOUBLE_CLICK)
+       return 0xfd;
+    if (c == ZC_MENU_CLICK)
+       return 0xfc;
+
+    if (c >= ZC_LATIN1_MIN)
+
+       if (hx_unicode_table != 0) {    /* game has its own Unicode table */
+
+           zbyte N;
+           int i;
+
+           LOW_BYTE (hx_unicode_table, N)
+
+           for (i = 0x9b; i < 0x9b + N; i++) {
+
+               zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b);
+               zword unicode;
+
+               LOW_WORD (addr, unicode)
+
+               if (c == unicode)
+                   return (zbyte) i;
+
+           }
+
+           return '?';
+
+       } else {                        /* game uses standard set */
+
+           for (i = 0x9b; i <= 0xdf; i++)
+               if (c == zscii_to_latin1[i - 0x9b])
+                   return (zbyte) i;
+
+           return '?';
+
+       }
+
+    return c;
+
+}/* translate_to_zscii */
+
+/*
+ * alphabet
+ *
+ * Return a character from one of the three character sets.
+ *
+ */
+
+static zchar alphabet (int set, int index)
+{
+
+    if (h_alphabet != 0) {     /* game uses its own alphabet */
+
+       zbyte c;
+
+       zword addr = h_alphabet + 26 * set + index;
+       LOW_BYTE (addr, c)
+
+       return translate_from_zscii (c);
+
+    } else                     /* game uses default alphabet */
+
+       if (set == 0)
+           return 'a' + index;
+       else if (set == 1)
+           return 'A' + index;
+       else if (h_version == V1)
+           return " 0123456789.,!?_#'\"/\\<-:()"[index];
+       else
+           return " ^0123456789.,!?_#'\"/\\-:()"[index];
+
+}/* alphabet */
+
+/*
+ * load_string
+ *
+ * Copy a ZSCII string from the memory to the global "decoded" string.
+ *
+ */
+
+static void load_string (zword addr, zword length)
+{
+    int resolution = (h_version <= V3) ? 2 : 3;
+    int i = 0;
+
+    while (i < 3 * resolution)
+
+       if (i < length) {
+
+           zbyte c;
+
+           LOW_BYTE (addr, c)
+           addr++;
+
+           decoded[i++] = translate_from_zscii (c);
+
+       } else decoded[i++] = 0;
+
+}/* load_string */
+
+/*
+ * encode_text
+ *
+ * Encode the Unicode text in the global "decoded" string then write
+ * the result to the global "encoded" array. (This is used to look up
+ * words in the dictionary.) Up to V3 the vocabulary resolution is
+ * two, since V4 it is three words. Because each word contains three
+ * Z-characters, that makes six or nine Z-characters respectively.
+ * Longer words are chopped to the proper size, shorter words are are
+ * padded out with 5's. For word completion we pad with 0s and 31s,
+ * the minimum and maximum Z-characters.
+ *
+ */
+
+static void encode_text (int padding)
+{
+    static zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0 };
+    static zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0 };
+    static zchar wait[] = { 'w', 'a', 'i', 't', 0 };
+
+    zbyte zchars[12];
+    const zchar *ptr = decoded;
+    zchar c;
+    int resolution = (h_version <= V3) ? 2 : 3;
+    int i = 0;
+
+    /* Expand abbreviations that some old Infocom games lack */
+
+    if (option_expand_abbreviations)
+
+       if (padding == 0x05 && decoded[1] == 0)
+
+           switch (decoded[0]) {
+               case 'g': ptr = again; break;
+               case 'x': ptr = examine; break;
+               case 'z': ptr = wait; break;
+           }
+
+    /* Translate string to a sequence of Z-characters */
+
+    while (i < 3 * resolution)
+
+       if ((c = *ptr++) != 0) {
+
+           int index, set;
+           zbyte c2;
+
+           /* Search character in the alphabet */
+
+           for (set = 0; set < 3; set++)
+               for (index = 0; index < 26; index++)
+                   if (c == alphabet (set, index))
+                       goto letter_found;
+
+           /* Character not found, store its ZSCII value */
+
+           c2 = translate_to_zscii (c);
+
+           zchars[i++] = 5;
+           zchars[i++] = 6;
+           zchars[i++] = c2 >> 5;
+           zchars[i++] = c2 & 0x1f;
+
+           continue;
+
+       letter_found:
+
+           /* Character found, store its index */
+
+           if (set != 0)
+               zchars[i++] = ((h_version <= V2) ? 1 : 3) + set;
+
+           zchars[i++] = index + 6;
+
+       } else zchars[i++] = padding;
+
+    /* Three Z-characters make a 16bit word */
+
+    for (i = 0; i < resolution; i++)
+
+       encoded[i] =
+           (zchars[3 * i + 0] << 10) |
+           (zchars[3 * i + 1] << 5) |
+           (zchars[3 * i + 2]);
+
+    encoded[resolution - 1] |= 0x8000;
+
+}/* encode_text */
+
+/*
+ * z_check_unicode, test if a unicode character can be read and printed.
+ *
+ *     zargs[0] = Unicode
+ *
+ */
+
+void z_check_unicode (void)
+{
+    zword c = zargs[0];
+
+    if (c >= 0x20 && c <= 0x7e)
+       store (3);
+    else if (c == 0xa0)
+       store (1);
+    else if (c >= 0xa1 && c <= 0xff)
+       store (3);
+    else
+       store (0);
+
+}/* z_check_unicode */
+
+/*
+ * z_encode_text, encode a ZSCII string for use in a dictionary.
+ *
+ *     zargs[0] = address of text buffer
+ *     zargs[1] = length of ASCII string
+ *     zargs[2] = offset of ASCII string within the text buffer
+ *     zargs[3] = address to store encoded text in
+ *
+ * This is a V5+ opcode and therefore the dictionary resolution must be
+ * three 16bit words.
+ *
+ */
+
+void z_encode_text (void)
+{
+    int i;
+
+    load_string ((zword) (zargs[0] + zargs[2]), zargs[1]);
+
+    encode_text (0x05);
+
+    for (i = 0; i < 3; i++)
+       storew ((zword) (zargs[3] + 2 * i), encoded[i]);
+
+}/* z_encode_text */
+
+/*
+ * decode_text
+ *
+ * Convert encoded text to Unicode. The encoded text consists of 16bit
+ * words. Every word holds 3 Z-characters (5 bits each) plus a spare
+ * bit to mark the last word. The Z-characters translate to ZSCII by
+ * looking at the current current character set. Some select another
+ * character set, others refer to abbreviations.
+ *
+ * There are several different string types:
+ *
+ *    LOW_STRING - from the lower 64KB (byte address)
+ *    ABBREVIATION - from the abbreviations table (word address)
+ *    HIGH_STRING - from the end of the memory map (packed address)
+ *    EMBEDDED_STRING - from the instruction stream (at PC)
+ *    VOCABULARY - from the dictionary (byte address)
+ *
+ * The last type is only used for word completion.
+ *
+ */
+
+#define outchar(c)     if (st==VOCABULARY) *ptr++=c; else print_char(c)
+
+static void decode_text (enum string_type st, zword addr)
+{
+    zchar *ptr;
+    long byte_addr;
+    zchar c2;
+    zword code;
+    zbyte c, prev_c = 0;
+    int shift_state = 0;
+    int shift_lock = 0;
+    int status = 0;
+
+    /* Calculate the byte address if necessary */
+
+    if (st == ABBREVIATION)
+
+       byte_addr = (long) addr << 1;
+
+    else if (st == HIGH_STRING) {
+
+       if (h_version <= V3)
+           byte_addr = (long) addr << 1;
+       else if (h_version <= V5)
+           byte_addr = (long) addr << 2;
+       else if (h_version <= V7)
+           byte_addr = ((long) addr << 2) + ((long) h_strings_offset << 3);
+       else /* h_version == V8 */
+           byte_addr = (long) addr << 3;
+
+       if (byte_addr >= story_size)
+           runtime_error ("Print at illegal address");
+
+    }
+
+    /* Loop until a 16bit word has the highest bit set */
+
+    if (st == VOCABULARY)
+       ptr = decoded;
+
+    do {
+
+       int i;
+
+       /* Fetch the next 16bit word */
+
+       if (st == LOW_STRING || st == VOCABULARY) {
+           LOW_WORD (addr, code)
+           addr += 2;
+       } else if (st == HIGH_STRING || st == ABBREVIATION) {
+           HIGH_WORD (byte_addr, code)
+           byte_addr += 2;
+       } else
+           CODE_WORD (code)
+
+       /* Read its three Z-characters */
+
+       for (i = 10; i >= 0; i -= 5) {
+
+           zword abbr_addr;
+           zword ptr_addr;
+
+           c = (code >> i) & 0x1f;
+
+           switch (status) {
+
+           case 0:     /* normal operation */
+
+               if (shift_state == 2 && c == 6)
+                   status = 2;
+
+               else if (h_version == V1 && c == 1)
+                   new_line ();
+
+               else if (h_version >= V2 && shift_state == 2 && c == 7)
+                   new_line ();
+
+               else if (c >= 6)
+                   outchar (alphabet (shift_state, c - 6));
+
+               else if (c == 0)
+                   outchar (' ');
+
+               else if (h_version >= V2 && c == 1)
+                   status = 1;
+
+               else if (h_version >= V3 && c <= 3)
+                   status = 1;
+
+               else {
+
+                   shift_state = (shift_lock + (c & 1) + 1) % 3;
+
+                   if (h_version <= V2 && c >= 4)
+                       shift_lock = shift_state;
+
+                   break;
+
+               }
+
+               shift_state = shift_lock;
+
+               break;
+
+           case 1:     /* abbreviation */
+
+               ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c;
+
+               LOW_WORD (ptr_addr, abbr_addr)
+               decode_text (ABBREVIATION, abbr_addr);
+
+               status = 0;
+               break;
+
+           case 2:     /* ZSCII character - first part */
+
+               status = 3;
+               break;
+
+           case 3:     /* ZSCII character - second part */
+
+               c2 = translate_from_zscii ((prev_c << 5) | c);
+               outchar (c2);
+
+               status = 0;
+               break;
+
+           }
+
+           prev_c = c;
+
+       }
+
+    } while (!(code & 0x8000));
+
+    if (st == VOCABULARY)
+       *ptr = 0;
+
+}/* decode_text */
+
+#undef outchar
+
+/*
+ * z_new_line, print a new line.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_new_line (void)
+{
+
+    new_line ();
+
+}/* z_new_line */
+
+/*
+ * z_print, print a string embedded in the instruction stream.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_print (void)
+{
+
+    decode_text (EMBEDDED_STRING, 0);
+
+}/* z_print */
+
+/*
+ * z_print_addr, print a string from the lower 64KB.
+ *
+ *     zargs[0] = address of string to print
+ *
+ */
+
+void z_print_addr (void)
+{
+
+    decode_text (LOW_STRING, zargs[0]);
+
+}/* z_print_addr */
+
+/*
+ * z_print_char print a single ZSCII character.
+ *
+ *     zargs[0] = ZSCII character to be printed
+ *
+ */
+
+void z_print_char (void)
+{
+
+    print_char (translate_from_zscii (zargs[0]));
+
+}/* z_print_char */
+
+/*
+ * z_print_form, print a formatted table.
+ *
+ *     zargs[0] = address of formatted table to be printed
+ *
+ */
+
+void z_print_form (void)
+{
+    zword count;
+    zword addr = zargs[0];
+
+    bool first = TRUE;
+
+    for (;;) {
+
+       LOW_WORD (addr, count)
+       addr += 2;
+
+       if (count == 0)
+           break;
+
+       if (!first)
+           new_line ();
+
+       while (count--) {
+
+           zbyte c;
+
+           LOW_BYTE (addr, c)
+           addr++;
+
+           print_char (translate_from_zscii (c));
+
+       }
+
+       first = FALSE;
+
+    }
+
+}/* z_print_form */
+
+/*
+ * print_num
+ *
+ * Print a signed 16bit number.
+ *
+ */
+
+void print_num (zword value)
+{
+    int i;
+
+    /* Print sign */
+
+    if ((short) value < 0) {
+       print_char ('-');
+       value = - (short) value;
+    }
+
+    /* Print absolute value */
+
+    for (i = 10000; i != 0; i /= 10)
+       if (value >= i || i == 1)
+           print_char ('0' + (value / i) % 10);
+
+}/* print_num */
+
+/*
+ * z_print_num, print a signed number.
+ *
+ *     zargs[0] = number to print
+ *
+ */
+
+void z_print_num (void)
+{
+
+    print_num (zargs[0]);
+
+}/* z_print_num */
+
+/*
+ * print_object
+ *
+ * Print an object description.
+ *
+ */
+
+void print_object (zword object)
+{
+    zword addr = object_name (object);
+    zword code = 0x94a5;
+    zbyte length;
+
+    LOW_BYTE (addr, length)
+    addr++;
+
+    if (length != 0)
+       LOW_WORD (addr, code)
+
+    if (code == 0x94a5) {      /* encoded text 0x94a5 == empty string */
+
+       print_string ("object#");       /* supply a generic name */
+       print_num (object);             /* for anonymous objects */
+
+    } else decode_text (LOW_STRING, addr);
+
+}/* print_object */
+
+/*
+ * z_print_obj, print an object description.
+ *
+ *     zargs[0] = number of object to be printed
+ *
+ */
+
+void z_print_obj (void)
+{
+
+    print_object (zargs[0]);
+
+}/* z_print_obj */
+
+/*
+ * z_print_paddr, print the string at the given packed address.
+ *
+ *     zargs[0] = packed address of string to be printed
+ *
+ */
+
+void z_print_paddr (void)
+{
+
+    decode_text (HIGH_STRING, zargs[0]);
+
+}/* z_print_paddr */
+
+/*
+ * z_print_ret, print the string at PC, print newline then return true.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_print_ret (void)
+{
+
+    decode_text (EMBEDDED_STRING, 0);
+    new_line ();
+    ret (1);
+
+}/* z_print_ret */
+
+/*
+ * print_string
+ *
+ * Print a string of ASCII characters.
+ *
+ */
+
+void print_string (const char *s)
+{
+    char c;
+
+    while ((c = *s++) != 0)
+
+       if (c == '\n')
+           new_line ();
+       else
+           print_char (c);
+
+}/* print_string */
+
+/*
+ * z_print_unicode
+ *
+ *     zargs[0] = Unicode
+ *
+ */
+
+void z_print_unicode (void)
+{
+
+    print_char ((zargs[0] <= 0xff) ? zargs[0] : '?');
+
+}/* z_print_unicode */
+
+/*
+ * lookup_text
+ *
+ * Scan a dictionary searching for the given word. The first argument
+ * can be
+ *
+ * 0x00 - find the first word which is >= the given one
+ * 0x05 - find the word which exactly matches the given one
+ * 0x1f - find the last word which is <= the given one
+ *
+ * The return value is 0 if the search fails.
+ *
+ */
+
+static zword lookup_text (int padding, zword dct)
+{
+    zword entry_addr;
+    zword entry_count;
+    zword entry;
+    zword addr;
+    zbyte entry_len;
+    zbyte sep_count;
+    int resolution = (h_version <= V3) ? 2 : 3;
+    int entry_number;
+    int lower, upper;
+    int i;
+    bool sorted;
+
+    encode_text (padding);
+
+    LOW_BYTE (dct, sep_count)          /* skip word separators */
+    dct += 1 + sep_count;
+    LOW_BYTE (dct, entry_len)          /* get length of entries */
+    dct += 1;
+    LOW_WORD (dct, entry_count)                /* get number of entries */
+    dct += 2;
+
+    if ((short) entry_count < 0) {     /* bad luck, entries aren't sorted */
+
+       entry_count = - (short) entry_count;
+       sorted = FALSE;
+
+    } else sorted = TRUE;              /* entries are sorted */
+
+    lower = 0;
+    upper = entry_count - 1;
+
+    while (lower <= upper) {
+
+       if (sorted)                             /* binary search */
+           entry_number = (lower + upper) / 2;
+       else                                    /* linear search */
+           entry_number = lower;
+
+       entry_addr = dct + entry_number * entry_len;
+
+       /* Compare word to dictionary entry */
+
+       addr = entry_addr;
+
+       for (i = 0; i < resolution; i++) {
+           LOW_WORD (addr, entry)
+           if (encoded[i] != entry)
+               goto continuing;
+           addr += 2;
+       }
+
+       return entry_addr;              /* exact match found, return now */
+
+    continuing:
+
+       if (sorted)                             /* binary search */
+
+           if (encoded[i] > entry)
+               lower = entry_number + 1;
+           else
+               upper = entry_number - 1;
+
+       else lower++;                           /* linear search */
+
+    }
+
+    /* No exact match has been found */
+
+    if (padding == 0x05)
+       return 0;
+
+    entry_number = (padding == 0x00) ? lower : upper;
+
+    if (entry_number == -1 || entry_number == entry_count)
+       return 0;
+
+    return dct + entry_number * entry_len;
+
+}/* lookup_text */
+
+/*
+ * tokenise_text
+ *
+ * Translate a single word to a token and append it to the token
+ * buffer. Every token consists of the address of the dictionary
+ * entry, the length of the word and the offset of the word from
+ * the start of the text buffer. Unknown words cause empty slots
+ * if the flag is set (such that the text can be scanned several
+ * times with different dictionaries); otherwise they are zero.
+ *
+ */
+
+static void tokenise_text (zword text, zword length, zword from, zword parse, zword dct, bool flag)
+{
+    zword addr;
+    zbyte token_max, token_count;
+
+    LOW_BYTE (parse, token_max)
+    parse++;
+    LOW_BYTE (parse, token_count)
+
+    if (token_count < token_max) {     /* sufficient space left for token? */
+
+       storeb (parse++, token_count + 1);
+
+       load_string ((zword) (text + from), length);
+
+       addr = lookup_text (0x05, dct);
+
+       if (addr != 0 || !flag) {
+
+           parse += 4 * token_count;
+
+           storew ((zword) (parse + 0), addr);
+           storeb ((zword) (parse + 2), length);
+           storeb ((zword) (parse + 3), from);
+
+       }
+
+    }
+
+}/* tokenise_text */
+
+/*
+ * tokenise_line
+ *
+ * Split an input line into words and translate the words to tokens.
+ *
+ */
+
+void tokenise_line (zword text, zword token, zword dct, bool flag)
+{
+    zword addr1;
+    zword addr2;
+    zbyte length;
+    zbyte c;
+
+    /* Use standard dictionary if the given dictionary is zero */
+
+    if (dct == 0)
+       dct = h_dictionary;
+
+    /* Remove all tokens before inserting new ones */
+
+    storeb ((zword) (token + 1), 0);
+
+    /* Move the first pointer across the text buffer searching for the
+       beginning of a word. If this succeeds, store the position in a
+       second pointer. Move the first pointer searching for the end of
+       the word. When it is found, "tokenise" the word. Continue until
+       the end of the buffer is reached. */
+
+    addr1 = text;
+    addr2 = 0;
+
+    if (h_version >= V5) {
+       addr1++;
+       LOW_BYTE (addr1, length)
+    }
+
+    do {
+
+       zword sep_addr;
+       zbyte sep_count;
+       zbyte separator;
+
+       /* Fetch next ZSCII character */
+
+       addr1++;
+
+       if (h_version >= V5 && addr1 == text + 2 + length)
+           c = 0;
+       else
+           LOW_BYTE (addr1, c)
+
+       /* Check for separator */
+
+       sep_addr = dct;
+
+       LOW_BYTE (sep_addr, sep_count)
+       sep_addr++;
+
+       do {
+
+           LOW_BYTE (sep_addr, separator)
+           sep_addr++;
+
+       } while (c != separator && --sep_count != 0);
+
+       /* This could be the start or the end of a word */
+
+       if (sep_count == 0 && c != ' ' && c != 0) {
+
+           if (addr2 == 0)
+               addr2 = addr1;
+
+       } else if (addr2 != 0) {
+
+           tokenise_text (
+               text,
+               (zword) (addr1 - addr2),
+               (zword) (addr2 - text),
+               token, dct, flag );
+
+           addr2 = 0;
+
+       }
+
+       /* Translate separator (which is a word in its own right) */
+
+       if (sep_count != 0)
+
+           tokenise_text (
+               text,
+               (zword) (1),
+               (zword) (addr1 - text),
+               token, dct, flag );
+
+    } while (c != 0);
+
+}/* tokenise_line */
+
+/*
+ * z_tokenise, make a lexical analysis of a ZSCII string.
+ *
+ *     zargs[0] = address of string to analyze
+ *     zargs[1] = address of token buffer
+ *     zargs[2] = address of dictionary (optional)
+ *     zargs[3] = set when unknown words cause empty slots (optional)
+ *
+ */
+
+void z_tokenise (void)
+{
+
+    /* Supply default arguments */
+
+    if (zargc < 3)
+       zargs[2] = 0;
+    if (zargc < 4)
+       zargs[3] = 0;
+
+    /* Call tokenise_line to do the real work */
+
+    tokenise_line (zargs[0], zargs[1], zargs[2], zargs[3] != 0);
+
+}/* z_tokenise */
+
+/*
+ * completion
+ *
+ * Scan the vocabulary to complete the last word on the input line
+ * (similar to "tcsh" under Unix). The return value is
+ *
+ *    2 ==> completion is impossible
+ *    1 ==> completion is ambiguous
+ *    0 ==> completion is successful
+ *
+ * The function also returns a string in its second argument. In case
+ * of 2, the string is empty; in case of 1, the string is the longest
+ * extension of the last word on the input line that is common to all
+ * possible completions (for instance, if the last word on the input
+ * is "fo" and its only possible completions are "follow" and "folly"
+ * then the string is "ll"); in case of 0, the string is an extension
+ * to the last word that results in the only possible completion.
+ *
+ */
+
+int completion (const zchar *buffer, zchar *result)
+{
+    zword minaddr;
+    zword maxaddr;
+    zchar *ptr;
+    zchar c;
+    int len;
+    int i;
+
+    *result = 0;
+
+    /* Copy last word to "decoded" string */
+
+    len = 0;
+
+    while ((c = *buffer++) != 0)
+
+       if (c != ' ') {
+
+           if (len < 9)
+               decoded[len++] = c;
+
+       } else len = 0;
+
+    decoded[len] = 0;
+
+    /* Search the dictionary for first and last possible extensions */
+
+    minaddr = lookup_text (0x00, h_dictionary);
+    maxaddr = lookup_text (0x1f, h_dictionary);
+
+    if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr)
+       return 2;
+
+    /* Copy first extension to "result" string */
+
+    decode_text (VOCABULARY, minaddr);
+
+    ptr = result;
+
+    for (i = len; (c = decoded[i]) != 0; i++)
+       *ptr++ = c;
+    *ptr = 0;
+
+    /* Merge second extension with "result" string */
+
+    decode_text (VOCABULARY, maxaddr);
+
+    for (i = len, ptr = result; (c = decoded[i]) != 0; i++, ptr++)
+       if (*ptr != c) break;
+    *ptr = 0;
+
+    /* Search was ambiguous or successful */
+
+    return (minaddr == maxaddr) ? 0 : 1;
+
+}/* completion */
diff --git a/variable.c b/variable.c
new file mode 100644 (file)
index 0000000..86a663b
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * variable.c
+ *
+ * Variable and stack related opcodes
+ *
+ */
+
+#include "frotz.h"
+
+/*
+ * z_dec, decrement a variable.
+ *
+ *     zargs[0] = variable to decrement
+ *
+ */
+
+void z_dec (void)
+{
+    zword value;
+
+    if (zargs[0] == 0)
+       (*sp)--;
+    else if (zargs[0] < 16)
+       (*(fp - zargs[0]))--;
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       LOW_WORD (addr, value)
+       value--;
+       SET_WORD (addr, value)
+    }
+
+}/* z_dec */
+
+/*
+ * z_dec_chk, decrement a variable and branch if now less than value.
+ *
+ *     zargs[0] = variable to decrement
+ *     zargs[1] = value to check variable against
+ *
+ */
+
+void z_dec_chk (void)
+{
+    zword value;
+
+    if (zargs[0] == 0)
+       value = --(*sp);
+    else if (zargs[0] < 16)
+       value = --(*(fp - zargs[0]));
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       LOW_WORD (addr, value)
+       value--;
+       SET_WORD (addr, value)
+    }
+
+    branch ((short) value < (short) zargs[1]);
+
+}/* z_dec_chk */
+
+/*
+ * z_inc, increment a variable.
+ *
+ *     zargs[0] = variable to increment
+ *
+ */
+
+void z_inc (void)
+{
+    zword value;
+
+    if (zargs[0] == 0)
+       (*sp)++;
+    else if (zargs[0] < 16)
+       (*(fp - zargs[0]))++;
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       LOW_WORD (addr, value)
+       value++;
+       SET_WORD (addr, value)
+    }
+
+}/* z_inc */
+
+/*
+ * z_inc_chk, increment a variable and branch if now greater than value.
+ *
+ *     zargs[0] = variable to increment
+ *     zargs[1] = value to check variable against
+ *
+ */
+
+void z_inc_chk (void)
+{
+    zword value;
+
+    if (zargs[0] == 0)
+       value = ++(*sp);
+    else if (zargs[0] < 16)
+       value = ++(*(fp - zargs[0]));
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       LOW_WORD (addr, value)
+       value++;
+       SET_WORD (addr, value)
+    }
+
+    branch ((short) value > (short) zargs[1]);
+
+}/* z_inc_chk */
+
+/*
+ * z_load, store the value of a variable.
+ *
+ *     zargs[0] = variable to store
+ *
+ */
+
+void z_load (void)
+{
+    zword value;
+
+    if (zargs[0] == 0)
+       value = *sp;
+    else if (zargs[0] < 16)
+       value = *(fp - zargs[0]);
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       LOW_WORD (addr, value)
+    }
+
+    store (value);
+
+}/* z_load */
+
+/*
+ * z_pop, pop a value off the game stack and discard it.
+ *
+ *     no zargs used
+ *
+ */
+
+void z_pop (void)
+{
+
+    sp++;
+
+}/* z_pop */
+
+/*
+ * z_pop_stack, pop n values off the game or user stack and discard them.
+ *
+ *     zargs[0] = number of values to discard
+ *     zargs[1] = address of user stack (optional)
+ *
+ */
+
+void z_pop_stack (void)
+{
+
+    if (zargc == 2) {          /* it's a user stack */
+
+       zword size;
+       zword addr = zargs[1];
+
+       LOW_WORD (addr, size)
+
+       size += zargs[0];
+       storew (addr, size);
+
+    } else sp += zargs[0];     /* it's the game stack */
+
+}/* z_pop_stack */
+
+/*
+ * z_pull, pop a value off...
+ *
+ * a) ...the game or a user stack and store it (V6)
+ *
+ *     zargs[0] = address of user stack (optional)
+ *
+ * b) ...the game stack and write it to a variable (other than V6)
+ *
+ *     zargs[0] = variable to write value to
+ *
+ */
+
+void z_pull (void)
+{
+    zword value;
+
+    if (h_version != V6) {     /* not a V6 game, pop stack and write */
+
+       value = *sp++;
+
+       if (zargs[0] == 0)
+           *sp = value;
+       else if (zargs[0] < 16)
+           *(fp - zargs[0]) = value;
+       else {
+           zword addr = h_globals + 2 * (zargs[0] - 16);
+           SET_WORD (addr, value)
+       }
+
+    } else {                   /* it's V6, but is there a user stack? */
+
+       if (zargc == 1) {       /* it's a user stack */
+
+           zword size;
+           zword addr = zargs[0];
+
+           LOW_WORD (addr, size)
+
+           size++;
+           storew (addr, size);
+
+           addr += 2 * size;
+           LOW_WORD (addr, value)
+
+       } else value = *sp++;   /* it's the game stack */
+
+       store (value);
+
+    }
+
+}/* z_pull */
+
+/*
+ * z_push, push a value onto the game stack.
+ *
+ *     zargs[0] = value to push onto the stack
+ *
+ */
+
+void z_push (void)
+{
+
+    *--sp = zargs[0];
+
+}/* z_push */
+
+/*
+ * z_push_stack, push a value onto a user stack then branch if successful.
+ *
+ *     zargs[0] = value to push onto the stack
+ *     zargs[1] = address of user stack
+ *
+ */
+
+void z_push_stack (void)
+{
+    zword size;
+    zword addr = zargs[1];
+
+    LOW_WORD (addr, size)
+
+    if (size != 0) {
+
+       storew ((zword) (addr + 2 * size), zargs[0]);
+
+       size--;
+       storew (addr, size);
+
+    }
+
+    branch (size);
+
+}/* z_push_stack */
+
+/*
+ * z_store, write a value to a variable.
+ *
+ *     zargs[0] = variable to be written to
+ *      zargs[1] = value to write
+ *
+ */
+
+void z_store (void)
+{
+    zword value = zargs[1];
+
+    if (zargs[0] == 0)
+       *sp = value;
+    else if (zargs[0] < 16)
+       *(fp - zargs[0]) = value;
+    else {
+       zword addr = h_globals + 2 * (zargs[0] - 16);
+       SET_WORD (addr, value)
+    }
+
+}/* z_store */