David Griffith <[email protected]>

This patch applies to UnixFrotz232R2Std10.tar.gz as found in the IF
archive and unifies patches put out by Timo Korvola, Torbjorn Anderson,
and Martin Frost.  I have fixed a couple problems with Timo's patch and
made the Makefile a bit more flexible.  Below are listed the impact of
this patch according to the authors.

-------------------------
What will this patch do?
-------------------------
The patch by Timo Korvola <[email protected]> http://www.iki.fi/tkorvola
modifies the I/O routines.  Notable changes include:
* The terminal erase character, as set with stty, now functions
 as backspace.  On many terminals this character is DEL.
* Background colours should now work properly.
* timeout() from curses is now used for timed input instead of a busy loop.
* ^L and ^R now redraw the screen.  They are not passed as input to the
 Z-machine.
* ISO-Latin-1 characters are now valid input unless in plain
 ASCII mode.  This makes the meta bit unusable for hotkeys.  On
 terminals with 8-bit character sets alt usually sends ESC
 anyway.
* History search has been added.  Type the beginning of a past
 command and hit the up or down arrow key.
In addition there are lots of cosmetic changes.


The z-strict patch by Torbjorn Anderson <email unknown> permits the
interpreter to detect, and optionally report, invalid attempts to access
object 0.  (This often happens with old versions of the Inform library,
and usually has no ill effects but occasionally causes games to crash).
See comments in the patched version of frotz.h.  If you only play games,
you may wish to define STRICTZ_DEFAULT_REPORT_MODE in frotz.h to
STRICTZ_REPORT_NEVER so that the interpreter won't issue annoying error
messages when playing games (but will handle the error safely.)  If you
write games, it may be a good idea to increase STRICTZ_DEFAULT_REPORT_MODE
to STRICTZ_REPORT_ALWAYS for the purposes of testing.


The QUETZAL patch by Martin Frost <[email protected]> enables the use of
the portable QUETZAL save file format, which is now becoming the standard.
Note that if you compile with this option, you won't be able to read old
non-QUETZAL save files.


My changes include adding "install", "uninstall", and "clean" targets to
the Makefile and assorted bugfixes.  Those fixes include the following:

In ux_input.c in the function os_read_line(), a call to refresh() was
removed by Timo's patch.  That removal caused Frotz to not echo spaces
until a nonspace character was typed.

Portability fixes, including some weird curses problems.

Fixed a bug introduced by Timo's patch in ux_input.c

David Picton <[email protected]> alerted me to a couple potential
problems.  One is the fact that Timo's patch to allow Latin-1 characters
for input interferes with meta-key handling on Solaris systems.  Frotz's
'-p' option will disable Latin-1 input and allow meta to function
normally.  The other problem involves Frotz dying when encountering fatal
errors.  Frotz can be made to ignore such errors by using the '-i' option.
I would prefer to fix problems like this by adding config-file
functionality to Frotz, but this will require some heavy overhauling.  If
you want to change the default behavior of Frotz on for these two things,
you can set compile-time defines IGNORE_ERR or NOLATIN1 in the Makefile
before compiling.


-------------------
Applying the patch
-------------------

I advise Solaris users to install the GNU version of patch.
(available from several sources such as ftp://metalab.unc.edu,
ftp://prep.ai.mit.edu, ftp://ftp.gnu.org, http://www.sunhelp.org and so
on)

Put the patch file in the Frotz source directory and then type:

patch -p1 < davepatch-frotz

Assuming no other patches have been applied, this should work.  If you see
any failed "hunks", apply them by hand.


-----------------------------------
Compilation and portability issues
-----------------------------------

The Unix port of Frotz was written mainly with Linux in mind.
Fortunately, the source is rather generic C code and "porting" was little
more than changing semantics a bit and adding some casts to make the
compiler shut up.  I have tested and verified that Frotz with my
superpatch installed will compile and run with no problem on Linux on x86,
NetBSD on sparc, Digital Unix on Alpha, Solaris on Sparc and UltraSparc,
and Irix on SGI-MIPS.  Frotz should work fine with Linux and *BSD on any
hardware they run on.

Compiling should be as simple as checking the Makefile for defines to
match your machine.  Type "make" and if all goes well, type "make install"
and the frotz binary and manpage will be installed.  You may need to
become root to write to /usr/local/bin and /usr/local/man.


LINUX:  The Unix port of Frotz was written primarily with Linux in mind.
You shouldn't have any trouble here.

[Net|Open|Free]BSD:  These are confirmed to work just fine, however I
recommend using ncurses.  No problems seem to exist across architectures.

DIGITAL UNIX:  The standard curses library seems to work fine.  The same
is likely with Tru64Unix.  Color is okay.

IRIX:  Frotz doesn't seem to like Irix's curses library. Ncurses older
than version 5.0 cause assorted problems with various programs, including
Frotz.  As far as I can tell, you must install ncurses 5.0 to run Frotz.
Get the source and compile it yourself.  Don't use Irix's broken packaging
system.

SOLARIS:  Some versions Solaris curses have problems with color support.
At least the one in Solaris 2.6 works okay.  If compiled with -O2 on an
UltraSPARC using gcc 2.8.1, you may get lots of wierd segfaults.  The
problem seems UltraSPARC related and it's not clear if this problem
crosses flavor boundaries (ie, if UltraLinux or NetBSD has this problem)
or has anything to do with gcc.  Version 2.8.x of gcc had lots of strange
problems so I'm inclined to blame that.  I don't have sufficient access to
test this theory, so if you are able to enlighten me on this, please do
so.

OTHER UNICIES:  Getting Frotz to compile and work properly seems to focus
entirely on curses and the proper one of four defines listed in the
Makefile.  A bit of trial and error should get Frotz running.  Install
ncurses just to be safe.

NCURSES:  You can get ncurses via FTP at ftp://ftp.gnu.org/gnu/ncurses/ or
the nearest convenient mirror site.  Mirrors are listed at
ftp://ftp.gnu.org/gnu/GNUinfo/FTP or
http://www.gnu.org/server/list-mirror.html

---PATCH BEGINS HERE---

diff -c -N frotz-2.32/Makefile frotz-2.32-d/Makefile
*** frotz-2.32/Makefile Fri Oct 17 22:11:10 1997
--- frotz-2.32-d/Makefile       Sat Dec 18 03:24:31 1999
***************
*** 6,17 ****
 # Define your optimization flags.  Most compilers understand -O and -O2,
 # Debugging (don't use)
 #OPTS = -Wall -g
! # Standard
 OPTS = -O2
 # Pentium with gcc 2.7.0 or better
 #OPTS = -O2 -fomit-frame-pointer -malign-functions=2 -malign-loops=2 \
 -malign-jumps=2

 # There are a few defines which may be necessary to make frotz compile on
 # your system.  Most of these are fairly straightforward.
 #    -DNO_MEMMOVE:    Use this if your not-so-standard c library lacks the
--- 6,21 ----
 # Define your optimization flags.  Most compilers understand -O and -O2,
 # Debugging (don't use)
 #OPTS = -Wall -g
! # Standard (note: Solaris on UltraSparc using gcc 2.8.x might not like this.)
 OPTS = -O2
 # Pentium with gcc 2.7.0 or better
 #OPTS = -O2 -fomit-frame-pointer -malign-functions=2 -malign-loops=2 \
 -malign-jumps=2

+ # Define where you want frotz to be installed.  Usually this is /usr/local
+ PREFIX = /usr/local
+
+
 # There are a few defines which may be necessary to make frotz compile on
 # your system.  Most of these are fairly straightforward.
 #    -DNO_MEMMOVE:    Use this if your not-so-standard c library lacks the
***************
*** 19,30 ****
 #
 # You may need to define one of these to get the getopt prototypes:
 #    -DUSE_UNISTD_H:  Use this if your getopt prototypes are in unistd.h.
! #                     (Solaris?)
 #    -DUSE_GETOPT_H:  Use this if you have a separate /usr/include/getopt.h
! #                     file containing the getopt prototypes. (Linux, IRIX 5?)
 #    -DUSE_NOTHING:   I've heard reports of some systems defining getopt in
 #                     stdlib.h, which unix.c automatically includes.
! #                     (*BSD, Solaris?)
 #
 # If none of the above are defined, frotz will define the getopt prototypes
 # itself.
--- 23,35 ----
 #
 # You may need to define one of these to get the getopt prototypes:
 #    -DUSE_UNISTD_H:  Use this if your getopt prototypes are in unistd.h.
! #                     (Solaris)
 #    -DUSE_GETOPT_H:  Use this if you have a separate /usr/include/getopt.h
! #                     file containing the getopt prototypes.
! #                     (Linux, IRIX, Digital Unix)
 #    -DUSE_NOTHING:   I've heard reports of some systems defining getopt in
 #                     stdlib.h, which unix.c automatically includes.
! #                     (Older *BSD?, older Solaris?)
 #
 # If none of the above are defined, frotz will define the getopt prototypes
 # itself.
***************
*** 33,73 ****
 #                     than curses.h.
 #
 # These defines add various cosmetic features to the interpreter:
 #    -DCOLOR_SUPPORT: If the terminal you're using has color support, frotz
 #                     will use the curses color routines...if your curses
 #                     library supports color.
 #    -DEMACS_EDITING: This enables some of the standard emacs editing keys
 #                     (Ctrl-B,-F,-P,-N, etc.) to be used when entering
 #                     input.  I can't see any reason why you wouldn't want
 #                     to define it--it can't hurt anything--but you do
 #                     have that option.
 #
- #DEFS = -DUSE_GETOPT_H -DCOLOR_SUPPORT -DEMACS_EDITING
- DEFS =

 # This should point to the location of your curses or ncurses include file
 # if it's in a non-standard place.
 #INCL = -I/usr/local/include
 #INCL = -I/5usr/include
- INCL =

 # This should define the location and name of whatever curses library you're
! # linking with.
 #LIB = -L/usr/local/lib
! #CURSES = -lncurses
 #LIB = -L/5usr/lib
! LIB =
! CURSES = -lcurses

 # Nothing under this line should need to be changed.

 OBJECTS = buffer.o fastmem.o files.o hotkey.o input.o main.o math.o object.o \
!           process.o random.o redirect.o screen.o sound.o stream.o table.o \
!           text.o ux_init.o ux_input.o ux_pic.o ux_screen.o ux_sample.o \
!           ux_text.o variable.o

! CFLAGS = $(OPTS) $(DEFS) $(INCL)

! frotz: $(OBJECTS)
!       $(CC) -o frotz $(OBJECTS) $(LIB) $(CURSES)

--- 38,157 ----
 #                     than curses.h.
 #
 # These defines add various cosmetic features to the interpreter:
+ #    -DSTRICTZ:       Do stricter error checking. Adapted from Andrew
+ #                   Plotkin's zip_zstrict_patch.
 #    -DCOLOR_SUPPORT: If the terminal you're using has color support, frotz
 #                     will use the curses color routines...if your curses
 #                     library supports color.
+ #    -DUSE_QUETZAL:   Use the interpreter-independent QUETZAL format for save
+ #                     files.
 #    -DEMACS_EDITING: This enables some of the standard emacs editing keys
 #                     (Ctrl-B,-F,-P,-N, etc.) to be used when entering
 #                     input.  I can't see any reason why you wouldn't want
 #                     to define it--it can't hurt anything--but you do
 #                     have that option.
+ #    -DIGNORE_ERR:    Default to ignore runtime errors.  This is the same
+ #                     as starting Frotz with the "-i" option.
+ #    -DNOLATIN1:      Disable Latin-1 input.  Some machines will balk at
+ #                     having their meta key disabled by allowing Latin-1
+ #                     input.  If you don't use Latin-1 characters,
+ #                     defining this won't hurt.
+ #
+
+
+ ##################################################
+ # CHECK THE DEFINES BELOW TO MATCH YOUR MACHINE!
 #

+
+ # Use this one for Linux and Irix (install ncurses).
+ OS_DEFS = -DUSE_GETOPT_H -DUSE_NCURSES_H
+
+ # Use this one for *BSD and Solaris
+ #OS_DEFS = -DUSE_UNISTD_H
+
+ # Use this one for Digital Unix and Tru64 Unix.  Use the Linux one if you
+ # have ncurses.
+ #OS_DEFS = -DUSE_GETOPT_H
+
+ # Uncomment this if you want color support.
+ COLOR_DEFS = -DCOLOR_SUPPORT
+
+ # These shouldn't cause any portability problems.  See README.davepatch
+ # for more info.
+ OPT_DEFS = -DSTRICTZ -DUSE_QUETZAL -DEMACS_EDITING
+
+ # Don't crash on otherwise fatal errors.  A lot of games tested on
+ # ZIP-derived interpreters can cause crashes.  This forces the '-i' option
+ # to always be used.
+ FIX_DEFS1 = -DIGNORE_ERR
+
+ # Disable use of LATIN-1 characters.  This is here because LATIN-1 usage
+ # can reportedly mess with Sun consoles.  This forces the '-p' option to
+ # always be used.
+ #FIX_DEFS2 = -DNOLATIN1
+
 # This should point to the location of your curses or ncurses include file
 # if it's in a non-standard place.
 #INCL = -I/usr/local/include
+ #INCL = -I/usr/pkg/include
+ #INCL = -I/usr/freeware/include
 #INCL = -I/5usr/include

 # This should define the location and name of whatever curses library you're
! # linking with.  Usually, this isn't necessary if /etc/ld.so.conf is set
! # up correctly.
 #LIB = -L/usr/local/lib
! #LIB = -L/usr/pkg/lib
! #LIB = -L/usr/freeware/lib
 #LIB = -L/5usr/lib
!
! # One of these must be defined, use ncurses if you have it.
! #CURSES = -lcurses
! CURSES = -lncurses    # Linux always uses ncurses.

+
+ ####################################################
 # Nothing under this line should need to be changed.

+
 OBJECTS = buffer.o fastmem.o files.o hotkey.o input.o main.o math.o object.o \
!           process.o quetzal.o random.o redirect.o screen.o sound.o stream.o \
!           strictz.o table.o text.o ux_init.o ux_input.o ux_pic.o ux_screen.o \
!           ux_sample.o ux_text.o variable.o
!
!
! COMP_DEFS = $(OS_DEFS) $(OPT_DEFS) $(FIX_DEFS1) $(FIX_DEFS2) $(COLOR_DEFS)
!
! CFLAGS = $(OPTS) $(COMP_DEFS) $(INCL)
!
! BINNAME = frotz
!
! DISTNAME = $(BINNAME)-2.32-d
!
! $(BINNAME): $(OBJECTS)
!       $(CC) -o $(BINNAME) $(OBJECTS) $(LIB) $(CURSES)
!
! install: $(BINNAME)
!       install -m 755 $(BINNAME) $(PREFIX)/bin
!       install -m 644 $(BINNAME).6 $(PREFIX)/man/man6
!
! uninstall:
!       rm -f $(PREFIX)/bin/$(BINNAME)
!       rm -f $(PREFIX)/man/man6/$(BINNAME).6

! clean:
!       rm -f *.o

! realclean: clean
!       rm -f $(BINNAME)

+ help:
+       @echo
+       @echo "Targets:"
+       @echo "    frotz"
+       @echo "    install"
+       @echo "    uninstall"
+       @echo "    clean"
+       @echo "    realclean"
+       @echo
diff -c -N frotz-2.32/README.davepatch frotz-2.32-d/README.davepatch
*** frotz-2.32/README.davepatch
--- frotz-2.32-d/README.davepatch       Sat Dec 18 03:51:21 1999
***************
*** 0 ****
--- 1,141 ----
+ David Griffith <[email protected]>
+
+ This patch applies to UnixFrotz232R2Std10.tar.gz as found in the IF
+ archive and unifies patches put out by Timo Korvola, Torbjorn Anderson,
+ and Martin Frost.  I have fixed a couple problems with Timo's patch and
+ made the Makefile a bit more flexible.  Below are listed the impact of
+ this patch according to the authors.
+
+ -------------------------
+ What will this patch do?
+ -------------------------
+ The patch by Timo Korvola <[email protected]> http://www.iki.fi/tkorvola
+ modifies the I/O routines.  Notable changes include:
+ * The terminal erase character, as set with stty, now functions
+   as backspace.  On many terminals this character is DEL.
+ * Background colours should now work properly.
+ * timeout() from curses is now used for timed input instead of a busy loop.
+ * ^L and ^R now redraw the screen.  They are not passed as input to the
+   Z-machine.
+ * ISO-Latin-1 characters are now valid input unless in plain
+   ASCII mode.  This makes the meta bit unusable for hotkeys.  On
+   terminals with 8-bit character sets alt usually sends ESC
+   anyway.
+ * History search has been added.  Type the beginning of a past
+   command and hit the up or down arrow key.
+ In addition there are lots of cosmetic changes.
+
+
+ The z-strict patch by Torbjorn Anderson <email unknown> permits the
+ interpreter to detect, and optionally report, invalid attempts to access
+ object 0.  (This often happens with old versions of the Inform library,
+ and usually has no ill effects but occasionally causes games to crash).
+ See comments in the patched version of frotz.h.  If you only play games,
+ you may wish to define STRICTZ_DEFAULT_REPORT_MODE in frotz.h to
+ STRICTZ_REPORT_NEVER so that the interpreter won't issue annoying error
+ messages when playing games (but will handle the error safely.)  If you
+ write games, it may be a good idea to increase STRICTZ_DEFAULT_REPORT_MODE
+ to STRICTZ_REPORT_ALWAYS for the purposes of testing.
+
+
+ The QUETZAL patch by Martin Frost <[email protected]> enables the use of
+ the portable QUETZAL save file format, which is now becoming the standard.
+ Note that if you compile with this option, you won't be able to read old
+ non-QUETZAL save files.
+
+
+ My changes include adding "install", "uninstall", and "clean" targets to
+ the Makefile and assorted bugfixes.  Those fixes include the following:
+
+ In ux_input.c in the function os_read_line(), a call to refresh() was
+ removed by Timo's patch.  That removal caused Frotz to not echo spaces
+ until a nonspace character was typed.
+
+ Portability fixes, including some weird curses problems.
+
+ Fixed a bug introduced by Timo's patch in ux_input.c
+
+ David Picton <[email protected]> alerted me to a couple potential
+ problems.  One is the fact that Timo's patch to allow Latin-1 characters
+ for input interferes with meta-key handling on Solaris systems.  Frotz's
+ '-p' option will disable Latin-1 input and allow meta to function
+ normally.  The other problem involves Frotz dying when encountering fatal
+ errors.  Frotz can be made to ignore such errors by using the '-i' option.
+ I would prefer to fix problems like this by adding config-file
+ functionality to Frotz, but this will require some heavy overhauling.  If
+ you want to change the default behavior of Frotz on for these two things,
+ you can set compile-time defines IGNORE_ERR or NOLATIN1 in the Makefile
+ before compiling.
+
+
+ -------------------
+ Applying the patch
+ -------------------
+
+ I advise Solaris users to install the GNU version of patch.
+ (available from several sources such as ftp://metalab.unc.edu,
+ ftp://prep.ai.mit.edu, ftp://ftp.gnu.org, http://www.sunhelp.org and so
+ on)
+
+ Put the patch file in the Frotz source directory and then type:
+
+ patch -p1 < davepatch-frotz
+
+ Assuming no other patches have been applied, this should work.  If you see
+ any failed "hunks", apply them by hand.
+
+
+ -----------------------------------
+ Compilation and portability issues
+ -----------------------------------
+
+ The Unix port of Frotz was written mainly with Linux in mind.
+ Fortunately, the source is rather generic C code and "porting" was little
+ more than changing semantics a bit and adding some casts to make the
+ compiler shut up.  I have tested and verified that Frotz with my
+ superpatch installed will compile and run with no problem on Linux on x86,
+ NetBSD on sparc, Digital Unix on Alpha, Solaris on Sparc and UltraSparc,
+ and Irix on SGI-MIPS.  Frotz should work fine with Linux and *BSD on any
+ hardware they run on.
+
+ Compiling should be as simple as checking the Makefile for defines to
+ match your machine.  Type "make" and if all goes well, type "make install"
+ and the frotz binary and manpage will be installed.  You may need to
+ become root to write to /usr/local/bin and /usr/local/man.
+
+
+ LINUX:  The Unix port of Frotz was written primarily with Linux in mind.
+ You shouldn't have any trouble here.
+
+ [Net|Open|Free]BSD:  These are confirmed to work just fine, however I
+ recommend using ncurses.  No problems seem to exist across architectures.
+
+ DIGITAL UNIX:  The standard curses library seems to work fine.  The same
+ is likely with Tru64Unix.  Color is okay.
+
+ IRIX:  Frotz doesn't seem to like Irix's curses library. Ncurses older
+ than version 5.0 cause assorted problems with various programs, including
+ Frotz.  As far as I can tell, you must install ncurses 5.0 to run Frotz.
+ Get the source and compile it yourself.  Don't use Irix's broken packaging
+ system.
+
+ SOLARIS:  Some versions Solaris curses have problems with color support.
+ At least the one in Solaris 2.6 works okay.  If compiled with -O2 on an
+ UltraSPARC using gcc 2.8.1, you may get lots of wierd segfaults.  The
+ problem seems UltraSPARC related and it's not clear if this problem
+ crosses flavor boundaries (ie, if UltraLinux or NetBSD has this problem)
+ or has anything to do with gcc.  Version 2.8.x of gcc had lots of strange
+ problems so I'm inclined to blame that.  I don't have sufficient access to
+ test this theory, so if you are able to enlighten me on this, please do
+ so.
+
+ OTHER UNICIES:  Getting Frotz to compile and work properly seems to focus
+ entirely on curses and the proper one of four defines listed in the
+ Makefile.  A bit of trial and error should get Frotz running.  Install
+ ncurses just to be safe.
+
+ NCURSES:  You can get ncurses via FTP at ftp://ftp.gnu.org/gnu/ncurses/ or
+ the nearest convenient mirror site.  Mirrors are listed at
+ ftp://ftp.gnu.org/gnu/GNUinfo/FTP or
+ http://www.gnu.org/server/list-mirror.html
+
diff -c -N frotz-2.32/TODO.davepatch frotz-2.32-d/TODO.davepatch
*** frotz-2.32/TODO.davepatch
--- frotz-2.32-d/TODO.davepatch Sat Dec 18 03:40:40 1999
***************
*** 0 ****
--- 1,22 ----
+ David Griffith  December 17, 1999
+ Things to do for Unix-Frotz
+
+
+ Clean up the Makefile or, more preferably, convert it to autoconf.
+
+ Clean up the getopt code.
+
+ Implement some sort of config file for defining where story files will be
+ found.
+
+ Clean up error-handling code and have it configurable from a config file.
+
+ Implement a save scheme such that save are stored something like this:
+ $HOME/.if/saves/gamename/
+ A list of saves or menu will appear upon issuing the "RESTORE" command.
+ Just how this works is determined from the yet unimplemented config file.
+
+ Contact Galen Hazelwood and see if he wishes to maintain Unix-Frotz
+ anymore.  If not, get any unfinished Frotz code from him to include later.
+
+ Convert this superpatch thing into a regular tarballed distribution.
diff -c -N frotz-2.32/fastmem.c frotz-2.32-d/fastmem.c
*** frotz-2.32/fastmem.c        Fri Oct  3 22:04:57 1997
--- frotz-2.32-d/fastmem.c      Thu Nov 25 12:39:13 1999
***************
*** 40,45 ****
--- 40,50 ----
 extern void script_open (void);
 extern void script_close (void);

+ extern zword save_quetzal (FILE *, FILE *);
+ extern zword restore_quetzal (FILE *, FILE *);
+
+ extern void erase_window (zword);
+
 extern void (*op0_opcodes[]) (void);
 extern void (*op1_opcodes[]) (void);
 extern void (*op2_opcodes[]) (void);
***************
*** 483,488 ****
--- 488,494 ----
     restart_screen ();

     sp = fp = stack + STACK_SIZE;
+     frame_count = 0;

     if (h_version != V6) {

***************
*** 597,602 ****
--- 603,632 ----
       if ((gfp = fopen (new_name, "rb")) == NULL)
           goto finished;

+ #ifdef USE_QUETZAL
+
+       if ((short) (success = restore_quetzal (gfp, story_fp)) > 0) {
+
+           /* In V3, reset the upper window. */
+           if (h_version == V3)
+               split_window (0);
+
+           /* Reload cached header fields. */
+           restart_header ();
+
+           /*
+            * Since QUETZAL files may be saved on many different machines,
+            * the screen sizes may vary a lot. Erasing the status window
+            * seems to cover up most of the resulting badness.
+            */
+           if (h_version > V3 && h_version != V6)
+               erase_window (1);
+
+       } else if ((short) success < 0)
+           os_fatal ("Error reading save file");
+
+ #else
+
       /* Load game file */

       release = (unsigned) fgetc (gfp) << 8;
***************
*** 655,660 ****
--- 685,692 ----

       } else print_string ("Invalid save file\n");

+ #endif        /* !USE_QUETZAL */
+
       /* Close game file */

       fclose (gfp);
***************
*** 701,706 ****
--- 733,739 ----
       pc = ((long) stack[0] << 16) | stack[1];
       sp = stack + stack[2];
       fp = stack + stack[3];
+       frame_count = stack[4];

       SET_PC (pc)

***************
*** 790,795 ****
--- 823,834 ----
       if ((gfp = fopen (new_name, "wb")) == NULL)
           goto finished;

+ #ifdef USE_QUETZAL
+
+       success = save_quetzal (gfp, story_fp);
+
+ #else
+
       /* Write game file */

       fputc ((int) hi (h_release), gfp);
***************
*** 825,830 ****
--- 864,871 ----
               skip = 0;
           } else skip++;

+ #endif        /* !USE_QUETZAL */
+
       /* Close game file and check for errors */

       if (fclose (gfp) == EOF || ferror (story_fp)) {
***************
*** 858,864 ****
 {
     long pc;

!     if (undo_slots == 0)      /* undo feature unavailable */

       return -1;

--- 899,905 ----
 {
     long pc;

!     if (undo_slots == 0 || sp - stack < 5)    /* undo feature unavailable */

       return -1;

***************
*** 873,878 ****
--- 914,920 ----
       stack[1] = (zword) (pc & 0xffff);
       stack[2] = (zword) (sp - stack);
       stack[3] = (zword) (fp - stack);
+       stack[4] = frame_count;

       memcpy (undo[undo_count], stack, sizeof (stack));
       memcpy (undo[undo_count] + sizeof (stack), zmp, h_dynamic_size);
diff -c -N frotz-2.32/frotz.h frotz-2.32-d/frotz.h
*** frotz-2.32/frotz.h  Fri Oct  3 22:04:52 1997
--- frotz-2.32-d/frotz.h        Thu Nov 25 12:39:13 1999
***************
*** 396,401 ****
--- 396,402 ----
 extern zword stack[STACK_SIZE];
 extern zword *sp;
 extern zword *fp;
+ extern zword frame_count;

 extern zword zargs[8];
 extern zargc;
***************
*** 545,550 ****
--- 546,597 ----
 void  z_verify (void);
 void  z_window_size (void);
 void  z_window_style (void);
+
+ #ifdef STRICTZ
+
+ /* Definitions for STRICTZ functions and error codes. */
+
+ extern int strictz_report_mode;
+
+ void  init_strictz (void);
+ void  report_strictz_error (int, const char *);
+
+ /* Error codes */
+ #define STRZERR_NO_ERROR (0)
+ #define STRZERR_JIN (1)
+ #define STRZERR_GET_CHILD (2)
+ #define STRZERR_GET_PARENT (3)
+ #define STRZERR_GET_SIBLING (4)
+ #define STRZERR_GET_PROP_ADDR (5)
+ #define STRZERR_GET_PROP (6)
+ #define STRZERR_PUT_PROP (7)
+ #define STRZERR_CLEAR_ATTR (8)
+ #define STRZERR_SET_ATTR (9)
+ #define STRZERR_TEST_ATTR (10)
+ #define STRZERR_MOVE_OBJECT (11)
+ #define STRZERR_MOVE_OBJECT_2 (12)
+ #define STRZERR_REMOVE_OBJECT (13)
+ #define STRZERR_GET_NEXT_PROP (14)
+ #define STRICTZ_NUM_ERRORS (15)
+
+ /* There are four error reporting modes: never report errors;
+   report only the first time a given error type occurs; report
+   every time an error occurs; or treat all errors as fatal
+   errors, killing the interpreter. I strongly recommend
+   "report once" as the default. But you can compile in a
+   different default by changing the definition of
+   STRICTZ_DEFAULT_REPORT_MODE. In any case, the player can
+   specify a report mode on the command line by typing "-Z 0"
+   through "-Z 3". */
+
+ #define STRICTZ_REPORT_NEVER (0)
+ #define STRICTZ_REPORT_ONCE (1)
+ #define STRICTZ_REPORT_ALWAYS (2)
+ #define STRICTZ_REPORT_FATAL (3)
+
+ #define STRICTZ_DEFAULT_REPORT_MODE STRICTZ_REPORT_ONCE
+
+ #endif /* STRICTZ */

 /*** Various global functions ***/

diff -c -N frotz-2.32/main.c frotz-2.32-d/main.c
*** frotz-2.32/main.c   Wed Sep 10 15:07:58 1997
--- frotz-2.32-d/main.c Thu Nov 25 12:39:13 1999
***************
*** 77,82 ****
--- 77,83 ----
 zword stack[STACK_SIZE];
 zword *sp = 0;
 zword *fp = 0;
+ zword frame_count = 0;

 /* IO streams */

***************
*** 159,164 ****
--- 160,169 ----

 int cdecl main (int argc, char *argv[])
 {
+
+ #ifdef STRICTZ
+     init_strictz ();
+ #endif

     os_process_arguments (argc, argv);

diff -c -N frotz-2.32/object.c frotz-2.32-d/object.c
*** frotz-2.32/object.c Thu Sep 18 14:44:44 1997
--- frotz-2.32-d/object.c       Thu Nov 25 12:39:13 1999
***************
*** 149,154 ****
--- 149,162 ----
     zword parent_addr;
     zword sibling_addr;

+ #ifdef STRICTZ
+     if (object == 0) {
+       report_strictz_error (STRZERR_REMOVE_OBJECT,
+           "@remove_object called with object 0");
+       return;
+     }
+ #endif
+
     obj_addr = object_address (object);

     if (h_version <= V3) {
***************
*** 264,269 ****
--- 272,285 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_CLEAR_ATTR,
+           "@clear_attr called with object 0");
+       return;
+     }
+ #endif
+
     /* Get attribute address */

     obj_addr = object_address (zargs[0]) + zargs[1] / 8;
***************
*** 299,304 ****
--- 315,329 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_JIN,
+           "@jin called with object 0");
+       branch (0 == zargs[1]);
+       return;
+     }
+ #endif
+
     obj_addr = object_address (zargs[0]);

     if (h_version <= V3) {
***************
*** 351,356 ****
--- 376,391 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_CHILD,
+           "@get_child called with object 0");
+       store (0);
+       branch (FALSE);
+       return;
+     }
+ #endif
+
     obj_addr = object_address (zargs[0]);

     if (h_version <= V3) {
***************
*** 399,404 ****
--- 434,448 ----
     zbyte value;
     zbyte mask;

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_NEXT_PROP,
+           "@get_next_prop called with object 0");
+       store (0);
+       return;
+     }
+ #endif
+
     /* Property id is in bottom five (six) bits */

     mask = (h_version <= V3) ? 0x1f : 0x3f;
***************
*** 450,455 ****
--- 494,508 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_PARENT,
+           "@get_parent called with object 0");
+       store (0);
+       return;
+     }
+ #endif
+
     obj_addr = object_address (zargs[0]);

     if (h_version <= V3) {
***************
*** 498,503 ****
--- 551,565 ----
     zbyte value;
     zbyte mask;

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_PROP,
+           "@get_prop called with object 0");
+       store (0);
+       return;
+     }
+ #endif
+
     /* Property id is in bottom five (six) bits */

     mask = (h_version <= V3) ? 0x1f : 0x3f;
***************
*** 557,562 ****
--- 619,633 ----
     zbyte value;
     zbyte mask;

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_PROP_ADDR,
+           "@get_prop_addr called with object 0");
+       store (0);
+       return;
+     }
+ #endif
+
     if (story_id == BEYOND_ZORK)
       if (zargs[0] > MAX_OBJECT)
           { store (0); return; }
***************
*** 638,643 ****
--- 709,724 ----
 {
     zword obj_addr;

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_GET_SIBLING,
+           "@get_sibling called with object 0");
+       store (0);
+       branch (FALSE);
+       return;
+     }
+ #endif
+
     obj_addr = object_address (zargs[0]);

     if (h_version <= V3) {
***************
*** 698,703 ****
--- 779,798 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (obj1 == 0) {
+       report_strictz_error (STRZERR_MOVE_OBJECT,
+           "@move_object called moving object 0");
+       return;
+     }
+
+     if (obj2 == 0) {
+       report_strictz_error (STRZERR_MOVE_OBJECT_2,
+           "@move_object called moving into object 0");
+       return;
+     }
+ #endif
+
     /* Get addresses of both objects */

     obj1_addr = object_address (obj1);
***************
*** 752,757 ****
--- 847,860 ----
     zword value;
     zbyte mask;

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_PUT_PROP,
+           "@put_prop called with object 0");
+       return;
+     }
+ #endif
+
     /* Property id is in bottom five or six bits */

     mask = (h_version <= V3) ? 0x1f : 0x3f;
***************
*** 844,849 ****
--- 947,960 ----
       stream_mssg_off ();
     }

+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_SET_ATTR,
+           "@set_attr called with object 0");
+       return;
+     }
+ #endif
+
     /* Get attribute address */

     obj_addr = object_address (zargs[0]) + zargs[1] / 8;
***************
*** 888,893 ****
--- 999,1013 ----
       print_num (zargs[1]);
       stream_mssg_off ();
     }
+
+ #ifdef STRICTZ
+     if (zargs[0] == 0) {
+       report_strictz_error (STRZERR_TEST_ATTR,
+           "@test_attr called with object 0");
+       branch (FALSE);
+       return;
+     }
+ #endif

     /* Get attribute address */

diff -c -N frotz-2.32/process.c frotz-2.32-d/process.c
*** frotz-2.32/process.c        Thu Sep 18 14:37:14 1997
--- frotz-2.32-d/process.c      Thu Nov 25 12:39:13 1999
***************
*** 298,307 ****

     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;

--- 298,307 ----

     GET_PC (pc)

!     *--sp = (zword) (pc >> 9);                /* The stack format is no longer   */
!     *--sp = (zword) (pc & 0x1ff);     /* entirely Zip-compatible, due to */
!     *--sp = (zword) (fp - stack - 1); /* the extra information that must */
!     *--sp = (zword) (argc | (ct << 12));/* be stored for Quetzal. Sorry.   */

     fp = sp;

***************
*** 330,335 ****
--- 330,337 ----
     if (sp - stack < count)
       runtime_error ("Stack overflow");

+     fp[0] |= (zword) count << 8;      /* Save local var count for Quetzal. */
+
     value = 0;

     for (i = 0; i < count; i++) {
***************
*** 368,374 ****

     sp = fp;

!     ct = *sp++ >> 8;
     fp = stack + 1 + *sp++;
     pc = *sp++;
     pc = ((long) *sp++ << 9) | pc;
--- 370,376 ----

     sp = fp;

!     ct = *sp++ >> 12;
     fp = stack + 1 + *sp++;
     pc = *sp++;
     pc = ((long) *sp++ << 9) | pc;
***************
*** 560,566 ****
 void z_catch (void)
 {

!     store ((zword) (fp - stack));

 }/* z_catch */

--- 562,568 ----
 void z_catch (void)
 {

!     store (frame_count);

 }/* z_catch */

***************
*** 575,584 ****
 void z_throw (void)
 {

!     if (zargs[1] > STACK_SIZE)
       runtime_error ("Bad stack frame");

!     fp = stack + zargs[1];

     ret (zargs[0]);

--- 577,588 ----
 void z_throw (void)
 {

!     if (zargs[1] > frame_count)
       runtime_error ("Bad stack frame");

!     /* Unwind the stack a frame at a time. */
!     for (; frame_count > zargs[1]; --frame_count)
!       fp = stack + 1 + fp[1];

     ret (zargs[0]);

diff -c -N frotz-2.32/quetzal.c frotz-2.32-d/quetzal.c
*** frotz-2.32/quetzal.c
--- frotz-2.32-d/quetzal.c      Wed Aug  4 13:25:29 1999
***************
*** 0 ****
--- 1,551 ----
+ /* quetzal.c
+  *
+  * Saving and restoring of Quetzal files.
+  *
+  */
+
+ #ifdef USE_QUETZAL    /* don't compile anything otherwise */
+
+ #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
+
+ #define get_c fgetc
+ #define put_c fputc
+
+ typedef unsigned long zlong;
+
+ /*
+  * This is used only by save_quetzal. It probably should be allocated
+  * dynamically rather than statically.
+  */
+
+ static zword frames[STACK_SIZE/4+1];
+
+ /*
+  * ID types.
+  */
+
+ #define makeid(a,b,c,d) ((zlong) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)))
+
+ #define ID_FORM makeid ('F','O','R','M')
+ #define ID_IFZS makeid ('I','F','Z','S')
+ #define ID_IFhd makeid ('I','F','h','d')
+ #define ID_UMem makeid ('U','M','e','m')
+ #define ID_CMem makeid ('C','M','e','m')
+ #define ID_Stks makeid ('S','t','k','s')
+ #define ID_ANNO makeid ('A','N','N','O')
+
+ /*
+  * Various parsing states within restoration.
+  */
+
+ #define GOT_HEADER    0x01
+ #define GOT_STACK     0x02
+ #define GOT_MEMORY    0x04
+ #define GOT_NONE      0x00
+ #define GOT_ALL               0x07
+ #define GOT_ERROR     0x80
+
+ /*
+  * Macros used to write the files.
+  */
+
+ #define write_byte(fp,b) (put_c (b, fp) != EOF)
+ #define write_bytx(fp,b) write_byte (fp, (b) & 0xFF)
+ #define write_word(fp,w) \
+     (write_bytx (fp, (w) >>  8) && write_bytx (fp, (w)))
+ #define write_long(fp,l) \
+     (write_bytx (fp, (l) >> 24) && write_bytx (fp, (l) >> 16) && \
+      write_bytx (fp, (l) >>  8) && write_bytx (fp, (l)))
+ #define write_chnk(fp,id,len) \
+     (write_long (fp, (id))      && write_long (fp, (len)))
+ #define write_run(fp,run) \
+     (write_byte (fp, 0)         && write_byte (fp, (run)))
+
+ /* Read one word from file; return TRUE if OK. */
+ static bool read_word (FILE *f, zword *result)
+     {
+     int a, b;
+
+     if ((a = get_c (f)) == EOF) return FALSE;
+     if ((b = get_c (f)) == EOF) return FALSE;
+
+     *result = ((zword) a << 8) | (zword) b;
+     return TRUE;
+     }
+
+ /* Read one long from file; return TRUE if OK. */
+ static bool read_long (FILE *f, zlong *result)
+     {
+     int a, b, c, d;
+
+     if ((a = get_c (f)) == EOF) return FALSE;
+     if ((b = get_c (f)) == EOF) return FALSE;
+     if ((c = get_c (f)) == EOF) return FALSE;
+     if ((d = get_c (f)) == EOF) return FALSE;
+
+     *result = ((zlong) a << 24) | ((zlong) b << 16) |
+             ((zlong) c <<  8) |  (zlong) d;
+     return TRUE;
+     }
+
+ /*
+  * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error
+  * occurred before any damage was done, -1 on a fatal error.
+  */
+
+ zword restore_quetzal (FILE *svf, FILE *stf)
+     {
+     zlong ifzslen, currlen, tmpl;
+     zlong pc;
+     zword i, tmpw;
+     zword fatal = 0;  /* Set to -1 when errors must be fatal. */
+     zbyte skip, progress = GOT_NONE;
+     int x, y;
+
+     /* Check it's really an `IFZS' file. */
+     if (!read_long (svf, &tmpl)
+       || !read_long (svf, &ifzslen)
+       || !read_long (svf, &currlen))                          return 0;
+     if (tmpl != ID_FORM || currlen != ID_IFZS)
+       {
+       print_string ("This is not a saved game file!\n");
+       return 0;
+       }
+     if ((ifzslen & 1) || ifzslen<4) /* Sanity checks. */      return 0;
+     ifzslen -= 4;
+
+     /* Read each chunk and process it. */
+     while (ifzslen > 0)
+       {
+       /* Read chunk header. */
+       if (ifzslen < 8) /* Couldn't contain a chunk. */        return 0;
+       if (!read_long (svf, &tmpl)
+           || !read_long (svf, &currlen))                      return 0;
+       ifzslen -= 8;   /* Reduce remaining by size of header. */
+
+       /* Handle chunk body. */
+       if (ifzslen < currlen) /* Chunk goes past EOF?! */      return 0;
+       skip = currlen & 1;
+       ifzslen -= currlen + (zlong) skip;
+
+       switch (tmpl)
+           {
+           /* `IFhd' header chunk; must be first in file. */
+           case ID_IFhd:
+               if (progress & GOT_HEADER)
+                   {
+                   print_string ("Save file has two IFZS chunks!\n");
+                   return fatal;
+                   }
+               progress |= GOT_HEADER;
+               if (currlen < 13
+                   || !read_word (svf, &tmpw))                 return fatal;
+               if (tmpw != h_release)
+                   progress = GOT_ERROR;
+
+               for (i=H_SERIAL; i<H_SERIAL+6; ++i)
+                   {
+                   if ((x = get_c (svf)) == EOF)               return fatal;
+                   if (x != zmp[i])
+                       progress = GOT_ERROR;
+                   }
+
+               if (!read_word (svf, &tmpw))                    return fatal;
+               if (tmpw != h_checksum)
+                   progress = GOT_ERROR;
+
+               if (progress & GOT_ERROR)
+                   {
+                   print_string ("File was not saved from this story!\n");
+                   return fatal;
+                   }
+               if ((x = get_c (svf)) == EOF)                   return fatal;
+               pc = (zlong) x << 16;
+               if ((x = get_c (svf)) == EOF)                   return fatal;
+               pc |= (zlong) x << 8;
+               if ((x = get_c (svf)) == EOF)                   return fatal;
+               pc |= (zlong) x;
+               fatal = -1;     /* Setting PC means errors must be fatal. */
+               SET_PC (pc);
+
+               for (i=13; i<currlen; ++i)
+                   (void) get_c (svf); /* Skip rest of chunk. */
+               break;
+           /* `Stks' stacks chunk; restoring this is quite complex. ;) */
+           case ID_Stks:
+               if (progress & GOT_STACK)
+                   {
+                   print_string ("File contains two stack chunks!\n");
+                   break;
+                   }
+               progress |= GOT_STACK;
+
+               fatal = -1;     /* Setting SP means errors must be fatal. */
+               sp = stack + STACK_SIZE;
+
+               /*
+                * All versions other than V6 may use evaluation stack outside
+                * any function context. As a result a faked function context
+                * will be present in the file here. We skip this context, but
+                * load the associated stack onto the stack proper...
+                */
+               if (h_version != V6)
+                   {
+                   if (currlen < 8)                            return fatal;
+                   for (i=0; i<6; ++i)
+                       if (get_c (svf) != 0)                   return fatal;
+                   if (!read_word (svf, &tmpw))                return fatal;
+                   if (tmpw > STACK_SIZE)
+                       {
+                       print_string ("Save-file has too much stack (and I can't cope).\n");
+                       return fatal;
+                       }
+                   currlen -= 8;
+                   if (currlen < tmpw*2)                       return fatal;
+                   for (i=0; i<tmpw; ++i)
+                       if (!read_word (svf, --sp))             return fatal;
+                   currlen -= tmpw*2;
+                   }
+
+               /* We now proceed to load the main block of stack frames. */
+               for (fp = stack+STACK_SIZE, frame_count = 0;
+                    currlen > 0;
+                    currlen -= 8, ++frame_count)
+                   {
+                   if (currlen < 8)                            return fatal;
+                   if (sp - stack < 4) /* No space for frame. */
+                       {
+                       print_string ("Save-file has too much stack (and I can't cope).\n");
+                       return fatal;
+                       }
+
+                   /* Read PC, procedure flag and formal param count. */
+                   if (!read_long (svf, &tmpl))                return fatal;
+                   y = (int) (tmpl & 0x0F);    /* Number of formals. */
+                   tmpw = y << 8;
+
+                   /* Read result variable. */
+                   if ((x = get_c (svf)) == EOF)               return fatal;
+
+                   /* Check the procedure flag... */
+                   if (tmpl & 0x10)
+                       {
+                       tmpw |= 0x1000; /* It's a procedure. */
+                       tmpl >>= 8;     /* Shift to get PC value. */
+                       }
+                   else
+                       {
+                       /* Functions have type 0, so no need to or anything. */
+                       tmpl >>= 8;     /* Shift to get PC value. */
+                       --tmpl;         /* Point at result byte. */
+                       /* Sanity check on result variable... */
+                       if (zmp[tmpl] != (zbyte) x)
+                           {
+                           print_string ("Save-file has wrong variable number on stack (possibly wrong game version?)\n");
+                           return fatal;
+                           }
+                       }
+                   *--sp = (zword) (tmpl >> 9);        /* High part of PC */
+                   *--sp = (zword) (tmpl & 0x1FF);     /* Low part of PC */
+                   *--sp = (zword) (fp - stack - 1);   /* FP */
+
+                   /* Read and process argument mask. */
+                   if ((x = get_c (svf)) == EOF)               return fatal;
+                   ++x;        /* Should now be a power of 2 */
+                   for (i=0; i<8; ++i)
+                       if (x & (1<<i))
+                           break;
+                   if (x ^ (1<<i))     /* Not a power of 2 */
+                       {
+                       print_string ("Save-file uses incomplete argument lists (which I can't handle)\n");
+                       return fatal;
+                       }
+                   *--sp = tmpw | i;
+                   fp = sp;    /* FP for next frame. */
+
+                   /* Read amount of eval stack used. */
+                   if (!read_word (svf, &tmpw))                return fatal;
+
+                   tmpw += y;  /* Amount of stack + number of locals. */
+                   if (sp - stack <= tmpw)
+                       {
+                       print_string ("Save-file has too much stack (and I can't cope).\n");
+                       return fatal;
+                       }
+                   if (currlen < tmpw*2)                       return fatal;
+                   for (i=0; i<tmpw; ++i)
+                       if (!read_word (svf, --sp))             return fatal;
+                   currlen -= tmpw*2;
+                   }
+               /* End of `Stks' processing... */
+               break;
+           /* Any more special chunk types must go in HERE or ABOVE. */
+           /* `CMem' compressed memory chunk; uncompress it. */
+           case ID_CMem:
+               if (!(progress & GOT_MEMORY))   /* Don't complain if two. */
+                   {
+                   (void) fseek (stf, 0, SEEK_SET);
+                   i=0;        /* Bytes written to data area. */
+                   for (; currlen > 0; --currlen)
+                       {
+                       if ((x = get_c (svf)) == EOF)           return fatal;
+                       if (x == 0)     /* Start run. */
+                           {
+                           /* Check for bogus run. */
+                           if (currlen < 2)
+                               {
+                               print_string ("File contains bogus `CMem' chunk.\n");
+                               for (; currlen > 0; --currlen)
+                                   (void) get_c (svf); /* Skip rest. */
+                               currlen = 1;
+                               i = 0xFFFF;
+                               break; /* Keep going; may be a `UMem' too. */
+                               }
+                           /* Copy story file to memory during the run. */
+                           --currlen;
+                           if ((x = get_c (svf)) == EOF)       return fatal;
+                           for (; x >= 0 && i<h_dynamic_size; --x, ++i)
+                               if ((y = get_c (stf)) == EOF)   return fatal;
+                               else
+                                   zmp[i] = (zbyte) y;
+                           }
+                       else    /* Not a run. */
+                           {
+                           if ((y = get_c (stf)) == EOF)       return fatal;
+                           zmp[i] = (zbyte) (x ^ y);
+                           ++i;
+                           }
+                       /* Make sure we don't load too much. */
+                       if (i > h_dynamic_size)
+                           {
+                           print_string ("warning: `CMem' chunk too long!\n");
+                           for (; currlen > 1; --currlen)
+                               (void) get_c (svf);     /* Skip rest. */
+                           break;      /* Keep going; there may be a `UMem' too. */
+                           }
+                       }
+                   /* If chunk is short, assume a run. */
+                   for (; i<h_dynamic_size; ++i)
+                       if ((y = get_c (stf)) == EOF)           return fatal;
+                       else
+                           zmp[i] = (zbyte) y;
+                   if (currlen == 0)
+                       progress |= GOT_MEMORY; /* Only if succeeded. */
+                   break;
+               }
+               /* Fall right thru (to default) if already GOT_MEMORY */
+           /* `UMem' uncompressed memory chunk; load it. */
+           case ID_UMem:
+               if (!(progress & GOT_MEMORY))   /* Don't complain if two. */
+                   {
+                   /* Must be exactly the right size. */
+                   if (currlen == h_dynamic_size)
+                       {
+                       if (fread (zmp, currlen, 1, svf) == 1)
+                           {
+                           progress |= GOT_MEMORY;     /* Only on success. */
+                           break;
+                           }
+                       }
+                   else
+                       print_string ("`UMem' chunk wrong size!\n");
+                   /* Fall into default action (skip chunk) on errors. */
+                   }
+               /* Fall thru (to default) if already GOT_MEMORY */
+           /* Unrecognised chunk type; skip it. */
+           default:
+               (void) fseek (svf, currlen, SEEK_CUR);  /* Skip chunk. */
+               break;
+           }
+       if (skip)
+           (void) get_c (svf); /* Skip pad byte. */
+       }
+
+     /*
+      * We've reached the end of the file. For the restoration to have been a
+      * success, we must have had one of each of the required chunks.
+      */
+     if (!(progress & GOT_HEADER))
+       print_string ("error: no valid header (`IFhd') chunk in file.\n");
+     if (!(progress & GOT_STACK))
+       print_string ("error: no valid stack (`Stks') chunk in file.\n");
+     if (!(progress & GOT_MEMORY))
+       print_string ("error: no valid memory (`CMem' or `UMem') chunk in file.\n");
+
+     return (progress == GOT_ALL ? 2 : fatal);
+     }
+
+ /*
+  * Save a game using Quetzal format. Return 1 if OK, 0 if failed.
+  */
+
+ zword save_quetzal (FILE *svf, FILE *stf)
+     {
+     zlong ifzslen = 0, cmemlen = 0, stkslen = 0;
+     zlong pc;
+     zword i, j, n;
+     zword nvars, nargs, nstk, *p;
+     zbyte var;
+     long cmempos, stkspos;
+     int c;
+
+     /* Write `IFZS' header. */
+     if (!write_chnk (svf, ID_FORM, 0))                        return 0;
+     if (!write_long (svf, ID_IFZS))                   return 0;
+
+     /* Write `IFhd' chunk. */
+     GET_PC (pc);
+     if (!write_chnk (svf, ID_IFhd, 13))                       return 0;
+     if (!write_word (svf, h_release))                 return 0;
+     for (i=H_SERIAL; i<H_SERIAL+6; ++i)
+       if (!write_byte (svf, zmp[i]))                  return 0;
+     if (!write_word (svf, h_checksum))                        return 0;
+     if (!write_long (svf, pc << 8)) /* Includes pad. */       return 0;
+
+     /* Write `CMem' chunk. */
+     if ((cmempos = ftell (svf)) < 0)                  return 0;
+     if (!write_chnk (svf, ID_CMem, 0))                        return 0;
+     (void) fseek (stf, 0, SEEK_SET);
+     /* j holds current run length. */
+     for (i=0, j=0, cmemlen=0; i < h_dynamic_size; ++i)
+       {
+       if ((c = get_c (stf)) == EOF)                   return 0;
+       c ^= (int) zmp[i];
+       if (c == 0)
+           ++j;        /* It's a run of equal bytes. */
+       else
+           {
+           /* Write out any run there may be. */
+           if (j > 0)
+               {
+               for (; j > 0x100; j -= 0x100)
+                   {
+                   if (!write_run (svf, 0xFF))         return 0;
+                   cmemlen += 2;
+                   }
+               if (!write_run (svf, j-1))              return 0;
+               cmemlen += 2;
+               j = 0;
+               }
+           /* Any runs are now written. Write this (nonzero) byte. */
+           if (!write_byte (svf, (zbyte) c))           return 0;
+           ++cmemlen;
+           }
+       }
+     /*
+      * Reached end of dynamic memory. We ignore any unwritten run there may be
+      * at this point.
+      */
+     if (cmemlen & 1)  /* Chunk length must be even. */
+       if (!write_byte (svf, 0))                       return 0;
+
+     /* Write `Stks' chunk. You are not expected to understand this. ;) */
+     if ((stkspos = ftell (svf)) < 0)                  return 0;
+     if (!write_chnk (svf, ID_Stks, 0))                        return 0;
+
+     /*
+      * We construct a list of frame indices, most recent first, in `frames'.
+      * These indices are the offsets into the `stack' array of the word before
+      * the first word pushed in each frame.
+      */
+     frames[0] = sp - stack;   /* The frame we'd get by doing a call now. */
+     for (i = fp - stack + 4, n=0; i < STACK_SIZE+4; i = stack[i-3] + 5)
+       frames[++n] = i;
+
+     /*
+      * All versions other than V6 can use evaluation stack outside a function
+      * context. We write a faked stack frame (most fields zero) to cater for
+      * this.
+      */
+     if (h_version != V6)
+       {
+       for (i=0; i<6; ++i)
+           if (!write_byte (svf, 0))                   return 0;
+       nstk = STACK_SIZE - frames[n];
+       if (!write_word (svf, nstk))                    return 0;
+       for (j=STACK_SIZE-1; j >= frames[n]; --j)
+           if (!write_word (svf, stack[j]))            return 0;
+       stkslen = 8 + 2*nstk;
+       }
+
+     /* Write out the rest of the stack frames. */
+     for (i=n; i>0; --i)
+       {
+       p = stack + frames[i] - 4;      /* Points to call frame. */
+       nvars = (p[0] & 0x0F00) >> 8;
+       nargs =  p[0] & 0x00FF;
+       nstk  =  frames[i] - frames[i-1] - nvars - 4;
+       pc    =  ((zlong) p[3] << 9) | p[2];
+
+       switch (p[0] & 0xF000)  /* Check type of call. */
+           {
+           case 0x0000:        /* Function. */
+               var = zmp[pc];
+               pc = ((pc + 1) << 8) | nvars;
+               break;
+           case 0x1000:        /* Procedure. */
+               var = 0;
+               pc = (pc << 8) | 0x10 | nvars;  /* Set procedure flag. */
+               break;
+           /* case 0x2000: */
+           default:
+               runtime_error ("Can't save while in interrupt");
+               return 0;
+           }
+       if (nargs != 0)
+           nargs = (1 << nargs) - 1;   /* Make args into bitmap. */
+
+       /* Write the main part of the frame... */
+       if (!write_long (svf, pc)
+           || !write_byte (svf, var)
+           || !write_byte (svf, nargs)
+           || !write_word (svf, nstk))                 return 0;
+
+       /* Write the variables and eval stack. */
+       for (j=0, ++p; j<nvars+nstk; ++j, --p)
+           if (!write_word (svf, *p))                  return 0;
+
+       /* Calculate length written thus far. */
+       stkslen += 8 + 2 * (nvars + nstk);
+       }
+
+     /* Fill in variable chunk lengths. */
+     ifzslen = 3*8 + 4 + 14 + cmemlen + stkslen;
+     if (cmemlen & 1)
+       ++ifzslen;
+     (void) fseek (svf,         4, SEEK_SET);
+     if (!write_long (svf, ifzslen))                   return 0;
+     (void) fseek (svf, cmempos+4, SEEK_SET);
+     if (!write_long (svf, cmemlen))                   return 0;
+     (void) fseek (svf, stkspos+4, SEEK_SET);
+     if (!write_long (svf, stkslen))                   return 0;
+
+     /* After all that, still nothing went wrong! */
+     return 1;
+     }
+ #endif        /* USE_QUETZAL */
diff -c -N frotz-2.32/screen.c frotz-2.32-d/screen.c
*** frotz-2.32/screen.c Thu Sep 18 17:17:40 1997
--- frotz-2.32-d/screen.c       Thu Nov 25 12:39:13 1999
***************
*** 587,593 ****
  *
  */

! static void erase_window (zword win)
 {
     zword y = wp[win].y_pos;
     zword x = wp[win].x_pos;
--- 587,593 ----
  *
  */

! void erase_window (zword win)
 {
     zword y = wp[win].y_pos;
     zword x = wp[win].x_pos;
diff -c -N frotz-2.32/strictz.c frotz-2.32-d/strictz.c
*** frotz-2.32/strictz.c
--- frotz-2.32-d/strictz.c      Wed Aug  4 13:25:29 1999
***************
*** 0 ****
--- 1,86 ----
+ /*
+  * strictz.c
+  *
+  * Strict Z error reporting functions
+  */
+
+ #include "frotz.h"
+
+ #ifdef STRICTZ
+
+ #include <stdio.h>
+
+ /* Define stuff for stricter Z-code error checking, for the generic
+    Unix/DOS/etc terminal-window interface. Feel free to change the way
+    player prefs are specified, or replace report_zstrict_error()
+    completely if you want to change the way errors are reported. */
+
+ int strictz_report_mode;
+ static int strictz_error_count[STRICTZ_NUM_ERRORS];
+
+ /*
+  * report_strictz_error
+  *
+  * This handles Z-code error conditions which ought to be fatal errors,
+  * but which players might want to ignore for the sake of finishing the
+  * game.
+  *
+  * The error is provided as both a numeric code and a string. This allows
+  * us to print a warning the first time a particular error occurs, and
+  * ignore it thereafter.
+  *
+  * errnum : Numeric code for error (0 to STRICTZ_NUM_ERRORS-1)
+  * errstr : Text description of error
+  *
+  */
+
+ void init_strictz ()
+ {
+     int i;
+
+     /* Initialize the STRICTZ variables. */
+
+     strictz_report_mode = STRICTZ_DEFAULT_REPORT_MODE;
+
+     for (i = 0; i < STRICTZ_NUM_ERRORS; i++) {
+         strictz_error_count[i] = 0;
+     }
+ }
+
+ void report_strictz_error (int errnum, const char *errstr)
+ {
+     int wasfirst;
+
+     if (errnum <= 0 || errnum >= STRICTZ_NUM_ERRORS)
+       return;
+
+     if (strictz_report_mode == STRICTZ_REPORT_FATAL) {
+       flush_buffer ();
+       os_fatal (errstr);
+       return;
+     }
+
+     wasfirst = (strictz_error_count[errnum] == 0);
+     strictz_error_count[errnum]++;
+
+     if ((strictz_report_mode == STRICTZ_REPORT_ALWAYS)
+       || (strictz_report_mode == STRICTZ_REPORT_ONCE && wasfirst)) {
+       char buf[256];
+       long pc;
+
+       GET_PC (pc);
+       sprintf (buf, "Warning: %s (PC = %lx)", errstr, pc);
+       print_string (buf);
+
+       if (strictz_report_mode == STRICTZ_REPORT_ONCE) {
+           print_string(" (will ignore further occurrences)");
+       } else {
+           sprintf (buf, " (occurrence %d)", strictz_error_count[errnum]);
+           print_string(buf);
+       }
+       new_line ();
+     }
+
+ } /* report_strictz_error */
+
+ #endif /* STRICTZ */
diff -c -N frotz-2.32/ux_frotz.h frotz-2.32-d/ux_frotz.h
*** frotz-2.32/ux_frotz.h       Sat Oct  4 03:12:13 1997
--- frotz-2.32-d/ux_frotz.h     Fri Nov 26 07:07:39 1999
***************
*** 5,10 ****
--- 5,15 ----
  *
  */

+ /* Some regular curses (not ncurses) libraries don't do this correctly. */
+ #ifndef getmaxyx
+       #define getmaxyx(w, y, x)       (y) = getmaxy(w), (x) = getmaxx(w)
+ #endif
+
 extern int current_text_style;    /* ux_init */
 extern char unix_plain_ascii;     /* ux_init */
 extern int current_color;         /* ux_text */
diff -c -N frotz-2.32/ux_init.c frotz-2.32-d/ux_init.c
*** frotz-2.32/ux_init.c        Sat Oct  4 03:12:10 1997
--- frotz-2.32-d/ux_init.c      Sat Dec 18 03:17:09 1999
***************
*** 139,145 ****

     do {

!       c = getopt(argc, argv, "aAb:c:df:h:il:oOpPr:s:S:tu:w:x");

       switch(c) {
         case 'a': option_attribute_assignment = 1; break;
--- 139,149 ----

     do {

!       c = getopt(argc, argv, "aAb:c:df:h:il:oOpPr:s:S:tu:w:x"
! #ifdef STRICTZ
!                              "Z:"
! #endif
!                 );

       switch(c) {
         case 'a': option_attribute_assignment = 1; break;
***************
*** 170,175 ****
--- 174,186 ----
         case 'u': option_undo_slots = atoi(optarg); break;
         case 'w': user_screen_width = atoi(optarg); break;
         case 'x': option_expand_abbreviations = 1; break;
+ #ifdef STRICTZ
+         case 'Z': strictz_report_mode = atoi(optarg);
+                   if ((strictz_report_mode < STRICTZ_REPORT_NEVER) ||
+                       (strictz_report_mode > STRICTZ_REPORT_FATAL))
+                     strictz_report_mode = STRICTZ_DEFAULT_REPORT_MODE;
+                   break;
+ #endif
       }

     } while (c != EOF);
***************
*** 176,195 ****

     if (optind != argc - 1) {
       puts (INFORMATION);
       exit (1);
     }

     /* Save the story file name */

     story_name = argv[optind];

     /* Strip path off the story file name */

!     p = story_name;

     for (i = 0; story_name[i] != 0; i++)
         if (story_name[i] == '/')
!         p = story_name + i + 1;

     for (i = 0; p[i] != '\0' && p[i] != '.'; i++)
         stripped_story_name[i] = p[i];
--- 187,223 ----

     if (optind != argc - 1) {
       puts (INFORMATION);
+ #ifdef STRICTZ
+       printf ("  -Z # stricter error checking (default = %d)\n"
+           "\t(%d: none; %d: report first error; %d: report all errors;\n"
+           "\t %d: exit after any error)\n",
+           STRICTZ_DEFAULT_REPORT_MODE, STRICTZ_REPORT_NEVER,
+           STRICTZ_REPORT_ONCE, STRICTZ_REPORT_ALWAYS,
+           STRICTZ_REPORT_FATAL);
+ #endif
       exit (1);
     }

     /* Save the story file name */

+ #ifdef IGNORE_ERR
+     option_ignore_errors = 1;
+ #endif
+
+ #ifdef NOLATIN1
+     unix_plain_ascii = 1;
+ #endif
+
+
     story_name = argv[optind];

     /* Strip path off the story file name */

!     p = (char *)story_name;

     for (i = 0; story_name[i] != 0; i++)
         if (story_name[i] == '/')
!           p = (char *)story_name + i + 1;

     for (i = 0; p[i] != '\0' && p[i] != '.'; i++)
         stripped_story_name[i] = p[i];
***************
*** 320,326 ****
       else
         h_default_background = BLUE_COLOUR;
       os_set_colour(h_default_foreground, h_default_background);
-       bkgdset(current_color);
       erase();
       /*os_erase_area(1, 1, h_screen_rows, h_screen_cols);*/
     }
--- 348,353 ----
diff -c -N frotz-2.32/ux_input.c frotz-2.32-d/ux_input.c
*** frotz-2.32/ux_input.c       Fri Oct 17 22:11:33 1997
--- frotz-2.32-d/ux_input.c     Fri Nov 26 07:16:35 1999
***************
*** 10,15 ****
--- 10,16 ----
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+ #include <limits.h>

 #include <sys/time.h>

***************
*** 24,33 ****

 static struct timeval global_timeout;

 #define MAX_HISTORY 20
 static char *history_buffer[MAX_HISTORY];
! static short history_pointer = 0; /* Pointer to next available slot */
! static short history_frame = 0; /* Pointer to what the user's looking at */

 extern bool is_terminator (zchar);
 extern void read_string (int, zchar *);
--- 25,46 ----

 static struct timeval global_timeout;

+ /* Some special characters. */
+ #define CHR_CTRL_L 12
+ #define CHR_CTRL_R 18
+ #define CHR_DEL 127
+ #define MOD_META 0x80
+
+ /* These are useful for circular buffers.
+  */
+ #define RING_DEC( ptr, beg, end) (ptr > (beg) ? --ptr : (ptr = (end)))
+ #define RING_INC( ptr, beg, end) (ptr < (end) ? ++ptr : (ptr = (beg)))
+
 #define MAX_HISTORY 20
 static char *history_buffer[MAX_HISTORY];
! static char **history_next = history_buffer; /* Next available slot. */
! static char **history_view = history_buffer; /* What the user is looking at. */
! #define history_end (history_buffer + MAX_HISTORY - 1)

 extern bool is_terminator (zchar);
 extern void read_string (int, zchar *);
***************
*** 56,162 ****
     }
 }

 /*
  * unix_read_char
  *
!  * This uses the curses getch() routine to get the next character typed,
!  * and returns it unless the global timeout is reached.  It returns values
!  * which the standard considers to be legal input, and also returns editing
!  * and frotz hot keys.  If called with a non-zero flag, it will also return
!  * line-editing keys like INSERT, etc,
  *
  */
!
! static int unix_read_char(int flag)
 {
     int c;
-     struct timeval ctime;

     while(1) {
!       /* Timed keyboard input.  Crude but functional. */
!       if (global_timeout.tv_sec) {
!         nodelay(stdscr, TRUE);
!         do {
!               c = getch();
!               if (c == ERR) {
!                   gettimeofday(&ctime, NULL);
!                   if ((ctime.tv_sec >= global_timeout.tv_sec) &&
!                       (ctime.tv_usec >= global_timeout.tv_usec)) {
!                       nodelay(stdscr, FALSE);
!                       return 0;
!                   }
!               }
!           } while (c == ERR);
!           nodelay(stdscr, FALSE);
!       }
!       /* The easy way. */
!       else c = getch();
!
!       /* Catch 98% of all input right here... */
!       if ( ((c >= 32) && (c <= 126)) || (c == 13) || (c == 8))
!         return c;
!
!       /* ...and the other 2% makes up 98% of the code. :( */
!       switch(c) {
!         /* Lucian P. Smith reports KEY_ENTER on Irix 5.3.  10 has never
!            been reported, but I'm leaving it in just in case. */
!         case 10: case KEY_ENTER: return 13;
!         /* I've seen KEY_BACKSPACE returned on some terminals. */
!         case KEY_BACKSPACE: return 8;
!         /* On seven-bit connections, "Alt-Foo" is returned as an escape
!            followed by the ASCII value of the letter.  We have to decide
!            here whether to return a single escape or a frotz hot key. */
!         case 27: nodelay(stdscr, TRUE); c = getch(); nodelay(stdscr, FALSE);
!                  if (c == ERR) return 27;
!                  switch(c) {
!                    case 112: return ZC_HKEY_PLAYBACK; /* Alt-P */
!                    case 114: return ZC_HKEY_RECORD; /* Alt-R */
!                    case 115: return ZC_HKEY_SEED; /* Alt-S */
!                    case 117: return ZC_HKEY_UNDO; /* Alt-U */
!                    case 110: return ZC_HKEY_RESTART; /* Alt-N */
!                    case 120: return ZC_HKEY_QUIT; /* Alt-X */
!                  case 100: return ZC_HKEY_DEBUG; /* Alt-D */
!                  case 104: return ZC_HKEY_HELP; /* Alt-H */
!                    default: return 27;
!                  }
!                  break;
       /* The standard function key block. */
!         case KEY_UP: return 129;
!         case KEY_DOWN: return 130;
!         case KEY_LEFT: return 131;
!         case KEY_RIGHT: return 132;
!         case KEY_F(1): return 133; case KEY_F(2): return 134;
!         case KEY_F(3): return 135; case KEY_F(4): return 136;
!         case KEY_F(5): return 137; case KEY_F(6): return 138;
!         case KEY_F(7): return 139; case KEY_F(8): return 140;
!         case KEY_F(9): return 141; case KEY_F(10): return 142;
!         case KEY_F(11): return 143; case KEY_F(12): return 144;
!         /* Curses can't differentiate keypad numbers from cursor keys. Damn. */
!         /* This will catch the alt-keys on 8-bit clean input streams... */
!         case 240: return ZC_HKEY_PLAYBACK; /* Alt-P */
!         case 242: return ZC_HKEY_RECORD; /* Alt-R */
!         case 243: return ZC_HKEY_SEED; /* Alt-S */
!         case 245: return ZC_HKEY_UNDO; /* Alt-U */
!         case 238: return ZC_HKEY_RESTART; /* Alt-N */
!         case 248: return ZC_HKEY_QUIT; /* Alt-X */
!         case 228: return ZC_HKEY_DEBUG; /* Alt-D */
!         case 232: return ZC_HKEY_HELP; /* Alt-H */
 #ifdef EMACS_EDITING
!         case 21: return 27; /* Ctrl-U, erase line */
!         case 2: return 131; /* Ctrl-B, left arrow  */
!         case 6: return 132; /* Ctrl-F, right arrow */
!         case 16: return 129; /* Ctrl-P, up arrow */
!         case 14: return 130; /* Ctrl-N, down arrow */
!         case 1: c = KEY_HOME; break; /* Ctrl-A */
!         case 4: c = 127; break; /* Ctrl-D */
!         case 5: c = KEY_END; break; /* Ctrl-E */
 #endif
       default: break; /* Who knows? */
!       }

!       /* Finally, if we're in full line mode (os_read_line), we might return
!          codes which aren't legal Z-machine keys but are used by the editor. */
!       if (flag) return c;
     }
 }

--- 69,215 ----
     }
 }

+ /* This returns the number of milliseconds until the input timeout
+  * elapses or zero if it has already elapsed.  -1 is returned if no
+  * timeout is in effect, otherwise the return value is non-negative.
+  */
+ static int timeout_to_ms()
+ {
+     struct timeval now, diff;
+
+     if (global_timeout.tv_sec == 0) return -1;
+     gettimeofday( &now, NULL);
+     diff.tv_usec = global_timeout.tv_usec - now.tv_usec;
+     if (diff.tv_usec < 0) {
+       /* Carry */
+       now.tv_sec++;
+       diff.tv_usec += 1000000;
+     }
+     diff.tv_sec = global_timeout.tv_sec - now.tv_sec;
+     if (diff.tv_sec < 0) return 0;
+     if (diff.tv_sec >= INT_MAX / 1000 - 1) /* Paranoia... */
+       return INT_MAX - 1000;
+     return diff.tv_sec * 1000 + diff.tv_usec / 1000;
+ }
+
 /*
  * unix_read_char
  *
!  * This uses the curses getch() routine to get the next character
!  * typed, and returns it.  It returns values which the standard
!  * considers to be legal input, and also returns editing and frotz hot
!  * keys.  If called with extkeys set it will also return line-editing
!  * keys like INSERT etc.
  *
+  * If unix_set_global_timeout has been used to set a global timeout
+  * this routine may also return ZC_TIME_OUT if input times out.
  */
! static int unix_read_char(int extkeys)
 {
     int c;

     while(1) {
!       timeout( timeout_to_ms());
!       c = getch();
!
!       /* Catch 98% of all input right here... */
!       if ((c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX)
!           || (!unix_plain_ascii
!               && c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX))
!           return c;
!
!       /* ...and the other 2% makes up 98% of the code. :( */
!
!       /* On many terminals the backspace key returns DEL. */
!       if (c == erasechar()) return ZC_BACKSPACE;;
!       switch(c) {
!       /* Normally ERR means timeout.  I suppose we might also get
!          ERR if a signal hits getch. */
!       case ERR:
!           if (timeout_to_ms() == 0)
!               return ZC_TIME_OUT;
!           else
!               continue;
!       /* Screen decluttering. */
!       case CHR_CTRL_L: case CHR_CTRL_R:
!           clearok( curscr, 1); refresh(); clearok( curscr, 0);
!           continue;
!       /* Lucian P. Smith reports KEY_ENTER on Irix 5.3.  LF has never
!          been reported, but I'm leaving it in just in case. */
!       case '\n': case '\r': case KEY_ENTER: return ZC_RETURN;
!       /* I've seen KEY_BACKSPACE returned on some terminals. */
!       case KEY_BACKSPACE: case '\b': return ZC_BACKSPACE;
!       /* On terminals with 8-bit character sets or 7-bit connections
!          "Alt-Foo" may be returned as an escape followed by the ASCII
!          value of the letter.  We have to decide here whether to
!          return a single escape or a frotz hot key. */
!       case ZC_ESCAPE:
!           nodelay(stdscr, TRUE); c = getch(); nodelay(stdscr, FALSE);
!           switch(c) {
!           case ERR: return ZC_ESCAPE;
!           case 'p': return ZC_HKEY_PLAYBACK;
!           case 'r': return ZC_HKEY_RECORD;
!           case 's': return ZC_HKEY_SEED;
!           case 'u': return ZC_HKEY_UNDO;
!           case 'n': return ZC_HKEY_RESTART;
!           case 'x': return ZC_HKEY_QUIT;
!           case 'd': return ZC_HKEY_DEBUG;
!           case 'h': return ZC_HKEY_HELP;
!           default: continue;  /* Ignore unknown combinations. */
!           }
       /* The standard function key block. */
!       case KEY_UP: return ZC_ARROW_UP;
!       case KEY_DOWN: return ZC_ARROW_DOWN;
!       case KEY_LEFT: return ZC_ARROW_LEFT;
!       case KEY_RIGHT: return ZC_ARROW_RIGHT;
!       case KEY_F(1): return ZC_FKEY_MIN;
!       case KEY_F(2): return ZC_FKEY_MIN + 1;
!       case KEY_F(3): return ZC_FKEY_MIN + 2;
!       case KEY_F(4): return ZC_FKEY_MIN + 3;
!       case KEY_F(5): return ZC_FKEY_MIN + 4;
!       case KEY_F(6): return ZC_FKEY_MIN + 5;
!       case KEY_F(7): return ZC_FKEY_MIN + 6;
!       case KEY_F(8): return ZC_FKEY_MIN + 7;
!       case KEY_F(9): return ZC_FKEY_MIN + 8;
!       case KEY_F(10): return ZC_FKEY_MIN + 9;
!       case KEY_F(11): return ZC_FKEY_MIN + 10;
!       case KEY_F(12): return ZC_FKEY_MIN + 11;
!       /* Curses can't differentiate keypad numbers from cursor keys,
!          which is annoying, as cursor and keypad keys have
!          nothing to do with each other on, say, a vt200.
!          So we can only read 1, 3, 5, 7 and 9 from the keypad.  This
!          would be so silly that we choose not to provide keypad keys at all.
!       */
!         /* Catch the meta key on those plain old ASCII terminals where
!          it sets the high bit.  This only works in
!          unix_plain_ascii mode: otherwise these character codes
!          would have been interpreted according to ISO-Latin-1
!          earlier. */
!       case MOD_META | 'p': return ZC_HKEY_PLAYBACK;
!       case MOD_META | 'r': return ZC_HKEY_RECORD;
!       case MOD_META | 's': return ZC_HKEY_SEED;
!       case MOD_META | 'u': return ZC_HKEY_UNDO;
!       case MOD_META | 'n': return ZC_HKEY_RESTART;
!       case MOD_META | 'x': return ZC_HKEY_QUIT;
!       case MOD_META | 'd': return ZC_HKEY_DEBUG;
!       case MOD_META | 'h': return ZC_HKEY_HELP;
 #ifdef EMACS_EDITING
!       case 21: return ZC_ESCAPE; /* Ctrl-U, erase line */
!       case 2: return ZC_ARROW_LEFT; /* Ctrl-B */
!       case 6: return ZC_ARROW_RIGHT; /* Ctrl-F */
!       case 16: return ZC_ARROW_UP; /* Ctrl-P */
!       case 14: return ZC_ARROW_DOWN; /* Ctrl-N */
!       case 1: c = KEY_HOME; break; /* Ctrl-A */
!       case 4: c = CHR_DEL; break; /* Ctrl-D */
!       case 5: c = KEY_END; break; /* Ctrl-E */
 #endif
       default: break; /* Who knows? */
!       }

!       /* Finally, if we're in full line mode (os_read_line), we
!          might return codes which aren't legal Z-machine keys but
!          are used by the editor. */
!       if (extkeys) return c;
     }
 }

***************
*** 164,171 ****
 /*
  * unix_add_to_history
  *
!  * Add the given string to the next available history buffer slot.  Commandeer
!  * that slot if necessary using realloc.
  *
  */

--- 217,223 ----
 /*
  * unix_add_to_history
  *
!  * Add the given string to the next available history buffer slot.
  *
  */

***************
*** 172,185 ****
 static void unix_add_to_history(zchar *str)
 {

!     if (history_buffer[history_pointer] == NULL)
!       history_buffer[history_pointer] = (char *) malloc(strlen(str) + 1);
!     else
!       history_buffer[history_pointer] =
!         (char *) realloc(history_buffer[history_pointer], strlen(str) + 1);
!     strcpy(history_buffer[history_pointer], str);
!     history_pointer = ((history_pointer + 1) % MAX_HISTORY);
!     history_frame = history_pointer; /* Reset user frame after each line */
 }

 /*
--- 224,235 ----
 static void unix_add_to_history(zchar *str)
 {

!     if (*history_next != NULL)
!       free( *history_next);
!     *history_next = (char *)malloc(strlen(str) + 1);
!     strcpy( *history_next, str);
!     RING_INC( history_next, history_buffer, history_end);
!     history_view = history_next; /* Reset user frame after each line */
 }

 /*
***************
*** 186,206 ****
  * unix_history_back
  *
  * Copy last available string to str, if possible.  Return 1 if successful.
!  *
  */
!
! static int unix_history_back(zchar *str)
 {

!     history_frame--; if (history_frame==-1) history_frame = (MAX_HISTORY - 1);
!     if ((history_frame == history_pointer) ||
!         (history_buffer[history_frame] == NULL)) {
!         beep(); history_frame = (history_frame + 1) % MAX_HISTORY;
!         return 0;
!     }
!     strcpy(str, history_buffer[history_frame]);
     return 1;
-
 }

 /*
--- 236,260 ----
  * unix_history_back
  *
  * Copy last available string to str, if possible.  Return 1 if successful.
!  * Only lines of at most maxlen characters will be considered.  In addition
!  * the first searchlen characters of the history entry must match those of str.
  */
! static int unix_history_back(zchar *str, int searchlen, int maxlen)
 {
+     char **prev = history_view;

!     do {
!       RING_DEC( history_view, history_buffer, history_end);
!       if ((history_view == history_next)
!           || (*history_view == NULL)) {
!           beep();
!           history_view = prev;
!           return 0;
!       }
!     } while (strlen( *history_view) > maxlen
!            || (searchlen != 0 && strncmp( str, *history_view, searchlen)));
!     strcpy(str + searchlen, *history_view + searchlen);
     return 1;
 }

 /*
***************
*** 207,228 ****
  * unix_history_forward
  *
  * Opposite of unix_history_back, and works in the same way.
-  *
  */
!
! static int unix_history_forward(zchar *str)
 {

!     history_frame = (history_frame + 1) % MAX_HISTORY;
!     if ((history_frame == history_pointer) ||
!         (history_buffer[history_frame] == NULL)) {
!         beep(); history_frame--; if (history_frame == -1) history_frame =
!                                                             (MAX_HISTORY - 1);
!         return 0;
!     }
!     strcpy(str, history_buffer[history_frame]);
     return 1;
-
 }

 /*
--- 261,283 ----
  * unix_history_forward
  *
  * Opposite of unix_history_back, and works in the same way.
  */
! static int unix_history_forward(zchar *str, int searchlen, int maxlen)
 {
+     char **prev = history_view;

!     do {
!       RING_INC( history_view, history_buffer, history_end);
!       if ((history_view == history_next)
!           || (*history_view == NULL)) {
!           beep();
!           history_view = prev;
!           return 0;
!       }
!     } while (strlen( *history_view) > maxlen
!            || (searchlen != 0 && strncmp( str, *history_view, searchlen)));
!     strcpy(str + searchlen, *history_view + searchlen);
     return 1;
 }

 /*
***************
*** 253,259 ****
  *     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.
--- 308,314 ----
  *     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 ZC_TIME_OUT).
  *
  * The complete input line including the cursor must fit in "width"
  * screen units.
***************
*** 273,417 ****

 zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued)
 {
!     int ch, scrpos, pos, y, x;
!     char insert_flag = 1;  /* Insert mode is now default */

-     scrpos = pos = strlen((char *) buf);
-
-     if (!continued)
-       history_frame = history_pointer;  /* Reset user's history view */
-
-     unix_set_global_timeout(timeout);
-
     getyx(stdscr, y, x);
!
!     do {
!         refresh(); /* Shouldn't be necessary, but is, to print spaces */
!
!         ch = unix_read_char(1);
!
!       /* Backspace */
!         if ((ch == 8) && (scrpos)) {
!             mvdelch(y, --x);
!             pos--; scrpos--;
!             if (scrpos != pos)
!               memmove(&(buf[scrpos]), &(buf[scrpos+1]), pos-scrpos);
!         }
!
!       /* Delete */
!       if (((ch == 127) || (ch == KEY_DC)) && (scrpos < pos)) {
!           delch(); pos--;
!           memmove(&(buf[scrpos]), &(buf[scrpos+1]), pos-scrpos);
!       }
!
!         /* Left key */
!         if ((ch == 131) && (scrpos)) {
!             scrpos--;
!             move(y, --x);
!         }
!         /* Right key */
!         if ((ch == 132) && (scrpos < pos)) {
!             scrpos++;
!             move(y, ++x);
!         }

!         /* Home */
!         if (ch == KEY_HOME) {
!             x -= scrpos; scrpos = 0;
             move(y, x);
!         }
!         /* End */
!         if (ch == KEY_END) {
!             x += (pos - scrpos); scrpos = pos;
             move(y,x);
!         }
!
!         /* Insert */
!         if (ch == KEY_IC)
!           insert_flag = (insert_flag ? 0 : 1);
!
!         /* Up and down keys */
!         if (ch == 129) {
!             if (unix_history_back(buf)) {
!               x -= scrpos;
!               move(y, x);
!               while (scrpos) {addch(' '); scrpos--;}
!               move(y, x);
!               addstr(buf);
!               scrpos = pos = strlen(buf);
!               x += scrpos;
             }
!         }
!         if (ch == 130) {
!             if (unix_history_forward(buf)) {
!               x -= scrpos;
!               move(y, x);
!               while(scrpos) {addch(' '); scrpos--;}
!               move(y, x);
!               addstr(buf);
!               scrpos = pos = strlen(buf);
!               x += scrpos;
             }
!         }
!
!       /* Page up/down (passthrough as up/down arrows for beyond zork) */
!         if (ch == KEY_PPAGE) ch = 129;
!         if (ch == KEY_NPAGE) ch = 130;
!
!         /* Escape */
!         if (ch == 27) {
!           /* Move cursor to end of buffer first */
!             x += (pos - scrpos); scrpos = pos; move(y,x);
             x -= scrpos;
             move(y, x);
!             while (scrpos) {addch(' '); scrpos--;}
!             move(y, x); pos = 0;
!         }
!
!       /* Tab */
!       if ((ch == 9) && (scrpos == pos)) {
!           int status;
!           zchar extension[10];
!
!           status = completion(buf, extension);
!           if (status != 2) {
!             addstr(extension);
!             strcpy(&buf[pos], extension);
!             pos += strlen(extension); scrpos += strlen(extension);
           }
!           if (status) beep();
!       }
!
!         /* ASCII printable */
!         if ((ch >= 32) && (ch <= 126)) {
!             if (pos == scrpos) {
!                 /* Append to end of buffer */
!                 if ((pos < max) && (pos < width)) {
!                     buf[pos++] = (char) ch;
!                     addch(ch);
!                     scrpos++; x++;
!                 } else beep();
!             }
!             else {
!                 /* Insert/overwrite in middle of buffer */
!                 if (insert_flag) {
!                     memmove(&buf[scrpos+1], &buf[scrpos], pos-scrpos);
!                     buf[scrpos++] = (char) ch;
!                     insch(ch);
!                     pos++; x++; move(y, x);
!                 } else {
!                     buf[scrpos++] = (char) ch;
!                     addch(ch);
!                     x++;
!                 }
!             }
         }
!     } while (!is_terminator(ch));
!
!     buf[pos] = '\0';
!     if (ch == 13) unix_add_to_history(buf);
!     return ch;
!
 }/* os_read_line */

 /*
--- 328,491 ----

 zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued)
 {
!     int ch, y, x, len = strlen( (char *)buf);
!     /* These are static to allow input continuation to work smoothly. */
!     static int scrpos = 0, searchpos = -1, insert_flag = 1;

     getyx(stdscr, y, x);
!     if (width < max) max = width;
!     /* Better be careful here or it might segv.  I wonder if we should just
!        ignore 'continued' and check for len > 0 instead?  Might work better
!        with Beyond Zork. */
!     if (continued && scrpos <= len && searchpos <= len) {
!       x += scrpos - len;
!       move( y, x);
!     } else {
!       scrpos = len;
!       history_view = history_next; /* Reset user's history view. */
!       searchpos = -1;         /* -1 means initialize from len. */
!       insert_flag = 1;        /* Insert mode is now default. */
!     }

!     unix_set_global_timeout(timeout);
!     for (;;) {
!       /* Maybe there's a cleaner way to do this, but refresh() is */
!       /* still needed here to print spaces.  --DG */
!       refresh();
!         switch (ch = unix_read_char(1)) {
!       case ZC_BACKSPACE:
!           if (scrpos > 0) {
!               mvdelch(y, --x);
!               len--; scrpos--; searchpos = -1;
!               if (scrpos != len)
!                   memmove(&(buf[scrpos]), &(buf[scrpos+1]), len-scrpos);
!           }
!           break;
!       case CHR_DEL:
!       case KEY_DC:            /* Delete Character */
!           if (scrpos < len) {
!               delch();
!               len--; searchpos = -1;
!               memmove(&(buf[scrpos]), &(buf[scrpos+1]), len-scrpos);
!           }
!           continue;           /* Don't feed is_terminator bad zchars. */
!       case ZC_ARROW_LEFT:
!           if (scrpos > 0) {
!               scrpos--;
!               move(y, --x);
!           }
!           continue;
!         case ZC_ARROW_RIGHT:
!           if (scrpos < len) {
!               scrpos++;
!               move(y, ++x);
!           }
!           continue;
!       case KEY_HOME:
!             x -= scrpos;
!           scrpos = 0;
             move(y, x);
!           continue;
!         case KEY_END:
!             x += (len - scrpos); scrpos = len;
             move(y,x);
!           continue;
!       case KEY_IC:            /* Insert Character */
!           insert_flag = !insert_flag;
!           continue;
!       case ZC_ARROW_UP:
!           if (searchpos < 0)
!               searchpos = len;
!             if (unix_history_back(buf, searchpos, max)) {
!               x -= scrpos;
!               move(y, x);
!               clrtoeol();
!               addstr(buf);
!               x += scrpos = len = strlen(buf);
             }
!           continue;
!         case ZC_ARROW_DOWN:
!           if (searchpos < 0)
!               searchpos = len;
!             if (unix_history_forward(buf, searchpos, max)) {
!               x -= scrpos;
!               move(y, x);
!               clrtoeol();
!               addstr(buf);
!               x += scrpos = len = strlen(buf);
             }
!           continue;
!       /* Passthrough as up/down arrows for Beyond Zork. */
!       case KEY_PPAGE: ch = ZC_ARROW_UP; break;
!       case KEY_NPAGE: ch = ZC_ARROW_DOWN; break;
!       case ZC_ESCAPE:
             x -= scrpos;
             move(y, x);
!           clrtoeol();
!           scrpos = len = 0;
!           searchpos = -1;
!           history_view = history_next;
!           break;
!       case '\t':
!           /* This really should be fixed to work also in the middle of a
!              sentence. */
!           if (scrpos == len) {
!               int status;
!               zchar extension[10];
!
!               buf[len] = '\0';
!               status = completion( buf, extension);
!               if (status != 2) {
!                   strncpy( &buf[len], extension, max - len);
!                   len += strlen( extension);
!                   if (len > max) {len = max; status = 1;}
!                   addnstr( extension, len - scrpos);
!                   x += len - scrpos;
!                   scrpos = len;
!                   searchpos = -1;
!               }
!               if (status) beep();
           }
!           continue;           /* TAB is invalid as an input character. */
!       default:
!           /* ASCII or ISO-Latin-1 */
!           if ((ch >= ZC_ASCII_MIN && ch <= ZC_ASCII_MAX)
!               || (!unix_plain_ascii
!                   && ch >= ZC_LATIN1_MIN && ch <= ZC_LATIN1_MAX)) {
!               if (len == scrpos)
!                   /* Append to end of buffer */
!                   if (len < max) {
!                       buf[len++] = (char) ch;
!                       addch(ch);
!                       scrpos++; x++;
!                   } else
!                       beep();
!               else
!                   /* Insert/overwrite in middle of buffer */
!                   if (insert_flag) {
!                       memmove(&buf[scrpos+1], &buf[scrpos], len-scrpos);
!                       buf[scrpos++] = (char) ch;
!                       insch(ch);
!                       len++; x++; move(y, x);
!                   } else {
!                       buf[scrpos++] = (char) ch;
!                       addch(ch);
!                       x++;
!                   }
!               searchpos = -1;
!           }
         }
!       if (is_terminator(ch)) {
!           buf[len] = '\0';
!           if (ch == ZC_RETURN)
!               unix_add_to_history(buf);
!           /* Games don't know about line editing and might get
!              confused if the cursor is not at the end of the input
!              line. */
!           move( y, x + len - scrpos);
!           return ch;
!       }
!     }
 }/* os_read_line */

 /*
***************
*** 487,489 ****
--- 561,569 ----
     return 1;

 } /* os_read_file_name */
+
+ /*
+  * Local Variables:
+  * c-basic-offset: 4
+  * End:
+  */
diff -c -N frotz-2.32/ux_text.c frotz-2.32-d/ux_text.c
*** frotz-2.32/ux_text.c        Sat Oct  4 03:12:10 1997
--- frotz-2.32-d/ux_text.c      Thu Nov 25 12:39:23 1999
***************
*** 111,136 ****
  *
  */

- #ifdef COLOR_SUPPORT
- static int colorspace[10][10];
- static int count = 0;
- #endif
-
 void os_set_colour (int new_foreground, int new_background)
 {
 #ifdef COLOR_SUPPORT
!     int saved_style;

-     saved_style = current_text_style;
     if (new_foreground == 1) new_foreground = h_default_foreground;
     if (new_background == 1) new_background = h_default_background;
     if (!colorspace[new_foreground][new_background]) {
!       init_pair(++count, unix_convert(new_foreground), unix_convert(new_background));
!       colorspace[new_foreground][new_background] = count;
     }
     current_color = COLOR_PAIR(colorspace[new_foreground][new_background]);
!     os_set_text_style(saved_style);
!
 #endif
 }/* os_set_colour */

--- 111,131 ----
  *
  */

 void os_set_colour (int new_foreground, int new_background)
 {
 #ifdef COLOR_SUPPORT
!     static int colorspace[10][10];
!     static int n_colors = 0;

     if (new_foreground == 1) new_foreground = h_default_foreground;
     if (new_background == 1) new_background = h_default_background;
     if (!colorspace[new_foreground][new_background]) {
!       init_pair(++n_colors, unix_convert(new_foreground),
!               unix_convert(new_background));
!       colorspace[new_foreground][new_background] = n_colors;
     }
     current_color = COLOR_PAIR(colorspace[new_foreground][new_background]);
!     bkgdset( current_color | ' ');
 #endif
 }/* os_set_colour */

***************
*** 154,165 ****
     if (new_style & REVERSE_STYLE) temp |= A_REVERSE;
     if (new_style & BOLDFACE_STYLE) temp |= A_BOLD;
     if (new_style & EMPHASIS_STYLE) temp |= A_UNDERLINE;
- #ifdef COLOR_SUPPORT
-     attrset(temp | current_color);
- #else
     attrset(temp);
- #endif
-
 }/* os_set_text_style */

 /*
--- 149,155 ----
***************
*** 211,217 ****
         addch(c);
       return;
     }
!     if (c >= 32 && c <= 126) {
         addch(c);
       return;
     }
--- 201,207 ----
         addch(c);
       return;
     }
!     if (c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX) {
         addch(c);
       return;
     }